@computesdk/givemeanode

中文文档
1.2.16发布于 2026/11/07MITTypeScriptESM + CJS构建来源已验证近 30 天下载 8569

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

原始使用文档
文档目录12 节
  1. Installation & Setup
  2. Usage
  3. Configuration
  4. Asking for more than one core
  5. Container images
  6. The signed credential, and why you get it for free
  7. One connection for a burst
  8. Snapshots
  9. Supported
  10. Three behaviours worth knowing about
  11. The preview URL
  12. Limitations

@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.instances.create()
const result = await sandbox.runCommand('node -v')
console.log(result.stdout) // v24.19.0
await sandbox.destroy()

Configuration

OptionTypeDefaultWhat it does
apiKeystringGMN_TOKENThe gmnt_ org service token.
baseUrlstringGMN_API_HOST, else https://api.givemeanode.comWhich 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".
connectTimeoutnumber10000How long opening the HTTP/2 session may take, in ms.
ramGibnumber2Guest 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 defaultWhether the guest can reach the network. Fixed when the guest image is prepared, not per command.
execRetriesnumber1See "Two behaviours worth knowing about".
timeoutnumber120000Per-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.instances.create({ vcpus: 8, memory: 16384 })

// ...or name the size outright, which wins over the hints
const xl = await compute.instances.create({ size: 'sandbox-xl' })
SizevCPURAMBilled as
sandbox-sm (default)12 GiB2 GiB
sandbox-md48 GiB16 GiB
sandbox-lg832 GiB32 GiB
sandbox-xl1664 GiB64 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.instances.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.instances.create({ templateId: template.id })
const b = await compute.instances.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:

wireTTI medianp95p99
fetch, one connection per in-flight request211 ms315 ms338 ms
one HTTP/2 session40 ms44 ms45 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.instanceId)
const copy = await compute.instances.create({ snapshotId: snapshot.id })

The source keeps running. A fork resumes the exact captured state: the whole root filesystem (including /tmp), memory, and running processes (provider.snapshot.capabilities is { scope: 'rootfs', memory: true, destroysSource: false }). There is no explicit pause: an idle sandbox parks itself and wakes on its next command, so instance.pause() is not offered.

Supported

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

Three behaviours worth knowing about

Reads are bounded below the exec output cap. Files are read in 512 KiB byte chunks over base64, then decoded as UTF-8 once. This preserves multibyte characters, a leading BOM, and trailing newlines even across chunk boundaries. Truncated responses, failed reads, short chunks and size changes reject rather than return a prefix. Symlinks are followed and the guest's file permissions still apply. A file changing in place is not an atomic snapshot: stop writers before reading if you need one.

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 exec channel drops a command much over ~100 KB (the result comes back voided with exec_channel_lost), so content whose base64 exceeds 32 KiB is appended to a <path>.gmn-part file in slices and renamed over the target by the last one - a reader never sees a half-written file, and a large write costs one exec per slice. Every givemeanode image ships coreutils, so base64 is present. Encoding and command generation happen one slice at a time, not as a complete list of base64 commands held in memory.

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.instances.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.

版本与发布记录

@computesdk/givemeanode 共发布 22 个版本,首次发布于 2026/09/03;过去 12 个月发布了 22 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.2.16latest2026/11/073 天前
1.2.152026/11/064 天前
1.2.142026/11/046 天前
1.2.132026/10/2813 天前
1.2.122026/10/2318 天前
1.2.112026/10/2318 天前

关于 @computesdk/givemeanode 的常见问题

@computesdk/givemeanode 是什么?

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

如何安装 @computesdk/givemeanode?

在项目目录运行 npm install @computesdk/givemeanode,或使用 pnpm add / yarn add / bun add。

@computesdk/givemeanode 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@computesdk/givemeanode 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@computesdk/givemeanode 可以商用吗?

@computesdk/givemeanode 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@computesdk/givemeanode 的下载量有多少?

根据 npm 官方下载统计,@computesdk/givemeanode 在 2026/10/11 – 2026/11/09 这 30 天内被下载 8,569 次,日均约 286 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@computesdk/givemeanode 还在维护吗?

最新版本 1.2.16 发布于 2026/11/07(3 天前);至今共发布 22 个版本,其中过去 12 个月发布了 22 个;首次发布于 2026/09/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。