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

scoundrel-remote-eval

v1.0.32

Published

JavaScript client for running remote evaluations with Scoundrel.

Readme

Scoundrel JavaScript

JavaScript client for running remote evaluations with Scoundrel.

Install

npm install scoundrel-remote-eval

For the bundled Python WebSocket runner in this repo, create the Python virtualenv in ../python/.venv and install the package there:

cd ../python
python3 -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"

PythonWebSocketRunner prefers ../python/.venv/bin/python when it exists and falls back to system python3 otherwise.

Usage

import Client from "scoundrel-remote-eval/src/client/index.js"
import ClientWebSocket from "scoundrel-remote-eval/src/client/connections/web-socket/index.js"
import PythonWebSocketRunner from "scoundrel-remote-eval/src/python-web-socket-runner.js"

const pythonWebSocketRunner = new PythonWebSocketRunner()
const ws = new WebSocket("ws://localhost:53874")
const clientWebSocket = new ClientWebSocket(ws)

await clientWebSocket.waitForOpened()

const client = new Client(clientWebSocket)

const math = await client.import("math")
const pi = await math.pi
const result = await (await math.cos(pi)).__serialize()

expect(result).toEqual(-1)

client.close()
pythonWebSocketRunner.close()

Client and proxy examples

Create remote objects, call methods, and fetch attributes:

const array = await client.newObject("Array")
await array.push("one")
await array.push("two")
const joined = await (await array.join(", ")).__serialize()
expect(joined).toEqual("one, two")

const length = await array.length
expect(length).toEqual(2)

When you need a primitive (for comparisons, JSON, or string interpolation), serialize the proxy result first:

const accountCount = await (await accountClass.count()).__serialize()
expect(accountCount).toEqual(1)

newObject, import, and getObject return proxies by default; use newObjectReference/newObjectResult, importReference/importResult, and getObjectReference/getObjectResult when you need a Reference or serialized result.

const arrayRef = await client.newObjectReference("Array")
const emptyArray = await client.newObjectResult("Array")

Read attributes directly or as proxies:

const math = await client.import("math")
const pi = await math.pi
const e = await math.E
expect([pi, e].every((value) => typeof value === "number")).toEqual(true)

Reference variant (when you need to serialize):

const math = await client.importReference("math")
const piRef = await math.readAttributeReference("pi")
const pi = await piRef.serialize()

const e = await math.readAttributeResult("E")
expect([pi, e].every((value) => typeof value === "number")).toEqual(true)

Fetch globally available or registered objects:

client.registerObject("config", {mode: "test"})

const configProxy = await client.getObject("config")
const config = await configProxy.__serialize()
expect(config).toEqual({mode: "test"})

client.unregisterObject("config")

Reference/result variants:

const configRef = await client.getObjectReference("config")
const config = await configRef.serialize()

const configResult = await client.getObjectResult("config")

Callback arguments

When you pass a function as an argument, the server can call it and any arguments passed to that callback are delivered as references. This makes it safe to access complex objects from the callback.

const targetRef = await client.getObjectReference("eventTarget")

await targetRef.callMethodResult("addEventListener", "onTestEvent", async (eventRef) => {
  const event = await eventRef.serialize()
  console.log("Event payload:", event)
})

Serialization

Reference#serialize() supports JSON-safe values plus Scoundrel JSON types. Dates and regex values are encoded as objects with a __scoundrel_type__ key and a value string, and you can register additional types. It throws an error if the value contains functions, symbols, class instances without a registered handler, circular references, non-finite numbers, or other unsupported types.

Stack trace sanitization

When a command fails, Scoundrel combines the server and client stacks into a single error. Some frames are filtered to keep the combined stack readable:

  • Frames from the WebSocket client library (node_modules/ws)
  • Node internals (node: URLs and internal/ frames)
  • The leading Error: line from nested stacks

Application frames, including paths that contain /internal/ within your project, are preserved.

Calling static methods on classes

You can ask for a proxy to a class (either globally available or registered with registerClass) and call its static methods:

class TestMath {
  static add(a, b) { return a + b }
}

// Make the class available for lookups (for example, on a server-controlled client)
client.registerClass("TestMath", TestMath)

// Later, fetch the class proxy and call its static method
const testMathProxy = await client.getObject("TestMath")
const sum = await (await testMathProxy.add(2, 3)).__serialize()

expect(sum).toEqual(5)

Manual proxy wrapping (optional)

The library returns proxies by default. If you need to wrap an existing Reference, you can use the helper directly:

import referenceProxy from "scoundrel-remote-eval/src/client/reference-proxy.js"

const arrayRef = await client.newObjectReference("Array")
const array = referenceProxy(arrayRef)

await array.push("one")
await array.push("two")
const firstValue = await array[0]
const length = await array.length

Chaining proxy calls

You can chain method calls on the same proxy and only await once (the last call's raw result is returned). The chain returns the final method result directly, so helpers like __serialize() are not available on the chain itself. Chaining is only ergonomic when intermediate calls return the original object (or another object that still supports the next method), because the chain replays calls on the same reference.

const result = await array
  .__chain()
  .push("one")
  .push("two")
  .toString()

expect(result).toEqual("one,two")

Explicit return helpers

Use the explicit helpers when you need a definite return type:

  • callMethod(...): proxy
  • callMethodReference(...): Reference
  • callMethodResult(...): raw result
  • readAttribute(...): proxy
  • readAttributeReference(...): Reference
  • readAttributeResult(...): raw result

Examples:

const array = await client.newObject("Array")
const lengthProxy = await array.push("three")
const length = await lengthProxy.__serialize()

const arrayRef = await client.newObjectReference("Array")
const lengthRef = await arrayRef.callMethodReference("push", "four")
const lengthValue = await lengthRef.serialize()

const rawLength = await arrayRef.callMethodResult("push", "five")

Server-to-client control

By default, a client refuses server-initiated commands. Enable it by passing enableServerControl: true when constructing the client:

const client = new Client(clientWebSocket, {enableServerControl: true})

If you want to explicitly disable server control (the default), pass enableServerControl: false or omit the option:

const client = new Client(clientWebSocket, {enableServerControl: false})
// equivalent to: new Client(clientWebSocket)

You can also enable it after construction:

client.enableServerControl()

Registered objects and classes are available inside eval:

const client = new Client(clientWebSocket, {enableServerControl: true})

class TestGreeter {
  constructor(prefix) {
    this.prefix = prefix
  }

  greet(name) {
    return `${this.prefix} ${name}`
  }
}

client.registerClass("TestGreeter", TestGreeter)
client.registerObject("testSettings", {prefix: "Hello"})

const serverClient = server.getClients()[0] // from your ScoundrelServer instance
const greetingProxy = await serverClient.eval("return new TestGreeter(testSettings.prefix).greet('World')")
const greeting = await greetingProxy.__serialize()

expect(greeting).toEqual("Hello World")

You can unregister classes or objects to remove them from server-side lookups and eval scope:

client.unregisterClass("TestGreeter")
client.unregisterObject("testSettings")

eval wraps your string in an async function, so use return to provide a value. It returns a proxy by default, but you can request the raw result or a reference:

const proxyResult = await serverClient.eval("return {value: 42}")
const value = await proxyResult.__serialize()

const result = await serverClient.evalResult("return 1 + 1")
expect(result).toEqual(2)

Use evalReference if you need a reference:

const greetingRef = await serverClient.evalReference("return 'Hello'")