弹匣
在 Cloudflare 持久对象中运行一个被屏蔽的被解释器
墨盒在一个 Cloudflare 持久对象内运行一个被屏蔽的被子解释器 。 过
createCartridge() 一个主题模块和呼叫 run();再入门;中断口罩
文件系统为您设置了线程 。 每件物品都会被出口给需要的呼叫者.
工人运行时间是同步的,而原声翻译不是同步的。 两项重叠的援引 共享一个可变的机器—— globals, 打开文件描述符, 会话状态 —— 和 interleave into 似乎错误的输出而不是失败.
这里没有关于 PHP 的具体信息 。 实际建筑贯穿 tests/interpreters/ 每推一推:
第8.3页 (日语), 卢亚 5.4 (是月亮), 氯乙烯 3 (P), 快速JS
(速记), 克鲁比3.4 (Ruby.wasm, over WASI 而不是标注)和 爪哇语
(字节框,提前编译而未解读). 见
推进 用户.md 每个语言状态和每种语言的食谱.
目录
为什么是卡特里奇
一个被翻译者是 单可变机。环球,自动装入器缓存,容器 指针,打开文件描述符和会话状态都是全过程的. 两起重叠的援引 不要大声地失败。 它们相互交织,产生可信的错误输出.
Cloudflare的运行时间是相反的形状. 它是事件驱动的, 它将幸福地提供第二 请求时,第一个已停放。 持久物体缩小范围,但不要关闭.
所以,主机必须提供 四个东西 口译员不能为自己提供: 串行化,一种方式 在主机呼叫中挡住一个中断, 这个文件系统在启动时不会花一大笔钱, 和一条不会在它修理的东西里面运行的修理道.
每一个都默不作声。 无假名的回叫表象 SuspendError 从哪里
opcode计数器在数月后着陆 负载不足.
QQ 安装
bun add @drupflare/cartridge
两个运行时间依赖关系, fflate 财务报告和审定财务报表 fzstd两个同步减压器 一个懒惰的山
在同步内充气 open() 预压缩的翻译器充气
范围; 两点都不能等待 DecompressionStream,它也仅限于gzip和放出.
QQ 快速启动
import { createCartridge } from '@drupflare/cartridge';
const cartridge = createCartridge({
// whatever your build's factory is; forward the io it gives you
instantiate: (io) => initInterpreter({ print: io.print, printErr: io.printErr }),
// files every run should see, written once at boot
files: { 'lib/helper.txt': 'shared by every run' }
});
const result = await cartridge.run('print("hello")');
console.log(result.stdoutText); // "hello\n"
console.log(result.status); // 0
这是默认路径。 每个条目都是连续的,每个 print 装入口罩,脚本是
在文件系统之前 callMain 输入,并用名称报告被泄露的口罩深度
毒死下一个跑道.
你的建筑必须满足 是两个成员:
interface Interpreter {
FS: MountFS; // emscripten's FS: mkdir, writeFile, and optionally utime
// may answer a promise, for an adapter with work to finish after main returns
callMain(argv: string[]): number | void | Promise<number | void>;
}
这是标语为 main()- 程序与 -sINVOKE_RUN=0 财务报告和审定财务报表
-sEXPORTED_RUNTIME_METHODS=callMain,FS。 。 。 . 大多数出版的建筑不这样做曾经是月亮 活地,
快速标记、红宝石、花字节和字节都检查过了,6个出口中没有一个
callMain,因此将其写作"双行适配器"是正常的例而非倒置. createMemoryFS()
当一个建筑也没有文件系统时,则提供另一半,以及 FS 是三种方法签名
而不是标注对象, 所以一个WASI预开也满足它。 见食谱在
推进 用户.md,每个标签 实际运行.
这里是什么
| 模块 | 它会做什么 |
|---|---|
lazy-fs.ts | MEMFS 从每个文件包的分层上挂上第一个打开的文件 |
supervisor.ts | 断路器 修理梯子 |
cartridge.ts | createCartridge():高级默认值,以及 RunResult |
mount.ts | 急切地奔流而行,驾车而行, mountRecord(), (中文(简体) ), createMemoryFS() |
tail-worker.ts | {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}把一个尾巴工人的事件 归纳成一个人类读到的东西 {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}我从小到大 |
mask.ts | 重算中断口罩,加上 VM 切片计数器 |
inflate.ts | 同步 zstd: 内容大小, inflateZstd,一个被支持的解码器 |
util.ts | fromUtf8编号 :toUtf8, (中文(简体) ), encodeJson编号 :decodeJson, (中文(简体) ), toBytes, (中文(简体) ), splitLines |
serialize.ts | Gate: 1名口译员, 一次引用 |
errors.ts | 五个名称出错词汇表,每个都有一个稳定的点 code |
worker-shim.ts | 使标注 ENVIRONMENT=worker 在工人身上建造即时电源d |
子路径导出
一个包裹, sideEffects 诚实地宣布, 和一个子路径 每一个顾虑
你没有使用.
| 导入 | 里面有什么东西 |
|---|---|
@drupflare/cartridge | 下方除了烟雾 |
@drupflare/cartridge/cartridge | createCartridge(), (中文(简体) ), RunResult, (中文(简体) ), Interpreter |
@drupflare/cartridge/gate | Gate, (中文(简体) ), doGate, (中文(简体) ), GateStats |
@drupflare/cartridge/mask | createMask单子, vmFromBinary切片计数器 |
@drupflare/cartridge/fs | 两座山, mountRecord, (中文(简体) ), createMemoryFS,两个包索引形状 |
@drupflare/cartridge/inflate | 同步zstd,用于一个在后靴上充气的预压缩模块 |
@drupflare/cartridge/supervisor | 铁丝网, CircuitBreaker,分类账 DDL |
@drupflare/cartridge/tail | 佣人和纯洁的仆人 |
@drupflare/cartridge/util | 字节、字符串和 JSON 便利 |
@drupflare/cartridge/errors | 错误词汇表 |
@drupflare/cartridge/shim | 副作用; 在粘结值评估前导入以生效 |
[尖叫声]
sideEffects是数组而不是false。 。 。 .worker-shim.ts补丁globalThis在导入时间; 声明包无副作用将允许捆绑树摇 删掉Asyncify断接并恢复 无法捕获 请求杀死。 声明true将禁用摇动整个软件包。 该阵列是唯一诚实的答案,和tests/exports.spec.ts钉住它.
Ergonomics (Ergonomics) (英语)
永远不要建立 TextEncoder 用于使用此软件包. 每个有效载荷 string | Uint8Array· ;
将字节交回的任何东西 也都交回解码文本.
import { createCartridge, fromUtf8, toUtf8 } from '@drupflare/cartridge';
const cartridge = createCartridge({ instantiate });
// a string or bytes, both fine
await cartridge.run('print(1)');
await cartridge.run(fromUtf8('print(1)'));
// structured input, no encoder at the call site
await cartridge.writeJson('input.json', { items: [1, 2, 3] });
const result = await cartridge.run('read input.json and print a total');
result.stdout; // Uint8Array, the primitive
result.stdoutText; // string, what you wanted
result.json<{ total: number }>(); // parsed
result.lines(); // ['{"total":6}']
result.lastLine(); // the last line, no trailing empty element
result.assertOk(); // throws InterpreterError naming the status and stderr
生道犹在
// the module itself, instantiated if needed, handed over inside the gate
const raw = await cartridge.interpreter();
// or run arbitrary work against it, still serialised against every other entry
await cartridge.withInterpreter((module) => module.FS.writeFile('/tmp/x', 'by hand'));
命名是固定的,所以可以学习一次: to*编号 :from* 用于编解码器(toUtf8, (中文(简体) ), fromUtf8, (中文(简体) ), *Json 页:1
序列/区间(writeJson, (中文(简体) ), encodeJson, (中文(简体) ), decodeJson, (中文(简体) ), *Text 用于解码字段(stdoutText, (中文(简体) ),
.json() 编号 : .lines() 最终结果.
QQ 错误处理
五个名字,一个基地,每个都带一个稳定的点 code匹配代码,而不是
信件文本.
| 类 | 默认代码 | 升起时 |
|---|---|---|
CartridgeError | cartridge.error | 底; 以及 JSON 编码拒绝 |
GateError | gate.error | 闸门被滥用,或者看到两跑一次 |
MaskViolationError | mask.violation | 面罩不均匀,或蒙面时试图悬浮 |
MountError | mount.error | 一个包是无法到达的, 或者FS拒绝写 |
BudgetError | budget.exceeded | 切片、居民字节上限或中断津贴 |
InterpreterError | interpreter.error | 即时,无入站,无出站,有口罩 |
import { InterpreterError, isCartridgeError } from '@drupflare/cartridge';
try {
(await cartridge.run(script)).assertOk();
} catch (error) {
if (error instanceof InterpreterError && error.code === 'interpreter.nonzero_exit') {
// the script failed, which is different from the interpreter failing
} else if (isCartridgeError(error)) {
// something in the cartridge layer; error.code says which seam
}
}
代码使用与 supervisor.ts是因为 Finding.code,所以一个抛出的错误和一个
有记录的关于同一缺陷的发现与没有翻译表相关.
* 留校口
Gate 串行输入解释器:一次引用一次,排入FIFO顺序.
今天每个主机的呼叫都是同步的,所以还没有重叠. 一个暂停电话——JSPI, a 正在等待的查询, 一个出局的取回—— 足够第二个请求输入, 而第一个请求是 停车要求中.
有两个不同的机制:
| 机制 | 工作地点 | 为何不只这个 |
|---|---|---|
Gate | 一个纯洁的劳动者,一个坚忍的、, wrangler dev | 便携式,什么测试驱动 |
ctx.blockConcurrencyWhile() | 仅限持久对象 | 更强的,运行时间停止了 事件,但它的上限为 30 s ,不能被巢 |
doGate(gate, ctx) 是组合,和顺序事项: 门已输入 第一个 编辑
挡板被取入其中 绝不相反; blockConcurrencyWhile 无法筑巢.
传出 a ctx 改为 createCartridge() 你自动得到它.
[叹息] 在电话里找到门 已经挂起它就永远挂起,没有错误.
alarm()整个封口时fetch()因此,在添加有门的入站点之前,请检查是否 你已经在其中之一.
· 断开口罩
mask.ts 是最没有明显存在理由的作品. _zend_wasm_slice_arm(period) 火灾
越战中断 密码计数器,不是接缝和JSPI不能中止 堆与JS
里面的画框.. SuspendError: trying to suspend JS frames. 多个主机的电话正好设置了这个框架
翻译堆栈下方: SQL桥, 里面的解码器, 记录器, 每种能力
呼叫,每个懒惰的FS inflateSync和每个 print 调用.
因此中断时间必须在主机通话期间被扣住并随后被放出. 面具 实值 重新计算由于这些呼叫巢穴, 它暴露了柜台 所以泄漏是可见的而不是 推断:
import { createMask } from '@drupflare/cartridge/mask';
const mask = createMask();
const rows = mask.withMask(() => {
// any host call that puts a JS frame under the interpreter goes here
return [];
});
const { enters, nested, maxDepth, deferred, violations } = mask.stats();
4个属性,每个属性都是一种无声错误的方式:
- 被贬. 案例之巢——主机呼叫可以触发懒惰的FS读取——和不计数 内出入口的口罩解密.
- 旗子上挂着一面旗子.
zend_wasm_tick_fired()跳过EG(vm_interrupt)除非宿主记得,否则边界就会消失. - 预算在解职后重新检查不仅在民意测验现场 一个充气成员为~1毫秒 本地比例,所以一个被遮住的窗口可以自行超过一片.
- 肯定的断言 暂停从不发生在深度0以上.
MaskViolationError被丢弃 而不是吞没.
createCartridge() 给每个弹匣一个 新鲜 口罩而不是模块单子: 二
一个隔离的口译是两个C边的计数器,共用一个主计数器将使两者都成为
深度错误。 单通为工人使用的单通解释案保留出口.
[注] 主机轨道的深度是 东道主 深处。 C处理器在使用期间自我伪装 自己的回报,所以
zend_wasm_slice_stat(4)在收益范围内读取1,这是正确的,而不是 违反。 永远不要断言这一点.
QQ 文件系统
3个挂载,用于3个不同大小的问题.
| 函数 | 对于 | 费用 |
|---|---|---|
mountRecord() | 手持的几个文件 | 无;平地 Record<string, string | bytes> |
mountDrupalStreaming() | 一整棵树,在靴子上铸造 | 一分一分,一分一分 |
mountDrupalLazy() | 一整棵树, 每个文件在第一个上充气 open() | 1个居民压缩blob+1个LRU |
mountRecord() 是一个第一需要的 createCartridge({ files }) 给你打电话 A 类
包是 11 421 文件 CMS 树的正确答案 和 3 文件的错误答案 an
翻译需要运行一个脚本.
当建筑有了 没有自己的文件系统 - 速成图不出口,并出口一台TeaVM或
WASI目标没有标记 FS 或者是.. createMemoryFS() 是一个 MountFS 一个以上 Map,与
read() 财务报告和审定财务报表 readText() 所以适配器从不构建 TextDecoder。 。 。 . MountFS.utime 可选
出于同样的原因:是月亮的Lua建造了一艘真正的船标 FS 没有 utime 完全没有,而且只有
mountDrupalStreaming() 从不叫它.
懒惰的山体开始冷却 温度为3,754毫秒 cpuTime
财务报告和审定财务报表 3,066毫秒是那座山。图层按顺序合并,所以驱动程序或补丁可以阴影一个
未重建的基底包,以及 InflateStats 报告实际夸大的内容——哪些
是衡量懒惰是否付出的尺度.
[大
StreamPackEntry财务报告和审定财务报表PerFilePackEntry不属于一类,不得合并.o表示 不同的东西:流线索引被相抵入了 INFLated 连接core.bin.gz生产、每个文件的指数相抵为压缩blob和需要c旁边l一个 覆盖两者的界面将提供o两个意义和制造c可选materialise(), (中文(简体) ), 计算blob.subarray(e.o, e.o + e.c)并且会得到NaN。 。 。 .
还有两项行为:
- 图层在节点创建前合并,所以后面的一层会覆盖同一路径上较早的一层.
- 平面还原取决于打开的文件描述器表,在相同的fd数字上. 窗口对齐
已经 没有 物质:测量和伪造的(将每个inode转换为1个和500个两种)
恢复同字节。 丢弃
/dev/urandom单 fd 扔出; 丢出 sqlite 的 fd 给定 锁定- 校正错误 80-120 二档,在边缘运行时间是一个吊 请求,而不是错误。 如果你触摸了挂载或快照路径,那就是无常.
R2支持层成本 零子请求,使R2成为可变层的正确商店.
主管
supervisor.ts 持有必须是 JavaScript 的健康层的一半 : 修补路径不能
取决于它修复的子系统.
译者不能看到JS 被扔出一个垃圾堆, @ (单位:千美元) catch
既无用,又整个援引无效——不能观察它自己的孤立被杀死
无法相信自己一旦被下毒后会修复自己。 所以这些班级的检测和修理 都活下来了
在这里.
每条绊倒线都相当于一个缺陷 这个项目已经运出,然后找到. 这是 选择标准, 并每个人命名它的事件。 没人见过火的支票就是装饰.
断开器会衰减而不是循环: 窗口内相同的代码 N 次会提升一个运行 梯子,一个干净的间隔会衰变一跑, 并用一个命名的理由限制尝试.
import {
CircuitBreaker,
quarantineDecision,
runHostTripwires
} from '@drupflare/cartridge/supervisor';
const findings = runHostTripwires({ status: 200, bytes: 0, path: '/' });
const { quarantine, reason } = quarantineDecision(findings);
// quarantine === true, reason === 'render.empty: 200 with 0 bytes'
检疫击败了错误的输出. 一个503 Retry-After 答案比0字节200还好
这个工程运出了0字节200——然后缓存了它,然后从边缘服务.
尾矿工人
wrangler tail 仅在操作员被附加时报告 CPU。 尾巴工人也一样
追踪平台内部的事件,持续且没有操作员在场.
也带有 金丝雀。CPU分配结果 整个切片设计依赖于
无证行为:工作停放在一个持久对象引用中,工作恢复在另一个对象引用中
收取的费用 恢复 引证. 如果Cloudflare改变这一点,产品中没有任何失败
大声地——让自己不再适应。 这么说吧 evaluateCanary() 重新检查变量,阈值为
比率 而不是毫秒。 绝对 cpuTime 以colo为单位的不同(一种
一次部署的46毫秒是另一次部署的75毫秒).
🏗 二层
姊妹图书馆 edgeport 使用双层规则: src/core/ 唯一的代码
导入平台原始,每个公共模块都以此为基础. 这个规则不会转移
在这里.
弹匣没有从平台上进口任何东西. grep -rn "from 'cloudflare" src/ 返回无.
其原始内容是: Module 带有 FS - 被立即攻击 消费者
交给我 instantiate,而持久对象状态作为可选对象到达
BlockingContext 上面有一个方法。 所以两层的分裂是用来保护的
已满足于没有要包含的平台导入 .
转让和应用什么:一个包的子路径输出,诚实的 sideEffects
声明,一个统一的出错词汇表,带有逃生舱门的ergonomics,以及配方,每个配方由
一个绿色的光谱.
测试
bun run typecheck
bun run test # in workerd
bun run test:coverage
bun run test:interpreters # 40 assertions against real wasm builds, in node
382通过,99%的发言. 门在下方 @cloudflare/vitest-pool-workers,所以这个
内部执行 工人d 而不是节点,和覆盖使用 provider: 'istanbul' 而不是说
v8。这是一个运行时间事实,而不是偏好:v8提供者从节点读取覆盖
检查员,这些测试 运行在工人的隔离, 所以它属性0 而每次测试
通过.
四个括号,每个括号都诚实地说明可以证明什么:
| 括号 | 何处 | 证明 |
|---|---|---|
| 行为过假 | 多数规格 | 合同,没有 40 MB 二进制 |
| 真实的平台原始 | mount.spec.ts (真正的gzip,真实) Response, (中文(简体) ), Fetcher(中文(简体) ) | 向上并写入路径到结束 |
| 来文方的断言 | lazy-fs.spec.ts,该部分 mask.spec.ts | 没有假的可以进入 |
| 一个真正的原子弹翻译器 | tests/interpreters/ (PHP,Lua,CPython,快活JS,Ruby,Java) | 一个真正的建筑满足 { FS, callMain } |
最后一道是唯一能让 ADVANCED_USAGE.md 说 已验证它的建筑是
被钉住 devDependencies 故能修饰之以相接 e2e.yml 每推一推,都有警卫
如果进行零测试或跳过任何测试, 则失败 : 一个默默跳过的道读取为通道 .
有关仓库
| 仓库 | 这是什么 |
|---|---|
drupflare/worker | 消费者:Cloudflare工人上的Drupal 11 |
drupflare/durabledb | 进口 /gate 财务报告和审定财务报表 /mask 用于其SQL桥 |
drupflare/phasm | 构建解释器,包括 VM 中断补丁口罩驱动器 |
drupflare/stream-http | ưμ㼯A https:// 流包, 在一个被注入的抓取上 |
drupflare/untarl | 取出无节点API的焦油和焦油.gz |
许可证
MIT (c) 格雷戈里·米切尔 2026. 见 自由主义。 。 。 .