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

@spider-mesh/core

v2.0.45

Published

Lightweight microservice framework for typescript, auto discovery, load-balancing, fault-torrent, multiple transporters

Readme

Spider Mesh Core

@spider-mesh/core is the runtime-agnostic Spider Mesh package.

It provides:

  • local microservice registration with decorators
  • typed remote service linking through proxies
  • RPC, discovery, and pubsub transporter contracts
  • a Registry for remote peer state and RPC routing
  • a SpiderMesh runtime for local services, transporters, and node metadata
  • NestJS helper adapters

Concrete transport implementations live in companion packages such as @spider-mesh/tcp and @spider-mesh/ws, or in your own custom transporters.

Install

bun add @spider-mesh/core rxjs reflect-metadata

For TypeScript projects using decorators, enable decorator metadata in your compiler settings.

Runtime Setup

Create a registry when you need remote peer discovery and RPC routing.

import { Registry, SpiderMesh } from '@spider-mesh/core'

const registry = new Registry()
const mesh = new SpiderMesh(registry)

Register transporter instances on the runtime:

import { Http2Pubsub, Http2Rpc, UdpDiscovery } from '@spider-mesh/tcp'

mesh.registerTransporter(new UdpDiscovery())
mesh.registerTransporter(new Http2Rpc())
mesh.registerTransporter(new Http2Pubsub())

Transporter capability is inferred by instance shape:

  • send() => RPC transporter
  • publish() => pubsub transporter
  • broadcast() => discovery transporter

Local Services

Use @Microservice() on local service classes and instantiate them normally.

import { BeforeMicroserviceOnline, Microservice } from '@spider-mesh/core'

@Microservice({ version: '1.0.0' })
export class UserService {
  private ready = false

  @BeforeMicroserviceOnline()
  async warmup() {
    this.ready = true
  }

  async getUser(id: string) {
    if (!this.ready) throw new Error('Service not ready')
    return { id, name: 'Ada' }
  }
}

new UserService()

@Microservice() emits the constructed instance into the shared LOCAL_SERVICES$ stream. SpiderMesh subscribes to that stream and adds the service to local node metadata.

Remote Services

Create a typed remote client with RemoteServiceLinker.link().

import { RemoteServiceLinker } from '@spider-mesh/core'

type UserServiceContract = {
  getUser(id: string): Promise<{ id: string; name: string }>
}

const users = RemoteServiceLinker.link<UserServiceContract>(mesh, {
  service: 'UserService',
})

await users.wait()

const user = await users.getUser('42')

Remote methods return RxJS observables and can also be awaited.

const user = await users.getUser('42')

users.getUser('42').subscribe(value => {
  console.log(value)
})

Waiting and watching

await users.wait(nodes => nodes.length > 0)

users.watch().subscribe(nodes => {
  console.log(nodes.map(node => node.node_id))
})

console.log(users.nodes)

Fan-out calls

users.__batch__getUser('42').subscribe(result => {
  console.log(result)
})

Each emission is either { node, data } or { node, error }.

RPC Options

The current RpcOptions shape is:

type RpcOptions<T = any> = {
  service: string
  method: string
  args: any[]
  fallback?: T
  timeout?: number
  retry?: number
  node_id?: string
  transporter?: string | { name?: string }
}

Examples:

import { firstValueFrom } from 'rxjs'

await users.set({ timeout: 3000, retry: 2 }).getUser('42')

await firstValueFrom(mesh.callRemoteService({
  service: 'UserService',
  method: 'getUser',
  args: ['42'],
  transporter: 'Http2Rpc',
}))

transporter may be:

  • a registered transporter name string
  • a class constructor (e.g. Http2Rpc)
  • an object with a name property

Events

Use SpiderMesh.linkEvent() to bind a topic by event class name.

class UserCreatedEvent {
  constructor(
    public readonly id: string,
    public readonly email: string,
  ) {}
}

const userCreated = mesh.linkEvent(UserCreatedEvent)

await userCreated.publish(new UserCreatedEvent('42', '[email protected]'))

const sub = userCreated.listen().subscribe(event => {
  console.log(event.id)
})

sub.unsubscribe()

Current event behavior:

  • publish() fans out through all registered pubsub transporters
  • listen() merges all transporter listeners into one shared stream
  • first subscribe adds the topic to local node metadata
  • last unsubscribe removes the topic from local node metadata
  • discovery transporters rebroadcast node metadata after topic changes

Registry

Registry stores remote peer and RPC routing state.

Current public methods:

  • getPeer(nodeId)
  • upsertPeer(node)
  • removePeer(nodeId)
  • listPeers(service?)
  • watch(service?)
  • pickRpcNode(service, { node_id? })
  • getRpcTransporterName(service)
  • listTopicNodes(topic)

Transporter Contracts

The contract source of truth is src/types.ts.

RPC transporter

type RpcTransporter = Observable<RpcEvent> & {
  linkRegistry?(registry: Registry): void
  send(data: RpcRequestPacket | RpcCancelPacket | RpcResponsePacket): Promise<{ cancel: () => void }>
}

send() returns a cancel function. For request packets, calling cancel() sends a RpcCancelPacket to the destination node, which causes the provider to stop any running stream. For response and cancel packets the returned cancel is a no-op.

SpiderMesh calls cancel() automatically when a subscriber unsubscribes from a stream that has not yet completed.

Packet types

type RpcRequestPacket = {
  kind: 'request'
  request_id: string
  service: string
  method: string
  args: any[]
  sender_node_id: string
  destination_node_id?: string  // explicit target; transporter falls back to registry round-robin when omitted
}

type RpcResponsePacket = {
  kind: 'response'
  request_id: string
  data?: any
  error?: SpiderMeshError | { code?: string; message: string }
  completed?: boolean
  destination_node_id?: string
}

type RpcCancelPacket = {
  kind: 'cancel'
  request_id: string
  destination_node_id?: string
}

Pubsub transporter

type PubsubTransporter = Observable<PubsubEvent> & {
  publish<T>(topic: string, data: T): Promise<void>
  listen<T>(topic: string): Observable<T>
  linkRegistry?(registry: Registry): void
}

Discovery transporter

type DiscoveryTransporter = Observable<DiscoveryEvent> & {
  linkRegistry?(registry: Registry): void
  broadcast(data: MdnsMessage<NodeMetadata>): Promise<void>
}

NestJS Helpers

The package exports:

  • NestJSExposeMicroservice(factory, metadata?)
  • NestJSLinkMicroservice(factory, transporter?)
  • NestJSLinkEvent(factory)

NestJSLinkMicroservice(factory, transporter?) forwards the optional transporter selector into RemoteServiceLinker.link().

Companion Packages

Use companion packages when you need concrete transport implementations.

  • @spider-mesh/tcp
  • @spider-mesh/ws

Keep runtime logic in @spider-mesh/core and import concrete transporters explicitly from the companion package.

Tests And Validation

The repository includes mock e2e coverage for:

  • RPC routing by transporter selector
  • async RemoteServiceLinker.wait() behavior
  • topic metadata lifecycle for linkEvent()
  • RPC routing across isolated child processes

Run tests with:

bun run test:e2e

Build with:

bun run build

NestJS Integration

Register SpiderMesh as a provider

import { Module } from '@nestjs/common'
import { Registry, SpiderMesh } from '@spider-mesh/core'
import { Http2Pubsub, Http2Rpc, UdpDiscovery } from '@spider-mesh/tcp'

@Module({
  providers: [
    {
      provide: SpiderMesh,
      useFactory: () => {
        const registry = new Registry()
        const mesh = new SpiderMesh(registry)

        mesh.registerTransporter(new UdpDiscovery())
        mesh.registerTransporter(new Http2Rpc())
        mesh.registerTransporter(new Http2Pubsub())

        return mesh
      },
    },
  ],
  exports: [SpiderMesh],
})
export class MeshModule {}

Expose a NestJS service as a microservice

import { Injectable, Module } from '@nestjs/common'
import { NestJSExposeMicroservice } from '@spider-mesh/core'

@Injectable()
export class BillingService {
  async charge(orderId: string) {
    return { orderId, status: 'ok' }
  }
}

@Module({
  providers: [
    BillingService,
    NestJSExposeMicroservice(BillingService, { boundedContext: 'billing' }),
  ],
})
export class BillingModule {}

Inject a remote service proxy in NestJS

import { Inject, Injectable, Module } from '@nestjs/common'
import { NestJSLinkMicroservice } from '@spider-mesh/core'

class BillingService {
  charge(orderId: string): Promise<{ orderId: string; status: string }> {
    throw new Error('typing only')
  }
}

@Injectable()
export class CheckoutService {
  constructor(
    @Inject(BillingService)
    private readonly billing: BillingService,
  ) {}

  async checkout(orderId: string) {
    return this.billing.charge(orderId)
  }
}

@Module({
  providers: [
    NestJSLinkMicroservice(BillingService),
    CheckoutService,
  ],
})
export class CheckoutModule {}

Pass a transporter only when you need to force a specific RPC transporter:

NestJSLinkMicroservice(BillingService, 'Http2Rpc')

Inject an event binding in NestJS

import { Inject, Injectable, Module } from '@nestjs/common'
import { NestJSLinkEvent } from '@spider-mesh/core'

class UserCreatedEvent {
  constructor(public readonly id: string) {}
}

@Injectable()
export class AuditService {
  constructor(
    @Inject(UserCreatedEvent)
    private readonly userCreated: {
      publish(data: UserCreatedEvent): Promise<void>
      listen(): any
    },
  ) {}
}

@Module({
  providers: [
    NestJSLinkEvent(UserCreatedEvent),
    AuditService,
  ],
})
export class AuditModule {}

Error Model

The core defines these error codes for RPC flows:

  • MICROSERVICE_OFFLINE
  • MICROSERVICE_NOT_FOUND
  • MICROSERVICE_RPC_TIMEOUT

Environment Variables

  • SPIDERMESH_NAMESPACE: namespace of the current node, default default
  • SPIDERMESH_NODE_HOSTNAME: optional hostname attached to node metadata

Helpers

The package also exports:

  • LimitConcurrency(limit) and LimitConcurrentRunning(limit) for throttling async method execution
  • MicroserviceError for common RPC error codes

Notes

  • This package is ESM-only.
  • Repository source uses emitted .js relative specifiers.
  • LOCAL_SERVICES$ is process-global inside one process.
  • Local services are registered when their class instances are constructed.
  • Service identity is based on the class name.
  • Event topic identity is based on the event class name.
  • RPC target selection is round-robin unless you force node_id.
  • SpiderMesh owns RPC stream lifecycle, timeout, retry, and cancel behavior. When a subscriber unsubscribes before a stream completes, SpiderMesh calls the cancel() function returned by transporter.send(), which causes the provider to stop the running Observable.
  • Transporters focus on byte transport, pubsub topic IO, and discovery broadcasts.
  • The root package entry intentionally focuses on runtime-agnostic APIs and shared contracts.
  • If an AI agent is uncertain which import to use, prefer @spider-mesh/core first, then opt into a companion transport package such as @spider-mesh/tcp or @spider-mesh/ws only when a concrete transport is needed.