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

@computesdk/givemeanode

v1.2.2

Published

givemeanode for ComputeSDK - sandboxes served from a per-region warm pool, with a signed credential that keeps the database off the create path

Readme

@computesdk/givemeanode

givemeanode provider for ComputeSDK. Very fast microVM sandboxes.

Installation & Setup

npm install @computesdk/givemeanode

Mint a token and put it in a .env file. Any org admin can mint one from /team or the CLI; it is shown once.

gman token create --name computesdk --workspace my-workspace
# prints GMN_TOKEN=gmnt_... once
GMN_TOKEN=gmnt_your_token
# Optional. Defaults to us-west-2. givemeanode runs an endpoint per region
# and they are not interchangeable for latency, so set this to the one
# nearest your workload.
GMN_API_HOST=https://api.use1.givemeanode.com

Usage

import { givemeanode } from '@computesdk/givemeanode'

const compute = givemeanode({ apiKey: process.env.GMN_TOKEN })

const sandbox = await compute.sandbox.create()
const result = await sandbox.runCommand('node -v')
console.log(result.stdout) // v24.19.0
await sandbox.destroy()

Configuration

| Option | Type | Default | What it does | |---|---|---|---| | apiKey | string | GMN_TOKEN | The gmnt_ org service token. | | baseUrl | string | GMN_API_HOST, else https://api.givemeanode.com | Which regional endpoint to use. | | fastToken | 'absorb' \| 'prime' \| 'off' | 'absorb' | See below. | | transport | 'auto' \| 'http2' \| 'fetch' | 'auto' | One HTTP/2 session for every request, or fetch. See "One connection for a burst". | | warm | 'connect' \| 'prime' \| 'off' | 'connect' | What construction does with the connection. See "One connection for a burst". | | connectTimeout | number | 10000 | How long opening the HTTP/2 session may take, in ms. | | ramGib | number | 2 | Guest memory. memoryMiB / memMiB on create are read too and rounded up to whole GiB; memory is decimal MB, per the shared options. | | egress | 'open' \| 'none' | account default | Whether the guest can reach the network. Fixed when the guest image is prepared, not per command. | | execRetries | number | 1 | See "Two behaviours worth knowing about". | | timeout | number | 120000 | Per-request timeout in ms. |

Asking for more than one core

A sandbox is one vCPU by default. create takes a vCPU count under any of ComputeSDK's spellings (vcpus, cpus, resources.vcpus), and it is mapped onto the smallest named instance type that satisfies both dimensions:

// 8 vCPU / 32 GiB (sandbox-lg)
const sandbox = await compute.sandbox.create({ vcpus: 8, memory: 16384 })

// ...or name the size outright, which wins over the hints
const xl = await compute.sandbox.create({ size: 'sandbox-xl' })

| Size | vCPU | RAM | Billed as | |---|---:|---:|---:| | sandbox-sm (default) | 1 | 2 GiB | 2 GiB | | sandbox-md | 4 | 8 GiB | 16 GiB | | sandbox-lg | 8 | 32 GiB | 32 GiB | | sandbox-xl | 16 | 64 GiB | 64 GiB |

Smallest-that-fits rather than largest is deliberate: asking for 8 vCPUs gets the 8-vCPU shape, never a 16-vCPU one. An ask past the top of the range clamps to sandbox-xl and lets the door refuse it with a message naming your own ceiling, rather than throwing something less useful locally.

Sizes above the default are opt-in per workspace. A new workspace's sandbox_vcpus ceiling is 4 and its sandbox_ram_gib ceiling is 8, so sandbox-lg and sandbox-xl are refused until an org admin raises both. The refusal names the limit and how to raise it, and this provider surfaces that message verbatim.

A named size and ramGib are mutually exclusive, because the size already fixes both dimensions. A memory-only ask stays on ramGib, so wanting 6 GiB on one core gets exactly that instead of being rounded up into a shape you would be billed more for.

Container images

Pass any container image by digest and givemeanode will run it:

const sandbox = await compute.sandbox.create({
  image: 'ghcr.io/acme/task@sha256:abc...',
})

Or prepare it once and start many sandboxes from it, which is much faster per sandbox:

const template = await compute.template.create({
  image: 'ghcr.io/acme/task@sha256:abc...',
})
const a = await compute.sandbox.create({ templateId: template.id })
const b = await compute.sandbox.create({ templateId: template.id })

Three things to know:

  • The reference must be digest-pinned. ghcr.io/acme/task:latest is refused; ghcr.io/acme/task@sha256:<64 hex> is accepted. A tag can be moved to point at different bytes, and the prepared image is cached under the image's identity, so a tag would eventually start a sandbox from content that no longer answers to that name. Read a digest with docker buildx imagetools inspect <ref> or crane digest <ref>.
  • Write the registry host in full. alpine@sha256:... is refused because it does not say which registry to authenticate to.
  • The first sandbox from a new image is slow, because the image has to be prepared before anything can start; every one after it is fast. Use template.create when you know the image up front. The provider prepares a given image only once per process and shares the result, so starting N sandboxes from one image does not prepare it N times.

givemeanode's own curated images are also available by name (sbx-base, the default, has python3, node 24, git, curl and a compiler; sbx-min and sbx-task are smaller). Anything containing @sha256: is treated as a container image; anything else is treated as a curated name.

The signed credential, and why you get it for free

Authenticating a request costs a round trip that presenting a signed credential does not. givemeanode hands one back on the response to any request made with your gmnt_ token:

gmn-fast-token:         gmns_<compact JWT>
gmn-fast-token-expires: 2026-08-30T19:48:19Z

This package absorbs that offer and presents it automatically. There is nothing to configure, nothing new to store, and no extra round trip: the offer rides on a response you were already getting, and your gmnt_ token stays the only secret you hold. Every failure falls back to the ordinary credential, so a credential that cannot be used is never worse than not having one.

Three modes, because the right one depends on your shape:

  • absorb (default) never adds a round trip. Your first request pays the ordinary cost, its response carries the credential, and everything after it is cheaper - including the command that follows that very first create.
  • prime pays one request up front, single-flighted across every caller sharing the token, so even the first creates of a burst present the credential. It is one GET /preview/sandboxes before the first create, and that listing crosses to the database: about 115 ms on the us-east door.
  • off never presents one.
const compute = givemeanode({
  apiKey: process.env.GMN_TOKEN,
  fastToken: 'prime', // pay the warm-up before the first burst
})

The default was absorb through 1.0.x, prime in 1.1.x, and is absorb again from 1.2.0. prime was measured on 2026-09-09 against a door that read its database to validate a gmnt_ token, so 100 cold creates queued 100 of those reads (about 600 ms of a 767 ms create). Later that day the door began answering a gmnt_ from an in-memory replica of its token table and the read stopped costing anything, while the prime kept costing its listing: measured from us-east-1 on 2026-09-18 at 100 concurrent creates over one HTTP/2 session, the median time-to-interactive read 56 to 106 ms with no prime against 166 ms waiting on one.

What it costs, stated plainly: a signed credential is valid for its own lifetime regardless of what happens to the token behind it, so gman token revoke stops anything new immediately, but a credential already in a client's hands keeps working until it expires. Bans behave the same way.

One connection for a burst

On Node this provider speaks HTTP/2 to the door: one session per provider, every request a stream on it, opened when the provider is constructed so the handshake is paid while you are still setting up. Measured from us-east-1 against the us-east door, 100 concurrent create-then-command pairs:

| wire | TTI median | p95 | p99 | |---|---|---|---| | fetch, one connection per in-flight request | 211 ms | 315 ms | 338 ms | | one HTTP/2 session | 40 ms | 44 ms | 45 ms |

The door answered in about 1 ms either way. The difference is the client: fetch opens one TLS connection per in-flight request, a single-threaded runtime performs those 100 handshakes one after another, and the median create waited about 170 ms for its turn before a byte reached the door.

fetch stays as the fallback wherever node:http2 is not available (browsers, edge runtimes) or a session cannot be opened, and it is what an injected fetch selects. Set transport: 'fetch' to never open a session, or transport: 'http2' to use one against a plaintext loopback dev server too (h2c).

Construction sends nothing on the session: your token first leaves the process with the first operation. Set warm: 'prime' to pay the signed credential's warm-up at construction, so even the first create of a burst presents it, or warm: 'off' to open nothing until the first request. An idle session does not keep a Node process alive, so a script that makes its requests and returns exits as it did over fetch. A request whose connection never completes fails at its own timeout, and a connection that takes longer than connectTimeout is given up on, with fetch answering from then on. That window is one credential lifetime and no longer.

Snapshots

A sandbox can be snapshotted, and a snapshot started from. This is the fastest way to get N copies of a prepared environment:

const snapshot = await compute.snapshot.create(sandbox.sandboxId)
const copy = await compute.sandbox.create({ snapshotId: snapshot.id })

Supported

| Feature | Supported | |---|---| | sandbox.create / getById / list / destroy | yes | | runCommand (cwd, env, background, timeout) | yes | | filesystem (read, write, mkdir, readdir, exists, remove) | yes, over runCommand | | template.create / list / destroy | yes, from a container image | | snapshot.create / list / delete | yes | | getUrl (a public HTTPS URL for a port) | yes - see below | | streaming stdout/stderr | not natively; the SDK's bridge applies |

Two behaviours worth knowing about

Writes go through base64, not a heredoc. A heredoc's body is every line up to its marker, so it can only ever produce a file ending in a newline: writing hello and reading it back gave hello\n. Base64 has no such rounding, needs no escaping (the alphabet is shell-inert), and carries content a heredoc cannot - no trailing newline, a line equal to the marker, arbitrary bytes. The cost is that the encoded content rides in one argv entry, so a single writeFile is bounded by the guest's ARG_MAX, typically around 1.5 MB of content. Every givemeanode image ships coreutils, so base64 is present.

A command is retried once, and only for one error. The connection to a sandbox is re-established on demand, so the first command after a quiet stretch can find it closed and needs a redial. Nothing else is retried - a command that ran and failed has an exit code and is returned as a result, not an exception. In the rare case where the sandbox did receive the request before the connection dropped, the retry means the command can run twice, so pass execRetries: 0 for a workload where that matters.

The preview URL

getUrl({ port }) returns a public HTTPS URL that reaches a server running inside the sandbox - the page a coding agent just wrote, looked at from outside:

const sandbox = await compute.sandbox.create({ templateId: template.id })
await sandbox.runCommand('sh', ['-c', 'cd app && (npm run dev > /tmp/dev.log 2>&1 &)'])
const url = await sandbox.getUrl({ port: 3000 })

Three things decide whether it works:

  • The server listens on the sandbox's loopback. 127.0.0.1:3000 is what dev servers bind by default and is exactly right; the host connects in over vsock and the guest dials its own loopback, so no packet arrives on the guest's network interface. That also means an exposed port works on a sandbox prepared with egress: 'none'.
  • The server survives the command that started it. runCommand waits for its command's output to end, so npm run dev & alone hangs the call and then dies with it. Redirect: (npm run dev > /tmp/dev.log 2>&1 &).
  • The URL is the secret. The hostname carries an unguessable capability, so treat it like a password; it expires (24h by default) and dies with the sandbox. unexposePort(sandbox, port) closes it sooner.

The call is idempotent per port - the same port returns the same URL with a refreshed expiry - so there is no reason to cache one.

Limitations

  • An exposed port is HTTP(S) only, and it is a preview URL rather than a CDN. getUrl({port}) mints a public HTTPS URL that reaches a port inside the sandbox; requests ride one proxy hop, which is right for a dev server or an API you are testing and not for serving production traffic. Streaming, SSE and WebSocket (protocol: 'wss', same URL) pass through. A raw TCP port cannot be exposed.
  • Container images must be digest-pinned and registry-qualified, and the first sandbox from a new one is slow. See "Container images".
  • Regional endpoints are not interchangeable. Set baseUrl to the one nearest your workload.