@absolutejs/isolated-jsc

中文文档
0.15.0发布于 2026/11/03BSL-1.1TypeScriptES Module构建来源已验证近 30 天下载 3052

JavaScript Core-native sandbox for Bun – 大量同位化执行不可信代码,孤立-vm-形状的 API

中文使用文档
文档目录17 节
  1. 为什么会这样
  2. 快速回答
  3. 今天的什么船(v0.12.2)
  4. Bun沙箱决策指南
  5. 它是什么ISN't(诚实限制每后端)
  6. FFI 后端安装注释
  7. API 密码
  8. 获取处理器
  9. 错误
  10. 安装
  11. 医生
  12. 实例
  13. 基准
  14. 测试
  15. 释放检查
  16. 相关
  17. 许可证

@absolutejs/同化-jsc

JavaScript Core-native sandbox for Bun. 中国植物物种信息数据库. 无信任代码的重同位化执行,带有 isolated-vm- 形状的API.

import { createIsolate, Reference } from "@absolutejs/isolated-jsc";

const isolate = await createIsolate({ memoryLimit: 64 });
const context = await isolate.createContext();

await context.setGlobal(
  "log",
  new Reference((msg) => console.log("[tenant]", msg)),
);

const script = await isolate.compileScript('await log("hello"); 1 + 1');
const result = await script.run(context, { timeout: 500 });
// result === 2

await isolate.dispose();

为什么会这样

Bun没有相当于Node的 isolated-vm. 节点库是 V8 的特异性——它与 V8 的链接 HasCustomHostObject ABI 符号——和 Bun 使用 Java 脚本公司,而不是 V8 键。 这么说吧 bun install isolated-vm 成功,则 import 失败 undefined symbol: HasCustomHostObject。 。 。 .

这使得整个类别的应用程序被困于节点:

  • AI特工代码执行. 安东尼 code_execution,OpenAI代码解释器,每个"运行这个 LLM 生成的片段"工具——它们都需要一个堆积同位化的运行时间,硬资源限制. ()烤箱-sh/bun#25929(中文(简体) )
  • 多租借书写. Cloudflare -Workers风格的"你的客户写了一些JS,运行范围到他们的账户"
  • 对不可信插件的构建时间评价. (单位:千美元)烤箱/箱# 23653(中文(简体) )
  • 沙箱一般许可. (单位:千美元)烤箱-sh/bun#6617(中文(简体) )

@absolutejs/isolated-jsc 填补了这一空白。 见 (原始内容存档于2018-10-21) (英语). BUN POLING. md 以邦为重点的战略框架, 二、结 论 该图书馆的上游问题 关闭,以及 (原始内容存档于2012-10-21). UPSTREAM ISSUES.md 对于上游 Bun bugs 此库 工作周围 (每件被固定时有清理指示). 见 缩写 FROM 标准化 VM.md 用于节点 isolated-vm 到 Bun 迁移路径 。 见 警卫 威胁模型 和更硬化的指导。 见 科学与研究 用于JavaScriptCore检查站研究. 见 变动情况 用于发行笔记.

快速回答

为什么不是节点 isolated-vm? 吗? 它对于Node/V8来说是正确的形状,但Bun是JavaScriptCore. isolated-jsc 将隔离形状的 API 移植到 Bun/JSC ,而不是试图加载一个 V8 加通 .

为什么不用邦工人? 工人是可移植底物和倒置后端. 此套件添加了沙盒产品层:硬化的全局,堆积限制,超时,度量,错误的忠诚度,TypeScript帮助器,池,以及明确的主机能力经纪人.

我什么时候需要FFI? 需求 backend: "ffi" 用于在 macOS 或 Linux 有 JavaScriptCore 的敌对代码生产路径。 使用 backend: "auto" 用于便携式默认、演示和CI。 当沙盒出逃会暴露有意义的主机秘密时,添加进程/容器/界面/网络边界.

今天的什么船(v0.12.2)

@absolutejs/isolated-jsc 运行在一个API后的两个可互换后端上:

  • FFI 后端( 当 libJSC 可达时默认) - 谈话 libJavaScriptCore 直接通过 bun:ffi. Cold heap ~ 300 KB, CPU 超时使用 JSC 中断驱动的监视器( 超时 Error 后隔离持续运行),以及 (0, eval)('X') 编号 : new Function('return X')() 完全通过 JSGlobalContextSetEvalEnabled。在 macOS (系统框架) + Linux 上可用 libjavascriptcoregtk-4.1-0 或者说 libjavascriptcoregtk-6.0-1 已安装.

  • 工人后端( 后退) - 一个邦 Worker 每个隔离。 其工人-当地JSC群起于1.3.x号机上大约1MB;工人全过程足迹较大. 超时终止整个隔离. 总是可用的( 不依赖系统) 。 在Windows和任何没有 libJSC 的 Linux 上默认 .

二者各分大众相. 明确选择 createIsolate({ backend: 'ffi' | 'worker' | 'auto' }),或让 'auto' (默认)LibJSC探测器并倒回工人. 两个后端:

  • 收缩隔离. 每个 Isolate 经营自己的Bun Worker – 自己的JSC VM – 自己的GC堆积. 不与主机或对等隔离共享内存 .
  • isolated-vm- 形状的API. Isolate, (中文(简体) ), Context, (中文(简体) ), Script, (中文(简体) ), Reference, (中文(简体) ), ExternalCopy方便港务.
  • 长城超时. script.run(context, { timeout: 500 }) 毫秒精度,通过主机执行 Worker.terminate().v1取舍:超时终止了整个隔离(如果需要再生的话在应用层集合).
  • 内存极限. 软盖通过工人——当地——每50 ms投票 bun:jsc.heapSize()不包括东道国和同行工作者的分配。 违反命令会致命且自我终止;主机拒绝等待执行的行动 MemoryLimitError。 。 。 .
  • 硬化沙盒默认(T2.1,0.1中为新). 东道主能力全球- fetch, (中文(简体) ), Bun, (中文(简体) ), process, (中文(简体) ), Worker, (中文(简体) ), WebSocket编辑器 postMessage 编号 : addEventListener, (中文(简体) ), navigator贮藏室 从沙盒取出 用户代码无法通过赤裸的检查到达它们, globalThis.X, (中文(简体) ), this.X,或直接 eval('X') (关于工人后端, 间接 eval 和动态 import() 仍然到达主机权限, 除非运行时间可以锁定隔离; 见 警卫 财务报告和审定财务报表 requireLockdown)),纯JS内置(Math,JSON,承诺,打入阵列的套房)和安全的Web原始人(URL,Text Encoder,Web Crypto, setTimeout, Cope)仍然可以接触. 通过 harden: false ,或通过 unsafelyExposeGlobals: ['fetch']。 。 。 .
  • 可调用 Reference编号. 向隔离区显示主机功能 。 工人后端: 通过 Async 消息传递( 使用) 拨打总是往返 await 在孤立的一侧。 FFI 后端 (0. 3+):同步主机 fns 直接通过 per Reference 返回其值 JSCallback. Async(承诺返回)主机fns在FFI(0.4+)上也工作——跑者交替向 Bun 事件回路发售,并排出 JSC 的微任务排队,直到承诺实现为止,以 Script.run是因为 timeout。 。 。 .
  • ExternalCopy 用于标出大型主机侧值,用于便宜的通过.
  • 可选的受限控制台抓取. onConsole 路由隔离 console.log 唤回主机. maxConsoleEntries 财务报告和审定财务报表 maxConsoleBytes 已发送日志,收据报告过多.
  • 一级隔离池(T2.2,0.1中新增). createIsolatePool({ isolate, maxSize, idleMs, recycleAfter }) 返回一个按键池——每个按键懒惰的产卵,跨呼号的再利用,LRU在上限下被驱出,隔离自灭后透明再生,可配置后N召回来捆绑JSC堆积物. 更换每张消费卷的按租户计价的明细图.
  • 上下文种子+数据检查点(T2.3,0.1为新;0.8.19为扩展;收件增加为0.8.21). createContext({ seed, snapshot }) 仍然恢复旧的平地快照,和 createContext({ seed, checkpoint }) 从 context.checkpoint({ maxBytes, include, exclude }). 检查站携带 schemaVersion,后端,字节长度,包含/已删除的计数,以及跳过的关键原因. context.checkpointWithReceipt() 财务报告和审定财务报表 isolate.createContextWithReceipt() 生产 schema-v1 CheckpointReceipt编号 - operation: "create" | "restore", (中文(简体) ), executionId, (中文(简体) ), durationMs合计 skippedReasons选择 purpose编号 :tenant编号 :policy 审计工作流程的标签。 这是一个数据检查点, 不是 JSC 堆积暂停/ 重现图像 ; 见 科学与研究。 。 。 .
  • 忠诚错误( T2.4, 0.1 有新内容) . 向隔开的回合中抛出错误 error.cause (倒行逆施)和可自有地产完整. 自定义错误子类实例数据( E)HttpError 与 .statusCode等)活下来. instanceof 无法跨越边界; 使用 .name 编号 : .code 检查.
  • 人均运行遥测(T2.4,0.1中为新). script.runWithMetrics(ctx, opts) 返回时 { result, metrics: { backend, cpuMs, heapBytes } } 用于计费/仪表板/每个呼叫监测。 平面 run() 仍然返回赤色值.
  • 处决收据. script.runWithReceipt(), (中文(简体) ), callable.callWithReceipt(), (中文(简体) ), runIsolated(..., { withReceipt: true })中,运行者接收模式将本地审计记录与 schemaVersion: 1,执行 id,后端,政策,资源设置,时间,输出大小,限值能力调用摘要,以及空出事件计数.
  • 能力审计编辑. 经纪人支持默认 redactAuditInput 编号 : redactAuditOutput 钩子,每个工具都可以在审计事件击中日志或收据之前覆盖它们.
  • 能力审计缓冲器. createCapabilityAuditBuffer({ maxEvents }) 给应用程序一个接收准备 onAudit 下沉中带有明显的切换元数据,避免当沙盒代码垃圾邮件主机工具时进行未绑定的内置审计阵列.
  • 能力输出限制. 设定 maxOutputBytes 每个能力或 defaultMaxOutputBytes 一个经纪人拒绝超大的主机工具输出 CapabilityError 在他们回到沙盒代码之前.
  • 结果大小限制. 过 maxResultBytes 在运行的选项中拒绝超大的成功产出 ResultSizeError 在应用代码接受之前.
  • 控制台输出限制 . 过 maxConsoleEntries 编号 : maxConsoleBytes 当创建隔离点时,可以将捕获到的超量控制台事件和表面溢出旗子降入收款.
  • 后端可观察. isolate.backend 报告已解决的后端( e)"ffi" 或者说 "worker", (中文(简体) ), runWithMetrics() 编号 : callWithMetrics() 包含 metrics.backend,以及 isolated-jsc doctor --json 发布机器可读后端探测器细节.
  • 政策方略. createIsolate({ policy: "ai-tool" | "tenant-script" | "plugin" | "trusted" }) 应用标准化隔离/运行默认值. resolveIsolatePolicy(name, overrides?) 返回相同的策略对象加一 recipe 带有建议的结果限制、控制台限制、审计缓冲盖、经纪人盖和跑道池设置。 帮助构建者, 如 policyAuditOptions(policy), (中文(简体) ), policyBrokerOptions(policy), (中文(简体) ), policyConsoleOptions(policy), (中文(简体) ), policyRunOptions(policy),以及 policyRunnerOptions(policy) 将这些建议变成周边API的防复制选项.
  • 一发一枪处死. runIsolated(source, { policy, globals, context, run, withMetrics }) 覆盖不需要直接管理隔离生命周期的请求/响应路径.
  • 可重复使用的跑道. createIsolatedRunner({ policy, globals, pool }) 用钥匙隔离热租户/会议/谈判路径的再利用。 使用 runner.run() 用于新文本源执行, runner.precompile() 以温和的饮料盟誓, runner.call() 每次按密钥/名称编译一次并反复拨打 runner.stats() 检查池子/笼子大小.

Bun沙箱决策指南

使用大小写选择为什么
本地开发、演示、CI烟雾测试或没有libJSC的平台backend: "auto"可用时使用FFI,不可用时使用Worker,因此,同一代码到处运行.
在 macOS 或 Linux 在其中安装 JavaScript Core 的不可信代码backend: "ffi"最低的冷堆积,中断驱动的CPU超时,隔离活生生的超时,并关闭了eval / 函数构件残存.
在没有系统JavaScriptCore的 Windows 或主机上生产不可信代码工人落后于进程/容器边界工人保持堆积隔离和资源封顶,但敌对的工作量不应共享主机机密或广泛的网络/文件权限.
需要明确主机权限的用户插件FFI 加上 Reference /能力经纪人保留"沙盒"全球小并只曝光经验证,经审计的主机工具.
高价值机密、任意第三方代码或网络相邻工作量FFI + 工艺、容器、设备、分包和网络入侵政策JavaScript隔离是一层;OS隔离仍然拥有爆炸-辐射控制.
仅需要取消或租户级集合的可信应用代码工人或FFI人才库安全态势较少重要;优化部署范围和业务简单.

拇指规则:使用 backend: "ffi" 用于敌对码生产路径, backend: "auto" 对于便携式默认值,当沙盒出逃会暴露有意义的主机机密时,则加入OS/处理隔离.

它是什么ISN't(诚实限制每后端)

路径FFI 后端工人后端
fetch(url) (空地)未定义未定义
Bun.spawn(...) (空地)未定义未定义
globalThis.fetch 编号 : globalThis.Bun未定义未定义
this.Bun未定义未定义
eval('Bun') (直接)块 (eval 已禁用文本)未定义
(0, eval)('Bun') (间接)块已锁定: 已丢失; 库存 Bun: 可达到
new Function('return Bun')()块已锁定: 已丢失; 库存 Bun: 可达到
import('node:child_process') 编号 : import('bun:ffi')无模块加载器已锁定: 拒绝; 库存 Bun : 可达到,即使没有 eval (见 警卫(中文(简体) )
Math.PI 编号 : JSON.stringify 编号 : Promise 编号 : crypto.randomUUID可达到可达到
URL 编号 : TextEncoder 编号 : WebSocket未见 (只有JSC API,没有Web API)可达( Bun Worker 曝光 Web API)
冷JSC堆积~300 KB 键~1 MB 键
超时行为(a) 将例外情形列入文字; 隔离持续运行隔离死亡,必须重生
内存盖监督者 heapCapacity; 过期终止调查50 ms;终止整个隔离
通过引用同步主机 fns支持 (直接返回)总是包裹在 await
阿辛克语( A)Promise(通过引用)主机 fns支持 (0.4+)——被泵入安置地,以 timeout支持通过 await
不同背景的原型隔离每个租户一个隔离每个租户一个隔离

FFI 后端安装注释

  • 马科斯:零安装. JavaScript Core 是一个系统框架,位于 /System/Library/Frameworks/JavaScriptCore.framework。 。 。 .
  • 链接 (德比安/乌本图: sudo apt install libjavascriptcoregtk-4.1-0 (8 MB / 31 MB 已安装).
  • 链接 (费多拉): sudo dnf install webkit2gtk4.1。 。 。 .
  • 已安装 Playwright 的 Linux我们接受Playwright的捆绑 libjavascriptcoregtk-6.0.so.1 - 没有额外安装.
  • 窗口:不支持(没有系统 JSC,没有现实的分发路径). createIsolate({ backend: 'auto' }) 自动返回工人; backend: 'ffi' 掷出 JscLibraryNotFoundError。 。 。 .

API 密码

import {
  createCapabilityAuditBuffer,
  createCapabilityBroker,
  defineCapabilityTool,
  compileTypeScriptCallable,
  compileTypeScriptCallableFile,
  compileTypeScriptFile,
  createIsolate,
  createIsolatedRunner,
  policyAuditOptions,
  policyBrokerOptions,
  policyConsoleOptions,
  policyRunnerOptions,
  resolveIsolatePolicy,
  runIsolated,
  runIsolatedFile,
  Reference,
  ExternalCopy,
  validateContextCheckpoint,
  type Isolate,
  type Context,
  type Script,
} from "@absolutejs/isolated-jsc";

// Create an isolate (one per untrusted tenant).
const isolate: Isolate = await createIsolate({
  memoryLimit: 256, // MB; default 256
  bootstrap: "var foo = 1", // optional — runs once in the worker
  maxConsoleEntries: 100,
  maxConsoleBytes: 64_000,
  onConsole: (level, args) => console.log(`[iso/${level}]`, ...args),
});

// One or more contexts (fresh global scopes) per isolate.
const context: Context = await isolate.createContext();

// Expose a host function callable from inside the isolate.
const dbQuery = new Reference(async (sql: string) => {
  return await myDb.query(sql); // host-side call, tenant-scoped
});
await context.setGlobal("db", dbQuery);

// Compile + run.
const script: Script = await isolate.compileScript(`
    (async () => {
        const rows = await db('SELECT * FROM users');
        return rows.length;
    })()
`);

const count = await script.run(context, { timeout: 500 });
// count === number of rows

// TypeScript source: transpiled with Bun before execution in the isolate.
const handler = await compileTypeScriptCallable(
  context,
  "async (name: string): Promise<string> => name.trim().toUpperCase()",
);
await handler.call([" alex "]); // "ALEX"

// Real files work too. Keep generics, types, and complex tenant/plugin code in
// normal `.ts`, `.tsx`, `.js`, or `.jsx` files instead of string literals.
const typedScript = await compileTypeScriptFile(
  isolate,
  "./tenant-code/score.ts",
);
await typedScript.run(context);

// Callable files use a default export:
//
//   type Box<T> = { value: T };
//   export default function unwrap<T>(box: Box<T>): T {
//     return box.value;
//   }
//
const unwrap = await compileTypeScriptCallableFile(
  context,
  "./tenant-code/unwrap.ts",
);
await unwrap.call([{ value: "typed file" }]); // "typed file"

// Explicit data checkpoints for turn/session handoff. These capture
// structured-cloneable own globals only, not a JSC heap image.
const checkpoint = await context.checkpoint({
  exclude: ["scratch"],
  maxBytes: 64 * 1024,
});
const persisted = JSON.stringify(checkpoint);
const restoredCheckpoint = JSON.parse(persisted);
validateContextCheckpoint(restoredCheckpoint);
const resumed = await isolate.createContext({
  checkpoint: restoredCheckpoint,
  seed: "this.double = (n) => n * 2",
});
// checkpoint.schemaVersion === 1
// checkpoint.skipped === [{ key, reason, bytes? }, ...]

// Versioned checkpoint receipts for operational evidence. Mirrors the
// ExecutionReceipt shape: schemaVersion:1, durationMs, startedAt/endedAt,
// executionId, plus operation:"create"|"restore" and aggregated
// skippedReasons counts. Pass purpose/tenant for audit-log workflows;
// errors rethrow with `.receipt` attached.
const audited = await context.checkpointWithReceipt({
  exclude: ["scratch"],
  maxBytes: 64 * 1024,
  purpose: "turn-handoff",
  tenant: "tenant-a",
});
// audited.receipt.operation === "create"
// audited.receipt.skippedReasons === { excluded, notClonable, overMaxBytes }
const restored = await isolate.createContextWithReceipt({
  checkpoint: audited.checkpoint,
  purpose: "turn-handoff",
  tenant: "tenant-a",
});
// restored.receipt.operation === "restore"
// restored.receipt.sourceBackend === "ffi" | "worker"

type TenantContext = { id: string };
type LookupOrderInput = { id: string };
type Order = {
  cardLast4: string;
  customerEmail: string;
  id: string;
  status: string;
};
const policy = resolveIsolatePolicy("ai-tool", {
  maxResultBytes: 32 * 1024,
  timeout: 750,
});
const audit = createCapabilityAuditBuffer<TenantContext>({
  ...policyAuditOptions(policy),
});
const redactEmail = (email: string) => {
  const [name, domain] = email.split("@");
  return domain === undefined
    ? "[email redacted]"
    : `${name.slice(0, 2)}***@${domain}`;
};

const broker = createCapabilityBroker(
  {
    lookupOrder: defineCapabilityTool<
      LookupOrderInput,
      Order | null,
      TenantContext
    >({
      description: "Read one order by id for the current tenant",
      risk: "read-only",
      input: { name: "LookupOrderInput" },
      output: "Order | null",
      maxOutputBytes: 16_384,
      redactAuditInput: (input) => ({ id: (input as { id?: unknown }).id }),
      redactAuditOutput: (output) => {
        if (output === null) return null;
        const order = output as Order;
        return {
          cardLast4: order.cardLast4,
          customerEmail: redactEmail(order.customerEmail),
          id: order.id,
          status: order.status,
        };
      },
      timeoutMs: 250,
      validateInput: (input) => {
        if (input === null || typeof input !== "object") {
          throw new Error("lookupOrder input must be an object");
        }
        const id = (input as { id?: unknown }).id;
        if (typeof id !== "string") {
          throw new Error("lookupOrder input requires a string id");
        }
        return { id };
      },
      handler: async ({ id }, tenant) => await lookupOrder(tenant.id, id),
    }),
    chargeCard: defineCapabilityTool<
      { cardToken: string; orderId: string },
      { authorizationId: string; processorTraceId: string },
      TenantContext
    >({
      description: "Charge an opaque card token for one tenant order",
      input: "ChargeInput",
      output: "ChargeResult",
      risk: "write",
      redactAuditInput: (input) => ({
        cardToken: "[token redacted]",
        orderId: (input as { orderId?: unknown }).orderId,
      }),
      redactAuditOutput: (output) => ({
        authorizationId: (output as { authorizationId?: unknown })
          .authorizationId,
        processorTraceId: "[trace redacted]",
      }),
      validateInput: (input) => input as { cardToken: string; orderId: string },
      handler: async (input, tenant) => await chargeCard(tenant.id, input),
    }),
  },
  {
    context: { id: "tenant_123" },
    ...policyBrokerOptions(policy),
    onAudit: audit.onAudit,
    redactAuditInput: () => "[input redacted by default]",
    redactAuditOutput: () => "[output redacted by default]",
  },
);

// Host-side direct calls are typed from the tool map.
const order = await broker.call("lookupOrder", { id: "ord_123" });
//    ^? Order | null

// Reviewable capability manifest for docs, audits, and agent/tool UIs.
broker.manifest();
// [{
//   name: "lookupOrder",
//   schemaVersion: 1,
//   description: "Read one order by id for the current tenant",
//   risk: "read-only",
//   input: { name: "LookupOrderInput" },
//   output: "Order | null",
//   maxOutputBytes: 16384,
//   timeoutMs: 250,
//   hasInputValidator: true,
//   hasOutputValidator: false,
//   redactsInput: true,
//   redactsOutput: true
// }]

// Audit events and receipts retain the operational proof, not raw secrets:
//   order ids stay visible
//   emails become ad***@example.com
//   card tokens become [token redacted]
//   processor trace ids become [trace redacted]

// Sandbox calls still use an untrusted-code-safe Reference.
await context.setGlobal("tools", broker.reference);

// Policy recipes standardize isolate construction, default timeout, default
// result-size limits, and recommended audit/broker/runner settings.
const policyIsolate = await createIsolate({
  policy,
  ...policyConsoleOptions(policy),
});
const policyContext = await policyIsolate.createContext();
const policyScript = await policyIsolate.compileScript("1 + 1");
await policyScript.run(policyContext);
await policyIsolate.dispose();

// One-shot execution for request/response paths.
const oneShot = await runIsolated<number>("input.n * 2", {
  policy,
  globals: { input: { n: 21 } },
});
// oneShot === 42

const oneShotFile = await runIsolatedFile<number>("./tenant-code/score.ts", {
  policy,
  globals: { input: { n: 21 } },
});
// oneShotFile === 42

const measured = await runIsolated<number>("input.n + 1", {
  policy: "tenant-script",
  globals: { input: { n: 41 } },
  withMetrics: true,
});
// measured.result === 42; measured.metrics.backend === "ffi" | "worker"

const receipted = await runIsolated<number>("input.n + 1", {
  policy: "tenant-script",
  globals: { input: { n: 41 } },
  run: {
    ...audit.receiptOptions(),
    executionId: "exec_123",
    purpose: "ai-tool-call",
    tenant: "tenant_123",
  },
  withReceipt: true,
});
// receipted.result === 42
// receipted.receipt.status === "success"
// receipted.receipt.backend === "ffi" | "worker"

// Reusable runner for repeated request/response paths.
const runner = createIsolatedRunner({
  ...policyRunnerOptions(policy),
  policy,
  globals: { tools: broker.reference },
});

const tenantResult = await runner.run<number>("input.n * 2", {
  key: "tenant_123",
  globals: { input: { n: 21 } },
});
// tenantResult === 42

const tenantFileResult = await runner.runFile<number>(
  "./tenant-code/score.ts",
  {
    key: "tenant_123",
    globals: { input: { n: 21 } },
  },
);
// tenantFileResult === 42

await runner.precompile(
  "scoreFormula",
  "(input) => input.base * 2 + input.bonus",
  { key: "tenant_123" },
);

const callableResult = await runner.call<number>(
  "scoreFormula",
  "(input) => input.base * 2 + input.bonus",
  [{ base: 20, bonus: 2 }],
  { key: "tenant_123" },
);
// callableResult === 42

await runner.precompileFile("unwrap", "./tenant-code/unwrap.ts", {
  key: "tenant_123",
});

const callableFileResult = await runner.callFile<string>(
  "unwrap",
  "./tenant-code/unwrap.ts",
  [{ value: "typed file" }],
  { key: "tenant_123" },
);
// callableFileResult === "typed file"
// runner.stats() === { poolSize: 1, callableCacheSize: 1, callablesByKey: { tenant_123: 1 } }

await runner.dispose();
await isolate.dispose();

获取处理器

运行一个 Web 标准应用( E)export default { fetch(request, env, ctx) },则 工人们, Bun.serve 以主机称呼它 Requests. 仅限工人后端.

import {
  bundleFetchHandler,
  createFetchHandler,
  createIsolate,
  Reference,
} from "@absolutejs/isolated-jsc";

const source = await bundleFetchHandler("./app.ts"); // one CommonJS bundle, Web APIs only
const isolate = await createIsolate({
  backend: "worker",
  requireLockdown: true,
});
const handler = await createFetchHandler(
  await isolate.createContext(),
  source,
  {
    env: { region: "nyc1", lookup: new Reference(async (id) => db.user(id)) },
  },
);

const response = await handler.fetch(
  new Request("https://app.example/users/7"),
);
const { metrics } = await handler.fetchWithMetrics(request); // cpuMs is thread CPU time
  • env 保存数据 Reference 能力(每个合成函数) 呼唤回住持.
  • ctx.state (一) 在请求之间保持上下文; context.checkpoint({ include: ["state"] }) 捕取来冬眠.
  • ctx.waitUntil(promise) 让我们在回复后完成工作.
  • 车体以1米B为缓冲并封顶(maxRequestBytes, (中文(简体) ), maxResponseBytes(三); FetchBodyTooLargeError 说哪边溢出.
  • bundleFetchHandler 拒绝孤立者不能提供的东西node: 财务报告和审定财务报表 bun: 进口、无已安装软件包的节点内置、原生加通) ,并将每个此类导入列表于 a FetchHandlerBundleError. Bundling 罐头 运行应用程序的宏, 所以运行它在哪里 你会运行应用程序自己的构建.
  • Elysia 2 的应用程序没有改变: export default { fetch: (request) => app.handle(request) }。 。 。 .

错误

  • FetchBodyTooLargeError ——采集处理者的请求或响应机构超过其极限.
  • FetchHandlerBundleError ——一个应用导入了Bun或Node APIs或本地的加通.
  • TimeoutError ——一站一站预算用完.
  • MemoryLimitError ——堆积超过所配置的盖.
  • ResultSizeError 成功产出超过 maxResultBytes。 。 。 .
  • IsolateDisposedError 处理隔离物.
  • CompileError ——脚本出处的语法错误.

安装

bun add @absolutejs/isolated-jsc

要求装有 1.3.

医生

bunx @absolutejs/isolated-jsc
bunx @absolutejs/isolated-jsc --json
# or, from this repo:
bun src/doctor.ts

博士会打印 Bun/平面细节, FFI 后端可用性, JavaScriptCore f味/ path 当找到时, 检查缺少的库路径, 以及当前平台的安装提示 。 过 --json 用于机器可读CI或部署检查.

实例

bun run example:agent-tool
bun run example:checkpoint

代理工具的例子结合了TypeScript可调用编译,一种能力经纪人,租户背景,工具超时,审计事件,以及按调用的标准. 检查站的例子显示,JSON检查站持续存在,恢复验证和种子恢复.

基准

bun run bench:proof

这个写着 BENCHMARKS.md 与 FFI 后端、 工人后端、 Bun 流程喷出基线和节点的本地测量数据相匹配 isolated-vm 当选择本地软件包安装在节点环境中时.

测试

bun install
bun test

测试套件包括编译/运行、环境、, Reference 通过电话, ExternalCopy,超时,内存封顶,处置一能,池行为,TypeScript帮助者,能力经纪人,FFI行为,主机可达性文件,以及敌对租借人压力案件.

释放检查

bun run check:release

发布检查会运行套件类型检查、构建、单元测试、代理工具示例以及兄弟会中的浏览器烟雾 ../examples/isolated-jsc 应用 absolutejs/examples 剪切发布时, repo 检查了此套件的旁边 repo, 因此在发布前会覆盖演示路径 .

  • @absolutejs/sync - 反应同步引擎。 最终会利用这个库在主机"PaaS"中用于沙盒每租户变异处理器.
  • Bun 文档 : bun:jsc ——JSC原始人 这个图书馆建立在.
  • isolated-vm ——V8/Node等同于这个库镜(我们欠安德鲁·拉弗德特的API形状).

许可证

商业来源许可证1.1——生产使用是免费的,但作为相互竞争的主机沙盒/同位素服务提供除外(见2000年12月31日的附加用途赠款) 自由主义. 2030年5月29日转换为Apache 2.0. 现有商业许可证——联系人 [email protected]。 。 。 .

版本与发布记录

@absolutejs/isolated-jsc 共发布 54 个版本,首次发布于 2026/05/27;过去 12 个月发布了 54 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.15.0latest2026/11/034 天前
0.14.22026/11/034 天前
0.14.12026/11/034 天前
0.14.02026/11/034 天前
0.13.12026/11/034 天前
0.13.02026/11/034 天前

关于 @absolutejs/isolated-jsc 的常见问题

@absolutejs/isolated-jsc 是什么?

JavaScript Core-native sandbox for Bun – 大量同位化执行不可信代码,孤立-vm-形状的 API

如何安装 @absolutejs/isolated-jsc?

在项目目录运行 npm install @absolutejs/isolated-jsc,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 isolated-jsc、isolated-jsc-doctor,可以用 npx isolated-jsc 直接运行。

@absolutejs/isolated-jsc 支持 TypeScript 吗?

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

@absolutejs/isolated-jsc 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@absolutejs/isolated-jsc 可以商用吗?

@absolutejs/isolated-jsc 使用 BSL-1.1 许可。声明的许可为 BSL-1.1,请阅读许可原文确认使用条件。以上为概括,不构成法律意见,请以许可原文为准。

@absolutejs/isolated-jsc 的下载量有多少?

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

@absolutejs/isolated-jsc 还在维护吗?

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