{"_id":"@drupflare/cartridge","_rev":"7-c2126c1c18b7d3c1d2813fd83eb37d3b","name":"@drupflare/cartridge","dist-tags":{"latest":"0.3.1"},"versions":{"0.1.0":{"name":"@drupflare/cartridge","version":"0.1.0","keywords":["cloudflare","workers","durable-objects","wasm","interpreter","emscripten","memfs","reentrancy","tail-worker"],"author":{"name":"Gregory Mitchell","email":"me@gmitch215.xyz"},"license":"MIT","_id":"@drupflare/cartridge@0.1.0","maintainers":[{"name":"gmitch215","email":"me@gmitch215.xyz"}],"homepage":"https://github.com/drupflare/cartridge","bugs":{"url":"https://github.com/drupflare/cartridge/issues"},"dist":{"shasum":"97a4b8047118f3888cb17060ed957b6007cf32d5","tarball":"https://registry.npmjs.org/@drupflare/cartridge/-/cartridge-0.1.0.tgz","fileCount":16,"integrity":"sha512-Csku2YUQLmbMj7ML2KlRpz7TNm+/+ZaDMhYPDohDuT7ZdVeowoGfmbM2X4Gw1uzDyZsHY3/vvG4PLywK7s9GPw==","signatures":[{"sig":"MEUCIC13eI3ffposxExmWp7VkozaeHh6GIZ473GTEvMl4NVLAiEAwWPHbcFYAws9JUqzKC/gVBhInvzPmB3PcR99ApMp0xo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":164272},"main":"./src/index.ts","type":"module","types":"./src/index.ts","exports":{".":"./src/index.ts","./fs":"./src/fs.ts","./gate":"./src/serialize.ts","./mask":"./src/mask.ts","./shim":"./src/worker-shim.ts","./tail":"./src/tail-worker.ts","./util":"./src/util.ts","./errors":"./src/errors.ts","./cartridge":"./src/cartridge.ts","./supervisor":"./src/supervisor.ts","./package.json":"./package.json"},"gitHead":"f67e150b6fbf6b7b03ef6939315f825a8489b117","scripts":{"test":"vitest run --project=unit","prepare":"husky","prettier":"prettier --write .","typecheck":"tsc -p tsconfig.json --noEmit","docs:build":"typedoc","docs:serve":"typedoc --watch","test:watch":"vitest --project=unit","test:coverage":"vitest run --project=unit --coverage","prettier:check":"prettier --check .","test:interpreters":"vitest run --project=interpreters"},"_npmUser":{"name":"gmitch215","email":"me@gmitch215.xyz"},"repository":{"url":"git+https://github.com/drupflare/cartridge.git","type":"git"},"_npmVersion":"11.6.1","description":"Run a blocking wasm interpreter inside a Cloudflare Durable Object","directories":{},"lint-staged":{"*.{ts,js,mjs,css,md,json,jsonc}":"prettier --write"},"sideEffects":["./src/worker-shim.ts"],"_nodeVersion":"24.11.0","dependencies":{"fflate":"^0.8.3"},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","vitest":"^4.1.0","pyodide":"^314.0.3","typedoc":"^0.28.20","wasmoon":"^1.16.0","php-wasm":"0.1.0","prettier":"^3.9.6","wrangler":"^4.20.0","typescript":"^5.9.3","lint-staged":"^17.3.0","@ruby/wasm-wasi":"^2.10.1","prettier-plugin-sh":"^0.19.0","quickjs-emscripten":"^0.32.0","@ruby/3.4-wasm-wasi":"^2.10.1","@bjorn3/browser_wasi_shim":"^0.4.2","@cloudflare/workers-types":"^5.20260811.1","@vitest/coverage-istanbul":"^4.1.0","@cloudflare/vitest-pool-workers":"^0.21.0","prettier-plugin-organize-imports":"^4.3.0"},"_npmOperationalInternal":{"tmp":"tmp/cartridge_0.1.0_1786684811782_0.8839263781470315","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@drupflare/cartridge","version":"0.1.1","keywords":["cloudflare","workers","durable-objects","wasm","interpreter","emscripten","memfs","reentrancy","tail-worker"],"author":{"name":"Gregory Mitchell","email":"me@gmitch215.xyz"},"license":"MIT","_id":"@drupflare/cartridge@0.1.1","maintainers":[{"name":"gmitch215","email":"me@gmitch215.xyz"}],"homepage":"https://github.com/drupflare/cartridge","bugs":{"url":"https://github.com/drupflare/cartridge/issues"},"dist":{"shasum":"17a9f0c4e04caae4f2e0404fe9e39afd76a77747","tarball":"https://registry.npmjs.org/@drupflare/cartridge/-/cartridge-0.1.1.tgz","fileCount":17,"integrity":"sha512-CCuxI8ewsubGKzE2q4Szg9o9rs6ZTduutAvbhblLGtOTRXTsFNuSb7mspGCUtYhWhyaNG01HQpv+6A9+Ie6y1A==","signatures":[{"sig":"MEQCIB03X1sWfqhVhHf5H+YLe5qkM3EJrbLLGCcrQ6/PxyQZAiBrhZEjwcqCJRixG/774QT0kbrvWsp09mebW4A0fsmylw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@drupflare%2fcartridge@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":171092},"main":"./src/index.ts","type":"module","types":"./src/index.ts","exports":{".":"./src/index.ts","./fs":"./src/fs.ts","./gate":"./src/serialize.ts","./mask":"./src/mask.ts","./shim":"./src/worker-shim.ts","./tail":"./src/tail-worker.ts","./util":"./src/util.ts","./errors":"./src/errors.ts","./inflate":"./src/inflate.ts","./cartridge":"./src/cartridge.ts","./supervisor":"./src/supervisor.ts","./package.json":"./package.json"},"gitHead":"166afa351e3ff7c4c03b16f8c4654be4760c3fcf","scripts":{"test":"vitest run --project=unit","prepare":"husky","prettier":"prettier --write .","typecheck":"tsc -p tsconfig.json --noEmit","docs:build":"typedoc","docs:serve":"typedoc --watch","test:watch":"vitest --project=unit","test:coverage":"vitest run --project=unit --coverage","prettier:check":"prettier --check .","test:interpreters":"vitest run --project=interpreters"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:65bde9ae-4455-47e9-932a-f5703eef03b9"}},"repository":{"url":"git+https://github.com/drupflare/cartridge.git","type":"git"},"_npmVersion":"11.17.0","description":"Run a blocking wasm interpreter inside a Cloudflare Durable Object","directories":{},"lint-staged":{"*.{ts,js,mjs,css,md,json,jsonc}":"prettier --write"},"sideEffects":["./src/worker-shim.ts"],"_nodeVersion":"24.19.0","dependencies":{"fzstd":"^0.1.1","fflate":"^0.8.3"},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","vitest":"^4.1.0","pyodide":"^314.0.3","typedoc":"^0.28.20","wasmoon":"^1.16.0","php-wasm":"0.1.0","prettier":"^3.9.6","wrangler":"^4.20.0","typescript":"^5.9.3","lint-staged":"^17.3.0","@ruby/wasm-wasi":"^2.10.1","prettier-plugin-sh":"^0.19.0","quickjs-emscripten":"^0.32.0","@ruby/3.4-wasm-wasi":"^2.10.1","@bjorn3/browser_wasi_shim":"^0.4.2","@cloudflare/workers-types":"^5.20260811.1","@vitest/coverage-istanbul":"^4.1.0","@cloudflare/vitest-pool-workers":"^0.21.0","prettier-plugin-organize-imports":"^4.3.0"},"_npmOperationalInternal":{"tmp":"tmp/cartridge_0.1.1_1786724041384_0.2088885765634132","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@drupflare/cartridge","version":"0.1.2","keywords":["cloudflare","workers","durable-objects","wasm","interpreter","emscripten","memfs","reentrancy","tail-worker"],"author":{"name":"Gregory Mitchell","email":"me@gmitch215.xyz"},"license":"MIT","_id":"@drupflare/cartridge@0.1.2","maintainers":[{"name":"gmitch215","email":"me@gmitch215.xyz"}],"homepage":"https://github.com/drupflare/cartridge","bugs":{"url":"https://github.com/drupflare/cartridge/issues"},"dist":{"shasum":"7d4ed954b7509ea4126cca00ac962a9df281c71c","tarball":"https://registry.npmjs.org/@drupflare/cartridge/-/cartridge-0.1.2.tgz","fileCount":17,"integrity":"sha512-fvD2zvb5AwqhsVwHeIyWwqFvxoCH25E2cvZQPbim9vfjzlgHEMHAp2FJ07dHqh/WQn01jc4XZT45YBDwP7Ug4w==","signatures":[{"sig":"MEUCIQCcy7r0DbleDtwvK2na049Db9fzYgVRgbP9+P+1WlOvDgIgPE2kByJD+mMSRLY73HwATXUjqhL6fniA4YCMDYYpUEw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@drupflare%2fcartridge@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":175534},"main":"./src/index.ts","type":"module","types":"./src/index.ts","exports":{".":"./src/index.ts","./fs":"./src/fs.ts","./gate":"./src/serialize.ts","./mask":"./src/mask.ts","./shim":"./src/worker-shim.ts","./tail":"./src/tail-worker.ts","./util":"./src/util.ts","./errors":"./src/errors.ts","./inflate":"./src/inflate.ts","./cartridge":"./src/cartridge.ts","./supervisor":"./src/supervisor.ts","./package.json":"./package.json"},"gitHead":"fa7464892d43ee8a102698b19c43d89f667df65f","scripts":{"test":"vitest run --project=unit","prepare":"husky","prettier":"prettier --write .","typecheck":"tsc -p tsconfig.json --noEmit","docs:build":"typedoc","docs:serve":"typedoc --watch","test:watch":"vitest --project=unit","test:coverage":"vitest run --project=unit --coverage","prettier:check":"prettier --check .","test:interpreters":"vitest run --project=interpreters"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:65bde9ae-4455-47e9-932a-f5703eef03b9"}},"repository":{"url":"git+https://github.com/drupflare/cartridge.git","type":"git"},"_npmVersion":"11.17.0","description":"Run a blocking wasm interpreter inside a Cloudflare Durable Object","directories":{},"lint-staged":{"*.{ts,js,mjs,css,md,json,jsonc}":"prettier --write"},"sideEffects":["./src/worker-shim.ts"],"_nodeVersion":"24.19.0","dependencies":{"fzstd":"^0.1.1","fflate":"^0.8.3"},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","vitest":"^4.1.0","pyodide":"^314.0.3","typedoc":"^0.28.20","wasmoon":"^1.16.0","php-wasm":"0.1.0","prettier":"^3.9.6","wrangler":"^4.20.0","typescript":"^5.9.3","lint-staged":"^17.3.0","@ruby/wasm-wasi":"^2.10.1","prettier-plugin-sh":"^0.19.0","quickjs-emscripten":"^0.32.0","@ruby/3.4-wasm-wasi":"^2.10.1","@bjorn3/browser_wasi_shim":"^0.4.2","@cloudflare/workers-types":"^5.20260811.1","@vitest/coverage-istanbul":"^4.1.0","@cloudflare/vitest-pool-workers":"^0.21.0","prettier-plugin-organize-imports":"^4.3.0"},"_npmOperationalInternal":{"tmp":"tmp/cartridge_0.1.2_1786734623441_0.5944520358054761","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@drupflare/cartridge","version":"0.1.3","keywords":["cloudflare","workers","durable-objects","wasm","interpreter","emscripten","memfs","reentrancy","tail-worker"],"author":{"name":"Gregory Mitchell","email":"me@gmitch215.xyz"},"license":"MIT","_id":"@drupflare/cartridge@0.1.3","maintainers":[{"name":"gmitch215","email":"me@gmitch215.xyz"}],"homepage":"https://github.com/drupflare/cartridge","bugs":{"url":"https://github.com/drupflare/cartridge/issues"},"dist":{"shasum":"350efc6de526e611e92ce9fb00cce5cf583a472f","tarball":"https://registry.npmjs.org/@drupflare/cartridge/-/cartridge-0.1.3.tgz","fileCount":17,"integrity":"sha512-RJ2UU/47hvFX4FI0Rgv2dSg+XrJIAFjlxFWYyktlZjqZi3xOggCHSBG9m9XlUYbRKO1m8kG+TayZ/nIZa6cdgg==","signatures":[{"sig":"MEQCIGqJtXtDzpSosWpcpoxEMQ3vBEEG00itt+WT9OzHqANSAiA5PZyTMRyPPTC6i7xOJTfWnYREuUIkU5RD776+IWQHSQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@drupflare%2fcartridge@0.1.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":176199},"main":"./src/index.ts","type":"module","types":"./src/index.ts","exports":{".":"./src/index.ts","./fs":"./src/fs.ts","./gate":"./src/serialize.ts","./mask":"./src/mask.ts","./shim":"./src/worker-shim.ts","./tail":"./src/tail-worker.ts","./util":"./src/util.ts","./errors":"./src/errors.ts","./inflate":"./src/inflate.ts","./cartridge":"./src/cartridge.ts","./supervisor":"./src/supervisor.ts","./package.json":"./package.json"},"gitHead":"f12f6bd3fb922bf1bd72e644654063afefc00a05","scripts":{"test":"vitest run --project=unit --project=node","prepare":"husky","prettier":"prettier --write .","typecheck":"tsc -p tsconfig.json --noEmit","docs:build":"typedoc","docs:serve":"typedoc --watch","test:watch":"vitest --project=unit --project=node","test:coverage":"vitest run --project=unit --project=node --coverage","prettier:check":"prettier --check .","test:interpreters":"vitest run --project=interpreters"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:65bde9ae-4455-47e9-932a-f5703eef03b9"}},"repository":{"url":"git+https://github.com/drupflare/cartridge.git","type":"git"},"_npmVersion":"11.17.0","description":"Run a blocking wasm interpreter inside a Cloudflare Durable Object","directories":{},"lint-staged":{"*.{ts,js,mjs,css,md,json,jsonc}":"prettier --write"},"sideEffects":["./src/worker-shim.ts"],"_nodeVersion":"24.19.0","dependencies":{"fzstd":"^0.1.1","fflate":"^0.8.3"},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","vitest":"^4.1.0","pyodide":"^314.0.3","typedoc":"^0.28.20","wasmoon":"^1.16.0","php-wasm":"0.1.0","prettier":"^3.9.6","wrangler":"^4.20.0","typescript":"^5.9.3","lint-staged":"^17.3.0","@ruby/wasm-wasi":"^2.10.1","prettier-plugin-sh":"^0.19.0","quickjs-emscripten":"^0.32.0","@ruby/3.4-wasm-wasi":"^2.10.1","@bjorn3/browser_wasi_shim":"^0.4.2","@cloudflare/workers-types":"^5.20260811.1","@vitest/coverage-istanbul":"^4.1.0","@cloudflare/vitest-pool-workers":"^0.22.0","prettier-plugin-organize-imports":"^4.3.0"},"_npmOperationalInternal":{"tmp":"tmp/cartridge_0.1.3_1787341537633_0.22753969605806623","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@drupflare/cartridge","version":"0.2.0","keywords":["cloudflare","workers","durable-objects","wasm","interpreter","emscripten","memfs","reentrancy","tail-worker"],"author":{"name":"Gregory Mitchell","email":"me@gmitch215.xyz"},"license":"MIT","_id":"@drupflare/cartridge@0.2.0","maintainers":[{"name":"gmitch215","email":"me@gmitch215.xyz"}],"homepage":"https://github.com/drupflare/cartridge","bugs":{"url":"https://github.com/drupflare/cartridge/issues"},"dist":{"shasum":"3d80ac02775d1172607262409074e93860f54221","tarball":"https://registry.npmjs.org/@drupflare/cartridge/-/cartridge-0.2.0.tgz","fileCount":17,"integrity":"sha512-W92yDLlzPGudvAZgZrZzlY5M8GBckCMU27k7VHBfXpvr2AZxEqGwilABoy3AMlsDbEMUPbGuchYF2ifnZtXM1w==","signatures":[{"sig":"MEUCIQC1sdkWZTH9dF+ueN4roA5xkwGrz9nCqGJU99+pO54EMQIgeb8f9gB+CtWzWPrfPy/a5SW2DrAag/wiSbv79l1VAsg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@drupflare%2fcartridge@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":179247},"main":"./src/index.ts","type":"module","types":"./src/index.ts","exports":{".":"./src/index.ts","./fs":"./src/fs.ts","./gate":"./src/serialize.ts","./mask":"./src/mask.ts","./shim":"./src/worker-shim.ts","./tail":"./src/tail-worker.ts","./util":"./src/util.ts","./errors":"./src/errors.ts","./inflate":"./src/inflate.ts","./cartridge":"./src/cartridge.ts","./supervisor":"./src/supervisor.ts","./package.json":"./package.json"},"gitHead":"1cec5318ce236da9d3dbe40acccd86e63e29629c","scripts":{"test":"vitest run --project=unit --project=node","prepare":"husky","prettier":"prettier --write .","typecheck":"tsc -p tsconfig.json --noEmit","docs:build":"typedoc","docs:serve":"typedoc --watch","test:watch":"vitest --project=unit --project=node","test:coverage":"vitest run --project=unit --project=node --coverage","prettier:check":"prettier --check .","test:interpreters":"vitest run --project=interpreters"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:65bde9ae-4455-47e9-932a-f5703eef03b9"}},"repository":{"url":"git+https://github.com/drupflare/cartridge.git","type":"git"},"_npmVersion":"11.17.0","description":"Run a blocking wasm interpreter inside a Cloudflare Durable Object","directories":{},"lint-staged":{"*.{ts,js,mjs,css,md,json,jsonc}":"prettier --write"},"sideEffects":["./src/worker-shim.ts"],"_nodeVersion":"24.19.0","dependencies":{"fzstd":"^0.1.1","fflate":"^0.8.3"},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","vitest":"^4.1.0","pyodide":"^314.0.3","typedoc":"^0.28.20","wasmoon":"^1.16.0","php-wasm":"0.1.0","prettier":"^3.9.6","wrangler":"^4.20.0","typescript":"^5.9.3","lint-staged":"^17.3.0","@ruby/wasm-wasi":"^2.10.1","@gmitch215/bytebox":"^1.0.0","prettier-plugin-sh":"^0.19.0","quickjs-emscripten":"^0.32.0","@ruby/3.4-wasm-wasi":"^2.10.1","@bjorn3/browser_wasi_shim":"^0.4.2","@cloudflare/workers-types":"^5.20260811.1","@vitest/coverage-istanbul":"^4.1.0","@cloudflare/vitest-pool-workers":"^0.22.0","prettier-plugin-organize-imports":"^4.3.0"},"_npmOperationalInternal":{"tmp":"tmp/cartridge_0.2.0_1787986479390_0.9842442664319335","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@drupflare/cartridge","version":"0.3.0","keywords":["cloudflare","workers","durable-objects","wasm","interpreter","emscripten","memfs","reentrancy","tail-worker"],"author":{"name":"Gregory Mitchell","email":"me@gmitch215.xyz"},"license":"MIT","_id":"@drupflare/cartridge@0.3.0","maintainers":[{"name":"gmitch215","email":"me@gmitch215.xyz"}],"homepage":"https://github.com/drupflare/cartridge","bugs":{"url":"https://github.com/drupflare/cartridge/issues"},"dist":{"shasum":"6cb6c9f6cf8719fd3769ca099cfe0002e3402b08","tarball":"https://registry.npmjs.org/@drupflare/cartridge/-/cartridge-0.3.0.tgz","fileCount":17,"integrity":"sha512-953j0h99BeTUe0Y1/pZUr3h7UIH+H0K2NFbCn/xga8FogYK4n/FClHNtq6RTxvTipYAdWaoO8p5SWumLPbQQ3g==","signatures":[{"sig":"MEQCIG8/XMbK09NePeQ0TEWUbYcvXFLKKf/ts97xtvpBMRlZAiBdGfb82IgOTL3ubql7qltprpv/KYZ6fVb3fABX2UMXhA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIAPRv31b2iaKlSINpZIsgQj86F1gr8yOeXNPl52btqZMAiEAvr6bMagR4yz+lonHx7qbmAGjEecABwOFKiqWQGVQ2TY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@drupflare%2fcartridge@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":180987},"main":"./src/index.ts","type":"module","types":"./src/index.ts","exports":{".":"./src/index.ts","./fs":"./src/fs.ts","./gate":"./src/serialize.ts","./mask":"./src/mask.ts","./shim":"./src/worker-shim.ts","./tail":"./src/tail-worker.ts","./util":"./src/util.ts","./errors":"./src/errors.ts","./inflate":"./src/inflate.ts","./cartridge":"./src/cartridge.ts","./supervisor":"./src/supervisor.ts","./package.json":"./package.json"},"gitHead":"519b05cf1b706c9569bf9cf69fcaf926227e5098","scripts":{"test":"vitest run --project=unit --project=node","prepare":"husky","prettier":"prettier --write .","typecheck":"tsc -p tsconfig.json --noEmit","docs:build":"typedoc","docs:serve":"typedoc --watch","test:watch":"vitest --project=unit --project=node","test:coverage":"vitest run --project=unit --project=node --coverage","prettier:check":"prettier --check .","test:interpreters":"vitest run --project=interpreters"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"65bde9ae-4455-47e9-932a-f5703eef03b9"}},"repository":{"url":"git+https://github.com/drupflare/cartridge.git","type":"git"},"_npmVersion":"11.19.0","description":"Run a blocking wasm interpreter inside a Cloudflare Durable Object","directories":{},"lint-staged":{"*.{ts,js,mjs,css,md,json,jsonc}":"prettier --write"},"sideEffects":["./src/worker-shim.ts"],"_nodeVersion":"24.21.0","dependencies":{"fzstd":"^0.1.1","fflate":"^0.8.3"},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","vitest":"^4.1.0","pyodide":"^314.0.3","typedoc":"^0.28.20","wasmoon":"^1.16.0","php-wasm":"0.1.0","prettier":"^3.9.6","wrangler":"^4.20.0","typescript":"^5.9.3","lint-staged":"^17.3.0","@ruby/wasm-wasi":"^2.10.1","@gmitch215/bytebox":"^1.0.0","prettier-plugin-sh":"^0.20.0","quickjs-emscripten":"^0.32.0","@ruby/3.4-wasm-wasi":"^2.10.1","@bjorn3/browser_wasi_shim":"^0.4.2","@cloudflare/workers-types":"^5.20260811.1","@vitest/coverage-istanbul":"^4.1.0","@cloudflare/vitest-pool-workers":"^0.22.0","prettier-plugin-organize-imports":"^4.3.0"},"_npmOperationalInternal":{"tmp":"tmp/cartridge_0.3.0_1790771616799_0.7132768796924582","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"_id":"@drupflare/cartridge@0.3.1","bugs":{"url":"https://github.com/drupflare/cartridge/issues"},"dist":{"shasum":"f5fd7ee41f8e86609262a3786a72500cf59cea21","tarball":"https://registry.npmjs.org/@drupflare/cartridge/-/cartridge-0.3.1.tgz","fileCount":17,"integrity":"sha512-lU7Xh5xmUmTet8m3iK9os51k6WDfoWZ7O+J+8igwZ3sFw399hjRkK4txdCLRk6zOEVlKeRltKKlfIVQ2tlSmQw==","signatures":[{"sig":"MEUCIQDw7dwjkLV4zWx+U42Cusvc6Wu31C79lQNQTRcrYhf5rQIgC7vs6mPbrKFBFM3xo9CT9cwLBmqfW54mQzd3aGwo6kE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDeKTKVFZ7emu6Dof5OB2O/IwiIBZBjpmOWLZ7HUZetlAIhAMmQH6VTo1KyNVK9poJ9/oJLdbrIUexCltetWwuwkru6"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@drupflare%2fcartridge@0.3.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":179247},"main":"./src/index.ts","name":"@drupflare/cartridge","type":"module","types":"./src/index.ts","author":{"name":"Gregory Mitchell","email":"me@gmitch215.xyz"},"exports":{".":"./src/index.ts","./fs":"./src/fs.ts","./gate":"./src/serialize.ts","./mask":"./src/mask.ts","./shim":"./src/worker-shim.ts","./tail":"./src/tail-worker.ts","./util":"./src/util.ts","./errors":"./src/errors.ts","./inflate":"./src/inflate.ts","./cartridge":"./src/cartridge.ts","./supervisor":"./src/supervisor.ts","./package.json":"./package.json"},"gitHead":"49393b575a27772d8649de6b2c3ffa367e633f15","license":"MIT","scripts":{"test":"vitest run --project=unit --project=node","prepare":"husky","prettier":"prettier --write .","typecheck":"tsc -p tsconfig.json --noEmit","docs:build":"typedoc","docs:serve":"typedoc --watch","test:watch":"vitest --project=unit --project=node","test:coverage":"vitest run --project=unit --project=node --coverage","prettier:check":"prettier --check .","test:interpreters":"vitest run --project=interpreters"},"version":"0.3.1","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"65bde9ae-4455-47e9-932a-f5703eef03b9"}},"homepage":"https://github.com/drupflare/cartridge","keywords":["cloudflare","workers","durable-objects","wasm","interpreter","emscripten","memfs","reentrancy","tail-worker"],"repository":{"url":"git+https://github.com/drupflare/cartridge.git","type":"git"},"_npmVersion":"11.19.0","description":"Run a blocking wasm interpreter inside a Cloudflare Durable Object","directories":{},"lint-staged":{"*.{ts,js,mjs,css,md,json,jsonc}":"prettier --write"},"maintainers":[{"name":"gmitch215","email":"me@gmitch215.xyz"}],"sideEffects":["./src/worker-shim.ts"],"_nodeVersion":"24.21.0","dependencies":{"fzstd":"^0.1.1","fflate":"^0.8.3"},"_hasShrinkwrap":false,"devDependencies":{"husky":"^9.1.7","vitest":"^4.1.0","pyodide":"^314.0.3","typedoc":"^0.28.20","wasmoon":"^1.16.0","php-wasm":"0.1.0","prettier":"^3.9.6","wrangler":"^4.20.0","typescript":"^5.9.3","lint-staged":"^17.3.0","@ruby/wasm-wasi":"^2.10.1","@gmitch215/bytebox":"^1.0.0","prettier-plugin-sh":"^0.20.0","quickjs-emscripten":"^0.32.0","@ruby/3.4-wasm-wasi":"^2.10.1","@bjorn3/browser_wasi_shim":"^0.4.2","@cloudflare/workers-types":"^5.20260811.1","@vitest/coverage-istanbul":"^4.1.0","@cloudflare/vitest-pool-workers":"^0.22.0","prettier-plugin-organize-imports":"^4.3.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cartridge_0.3.1_1790790215948_0.28350034905337296"}}},"time":{"created":"2026-08-14T05:20:11.604Z","modified":"2026-09-30T17:43:36.456Z","0.1.0":"2026-08-14T05:20:12.002Z","0.1.1":"2026-08-14T16:14:01.524Z","0.1.2":"2026-08-14T19:10:23.604Z","0.1.3":"2026-08-21T19:45:37.769Z","0.2.0":"2026-08-29T06:54:39.544Z","0.3.0":"2026-09-30T12:33:36.912Z","0.3.1":"2026-09-30T17:43:36.057Z"},"bugs":{"url":"https://github.com/drupflare/cartridge/issues"},"author":{"name":"Gregory Mitchell","email":"me@gmitch215.xyz"},"license":"MIT","homepage":"https://github.com/drupflare/cartridge","keywords":["cloudflare","workers","durable-objects","wasm","interpreter","emscripten","memfs","reentrancy","tail-worker"],"repository":{"url":"git+https://github.com/drupflare/cartridge.git","type":"git"},"description":"Run a blocking wasm interpreter inside a Cloudflare Durable Object","maintainers":[{"name":"gmitch215","email":"me@gmitch215.xyz"}],"readme":"# 🎮 cartridge\n\n> Run a blocking wasm interpreter inside a Cloudflare Durable Object\n\n[![Build](https://github.com/drupflare/cartridge/actions/workflows/build.yml/badge.svg)](https://github.com/drupflare/cartridge/actions/workflows/build.yml)\n[![Interpreters](https://github.com/drupflare/cartridge/actions/workflows/e2e.yml/badge.svg)](https://github.com/drupflare/cartridge/actions/workflows/e2e.yml)\n[![Prettier](https://github.com/drupflare/cartridge/actions/workflows/prettier.yml/badge.svg)](https://github.com/drupflare/cartridge/actions/workflows/prettier.yml)\n[![codecov](https://codecov.io/gh/drupflare/cartridge/branch/master/graph/badge.svg)](https://codecov.io/gh/drupflare/cartridge)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\ncartridge runs a blocking wasm interpreter inside a Cloudflare Durable Object. Pass\n`createCartridge()` a wasm module and call `run()`; the reentrancy gate, the interrupt mask and the\nfilesystem are wired for you. Every piece stays exported for callers that need them directly.\n\nThe Workers runtime is asynchronous and a wasm interpreter is not. Two overlapping invocations\nshare one mutable machine — globals, open file descriptors, session state — and interleave into\nplausible wrong output rather than failing.\n\nNothing here is specific to PHP. Real builds run through `tests/interpreters/` on every push:\n**PHP 8.3** (php-wasm), **Lua 5.4** (wasmoon), **CPython 3** (Pyodide), **QuickJS**\n(quickjs-emscripten), **CRuby 3.4** (ruby.wasm, over WASI rather than emscripten) and **Java**\n(bytebox, compiled ahead of time rather than interpreted). See\n[ADVANCED_USAGE.md](ADVANCED_USAGE.md) for the per-language status and a recipe for each.\n\n---\n\n## 📋 Table of Contents\n\n- [Why Cartridge](#-why-cartridge)\n- [Install](#-install)\n- [Quick Start](#-quick-start)\n- [What Is Here](#-what-is-here)\n- [Subpath Exports](#-subpath-exports)\n- [Ergonomics](#-ergonomics)\n- [Error Handling](#-error-handling)\n- [Reentrancy Gate](#-reentrancy-gate)\n- [Interrupt Mask](#-interrupt-mask)\n- [Filesystem](#-filesystem)\n- [Supervisor](#-supervisor)\n- [Tail Worker](#-tail-worker)\n- [Two Layers](#-two-layers)\n- [Testing](#-testing)\n- [Related Repositories](#-related-repositories)\n- [License](#-license)\n\n---\n\n## 🎯 Why Cartridge\n\nA wasm interpreter is **a single mutable machine**. Globals, the autoloader cache, the container\npointer, open file descriptors and session state are all process-wide. Two overlapping invocations do\nnot fail loudly. They interleave and produce plausible wrong output.\n\nCloudflare's runtime is the opposite shape. It is event-driven and it will happily deliver a second\nrequest while the first is parked. Durable Objects narrow that, but do not close it.\n\nSo the host has to supply four things the interpreter cannot supply for itself: serialisation, a way\nto hold off an interrupt across a host call, a filesystem that does not cost a full inflate at boot,\nand a repair path that does not run inside the thing it repairs.\n\nEach is silent when it is missing. An unmasked callback surfaces as `SuspendError` from wherever the\nopcode counter happened to land, months later and under load.\n\n---\n\n## 📥 Install\n\n```sh\nbun add @drupflare/cartridge\n```\n\nTwo runtime dependencies, `fflate` and `fzstd`, both synchronous decompressors. A lazy mount\ninflates inside a synchronous `open()` from wasm and a pre-compressed interpreter inflates at module\nscope; neither point can await `DecompressionStream`, which is also limited to gzip and deflate.\n\n---\n\n## 🚀 Quick Start\n\n```ts\nimport { createCartridge } from '@drupflare/cartridge';\n\nconst cartridge = createCartridge({\n  // whatever your build's factory is; forward the io it gives you\n  instantiate: (io) => initInterpreter({ print: io.print, printErr: io.printErr }),\n  // files every run should see, written once at boot\n  files: { 'lib/helper.txt': 'shared by every run' }\n});\n\nconst result = await cartridge.run('print(\"hello\")');\nconsole.log(result.stdoutText); // \"hello\\n\"\nconsole.log(result.status); // 0\n```\n\nThat is the default path. Every entry is serialised, every `print` runs masked, the script is\nin the filesystem before `callMain` is entered, and a leaked mask depth is reported by name instead\nof poisoning the next run.\n\n**What your build has to satisfy** is two members:\n\n```ts\ninterface Interpreter {\n  FS: MountFS; // emscripten's FS: mkdir, writeFile, and optionally utime\n  // may answer a promise, for an adapter with work to finish after main returns\n  callMain(argv: string[]): number | void | Promise<number | void>;\n}\n```\n\nwhich is what emscripten produces for a `main()`-having program built with `-sINVOKE_RUN=0` and\n`-sEXPORTED_RUNTIME_METHODS=callMain,FS`. **Most published builds do not do that**: wasmoon, Pyodide,\nquickjs-emscripten, ruby.wasm, php-wasm and bytebox were all checked and none of the six exports\n`callMain`, so writing it as a two-line adapter is the normal case rather than the fallback. `createMemoryFS()`\nsupplies the other half when a build has no filesystem either, and `FS` is three method signatures\nrather than emscripten's object, so a WASI preopen satisfies it too. See the recipes in\n[ADVANCED_USAGE.md](ADVANCED_USAGE.md), each labelled with what was actually run.\n\n---\n\n## 🧰 What Is Here\n\n| Module           | What it does                                                                     |\n| ---------------- | -------------------------------------------------------------------------------- |\n| `lazy-fs.ts`     | MEMFS mount that inflates a file on first open, from layered per-file packs      |\n| `supervisor.ts`  | tripwires, the health ledger, the circuit breaker, the repair ladder             |\n| `cartridge.ts`   | `createCartridge()`: the high-level default, and `RunResult`                     |\n| `mount.ts`       | the eager streaming mount, the driver mount, `mountRecord()`, `createMemoryFS()` |\n| `tail-worker.ts` | summarises a Tail Worker's events into something a human reads                   |\n| `mask.ts`        | the refcounted interrupt mask, plus the VM slice counters                        |\n| `inflate.ts`     | synchronous zstd: content size, `inflateZstd`, a wasm-backed decoder             |\n| `util.ts`        | `fromUtf8`/`toUtf8`, `encodeJson`/`decodeJson`, `toBytes`, `splitLines`          |\n| `serialize.ts`   | `Gate`: one interpreter, one invocation at a time                                |\n| `errors.ts`      | the five-name error vocabulary, each with a stable dotted `code`                 |\n| `worker-shim.ts` | makes the emscripten `ENVIRONMENT=worker` build instantiate on workerd           |\n\n---\n\n## 🧭 Subpath Exports\n\nOne package, `sideEffects` declared honestly, and a subpath per concern so a bundler can drop what\nyou do not use.\n\n| Import                            | What is in it                                                        |\n| --------------------------------- | -------------------------------------------------------------------- |\n| `@drupflare/cartridge`            | everything below except the shim                                     |\n| `@drupflare/cartridge/cartridge`  | `createCartridge()`, `RunResult`, `Interpreter`                      |\n| `@drupflare/cartridge/gate`       | `Gate`, `doGate`, `GateStats`                                        |\n| `@drupflare/cartridge/mask`       | `createMask`, the singleton, `vmFromBinary`, the slice counters      |\n| `@drupflare/cartridge/fs`         | both mounts, `mountRecord`, `createMemoryFS`, both pack-index shapes |\n| `@drupflare/cartridge/inflate`    | synchronous zstd, for a pre-compressed module inflated at boot       |\n| `@drupflare/cartridge/supervisor` | tripwires, `CircuitBreaker`, the ledger DDL                          |\n| `@drupflare/cartridge/tail`       | the Tail Worker handler and its pure reducers                        |\n| `@drupflare/cartridge/util`       | the byte, string and JSON conveniences                               |\n| `@drupflare/cartridge/errors`     | the error vocabulary                                                 |\n| `@drupflare/cartridge/shim`       | **side-effectful**; import for effect before the glue evaluates      |\n\n> [!WARNING]\n> `sideEffects` is an array rather than `false`. `worker-shim.ts` patches\n> `globalThis` at import time; declaring the package side-effect-free would let a bundler tree-shake\n> it away, which deletes the `Asyncify` stub and restores an **uncatchable** request kill. Declaring\n> `true` would disable shaking for the whole package. The array is the only honest answer, and\n> `tests/exports.spec.ts` pins it.\n\n---\n\n## ✨ Ergonomics\n\n**Never build a `TextEncoder` to use this package.** Every payload takes `string | Uint8Array`;\neverything that hands bytes back also hands back decoded text.\n\n```ts\nimport { createCartridge, fromUtf8, toUtf8 } from '@drupflare/cartridge';\n\nconst cartridge = createCartridge({ instantiate });\n\n// a string or bytes, both fine\nawait cartridge.run('print(1)');\nawait cartridge.run(fromUtf8('print(1)'));\n\n// structured input, no encoder at the call site\nawait cartridge.writeJson('input.json', { items: [1, 2, 3] });\n\nconst result = await cartridge.run('read input.json and print a total');\nresult.stdout; // Uint8Array, the primitive\nresult.stdoutText; // string, what you wanted\nresult.json<{ total: number }>(); // parsed\nresult.lines(); // ['{\"total\":6}']\nresult.lastLine(); // the last line, no trailing empty element\nresult.assertOk(); // throws InterpreterError naming the status and stderr\n```\n\nAnd the raw path is still there:\n\n```ts\n// the module itself, instantiated if needed, handed over inside the gate\nconst raw = await cartridge.interpreter();\n\n// or run arbitrary work against it, still serialised against every other entry\nawait cartridge.withInterpreter((module) => module.FS.writeFile('/tmp/x', 'by hand'));\n```\n\nNaming is fixed so it is learnable once: `to*`/`from*` for codecs (`toUtf8`, `fromUtf8`), `*Json` for\nserialise/parse (`writeJson`, `encodeJson`, `decodeJson`), `*Text` for decoded fields (`stdoutText`),\n`.json()` / `.lines()` on a result.\n\n---\n\n## 🔔 Error Handling\n\nFive names, one base, and every one carries a stable dotted `code`. Match on the code, not on the\nmessage text.\n\n| Class                | Default code        | Raised when                                                  |\n| -------------------- | ------------------- | ------------------------------------------------------------ |\n| `CartridgeError`     | `cartridge.error`   | the base; also the JSON codec refusals                       |\n| `GateError`          | `gate.error`        | the gate was misused, or observed two runs at once           |\n| `MaskViolationError` | `mask.violation`    | an unbalanced mask, or a suspension attempted while masked   |\n| `MountError`         | `mount.error`       | a pack was unreachable, or the FS refused a write            |\n| `BudgetError`        | `budget.exceeded`   | a slice, a resident-bytes ceiling or an interrupt allowance  |\n| `InterpreterError`   | `interpreter.error` | instantiation, no entry point, a nonzero exit, a leaked mask |\n\n```ts\nimport { InterpreterError, isCartridgeError } from '@drupflare/cartridge';\n\ntry {\n  (await cartridge.run(script)).assertOk();\n} catch (error) {\n  if (error instanceof InterpreterError && error.code === 'interpreter.nonzero_exit') {\n    // the script failed, which is different from the interpreter failing\n  } else if (isCartridgeError(error)) {\n    // something in the cartridge layer; error.code says which seam\n  }\n}\n```\n\nThe codes use the same dotted shape as `supervisor.ts`'s `Finding.code`, so a thrown error and a\nrecorded finding about the same defect correlate without a translation table.\n\n---\n\n## 🚧 Reentrancy Gate\n\n`Gate` serialises entry into the interpreter: one invocation at a time, queued in FIFO order.\n\nEvery host call is synchronous today, so nothing overlaps yet. One suspending call — JSPI, an\nawaited query, an outbound fetch — is enough for a second request to enter while the first is\nparked mid-request.\n\nTwo separate mechanisms exist:\n\n| Mechanism                     | Where it works                                   | Why not only this one                                                                     |\n| ----------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------- |\n| `Gate`                        | a plain Worker, a Durable Object, `wrangler dev` | portable, and what the tests drive                                                        |\n| `ctx.blockConcurrencyWhile()` | a Durable Object only                            | stronger — the runtime stops delivering events — but it caps at 30 s and cannot be nested |\n\n`doGate(gate, ctx)` is the combination, and the order matters: the gate is entered **first** and the\nblock is taken inside it, never the other way round; `blockConcurrencyWhile` cannot nest.\nPass a `ctx` to `createCartridge()` and you get that automatically.\n\n> [!CAUTION]\n> **Acquiring the gate inside a call that already holds it hangs forever**, with no error. `alarm()`\n> is not gated as a whole while `fetch()` is, so before adding a gated entry point, check whether\n> you are already inside one.\n\n---\n\n## 🎭 Interrupt Mask\n\n`mask.ts` is the piece with the least obvious reason to exist. `_zend_wasm_slice_arm(period)` fires\nthe VM interrupt on an **opcode counter, not a seam**, and JSPI cannot suspend a stack with a JS\nframe in it — `SuspendError: trying to suspend JS frames`. Several host calls put exactly that frame\nunder the interpreter's stack: the SQL bridge, the codec inside it, the logger, every capability\ncall, every lazy-FS `inflateSync`, and every `print` callback.\n\nSo the interrupt has to be held off for the duration of the host call and released after it. The mask\nis **refcounted**, since those calls nest, and it exposes counters so a leak is visible rather than\ninferred:\n\n```ts\nimport { createMask } from '@drupflare/cartridge/mask';\n\nconst mask = createMask();\n\nconst rows = mask.withMask(() => {\n  // any host call that puts a JS frame under the interpreter goes here\n  return [];\n});\n\nconst { enters, nested, maxDepth, deferred, violations } = mask.stats();\n```\n\nFour properties, each of which is a way this goes silently wrong without it:\n\n1. **Refcounted.** The cases nest — a host call can trigger a lazy-FS read — and a non-counting\n   mask unmasks at the inner exit.\n2. **A pending flag that fires on unmask.** `zend_wasm_tick_fired()` skips `EG(vm_interrupt)`\n   entirely while masked; it does not defer it, so the boundary is lost unless the host remembers it.\n3. **The budget re-checked on unmask**, not only at the poll site. One inflate member is ~1 ms of\n   local ratio, so a masked window can overrun a slice on its own.\n4. **A dev assertion** that suspension never happens above depth 0. `MaskViolationError` is thrown\n   rather than swallowed.\n\n`createCartridge()` gives each cartridge a **fresh** mask rather than the module singleton: two\ninterpreters in one isolate are two C-side counters, and sharing one host counter would make both\ndepths wrong. The singleton stays exported for the single-interpreter case the worker uses.\n\n> [!NOTE]\n> The depth the host tracks is the **host** depth. The C handler masks itself for the duration of its\n> own yield, so `zend_wasm_slice_stat(4)` reads 1 inside a yield and that is correct, not a\n> violation. Never assert on it.\n\n---\n\n## 💾 Filesystem\n\nThree mounts, for three different sizes of problem.\n\n| Function                 | For                                               | Cost                                            |\n| ------------------------ | ------------------------------------------------- | ----------------------------------------------- |\n| `mountRecord()`          | a handful of files you have in hand               | none; a plain `Record<string, string \\| bytes>` |\n| `mountDrupalStreaming()` | a whole tree, materialised at boot                | one inflate of everything                       |\n| `mountDrupalLazy()`      | a whole tree, inflated per file on first `open()` | one resident compressed blob + an LRU           |\n\n`mountRecord()` is the one a first use wants, and `createCartridge({ files })` calls it for you. A\npack is the right answer for an 11,421-file CMS tree and the wrong answer for the three files an\ninterpreter needs to run one script.\n\nWhen the build has **no filesystem of its own** — quickjs-emscripten exports none, and a TeaVM or\nWASI target has no emscripten `FS` either — `createMemoryFS()` is a `MountFS` over a `Map`, with\n`read()` and `readText()` so the adapter never builds a `TextDecoder`. `MountFS.utime` is optional\nfor the same reason: wasmoon's Lua build ships a real emscripten `FS` and no `utime` at all, and only\n`mountDrupalStreaming()` ever calls it.\n\nThe lazy mount attacks cold start, measured at 3,754 ms of `cpuTime`\nand **3,066 ms of that was the mount**. Layers merge in order, so a driver or a patch can shadow a\nbase pack without either being rebuilt, and `InflateStats` reports what was actually inflated — which\nis the measurement that says whether laziness paid.\n\n> [!IMPORTANT]\n> **`StreamPackEntry` and `PerFilePackEntry` are not one type and must not be merged.** `o` means\n> different things: the streaming index offsets into the INFLATED concatenation `core.bin.gz`\n> produces, the per-file index offsets into the COMPRESSED blob and needs `c` beside `l`. One\n> interface covering both would give `o` two meanings and make `c` optional for `materialise()`,\n> which computes `blob.subarray(e.o, e.o + e.c)` and would get `NaN`.\n\nTwo further behaviours:\n\n- **Layers merge BEFORE node creation**, so a later layer overrides an earlier one on the same path.\n- **Heap restore depends on the open file-descriptor table, at the same fd numbers.** Inode alignment\n  does **not** matter: that was measured and falsified (shifting every inode by 1 and by 500 both\n  restored byte-identically). Dropping `/dev/urandom`'s fd alone throws; dropping sqlite's fds gives\n  a locking-protocol error after an **80-120 second stall**, which on an edge runtime is a hung\n  request, not an error. If you touch the mount or the snapshot path, that is the invariant.\n\nAn R2-backed layer costs **zero subrequests**, making R2 the right store for a mutable layer.\n\n---\n\n## 🩺 Supervisor\n\n`supervisor.ts` holds the half of a health layer that must be JavaScript: **a repair path must not\ndepend on the subsystem it repairs.**\n\nThe interpreter cannot observe a JS throw out of a wasm import — measured twice, `@` and a `catch`\nare both useless and the whole invocation dies — cannot observe its own isolate being killed, and\ncannot be trusted to fix itself once poisoned. So detection and repair for those classes live out\nhere.\n\n**Every tripwire corresponds to a defect this project has already shipped and then found.** That is\nthe selection criterion, and each one names its incident. A check nobody has seen fire is decoration.\n\nThe breaker decays rather than looping: the same code N times inside a window escalates one rung on\nthe ladder, a clean interval decays one rung, and bounded attempts halt with a named reason.\n\n```ts\nimport {\n  CircuitBreaker,\n  quarantineDecision,\n  runHostTripwires\n} from '@drupflare/cartridge/supervisor';\n\nconst findings = runHostTripwires({ status: 200, bytes: 0, path: '/' });\nconst { quarantine, reason } = quarantineDecision(findings);\n// quarantine === true, reason === 'render.empty: 200 with 0 bytes'\n```\n\n**Quarantine beats wrong output.** A 503 with `Retry-After` is a better answer than a 0-byte 200, and\nthis project has shipped the 0-byte 200 — then cached it, then served it from the edge.\n\n---\n\n## 📡 Tail Worker\n\n`wrangler tail` reports CPU only while an operator is attached. A Tail Worker receives the same\ntrace events from inside the platform, continuously and with no operator present.\n\nIt also carries the **canary**. The CPU-attribution result the whole slicing design rests on is\nundocumented behaviour: work parked in one Durable Object invocation and resumed in another is\ncharged to the **resuming** invocation. If Cloudflare changes that, nothing in the product fails\nloudly — renders just stop fitting. So `evaluateCanary()` re-checks the invariant, with thresholds as\n**ratios** rather than milliseconds. Absolute `cpuTime` varies by colo (a render that was\n46 ms on one deploy was 75 ms on another).\n\n---\n\n## 🏗 Two Layers\n\nThe sibling library `edgeport` uses a two-layer rule: a private `src/core/` is the only code\nimporting the platform primitive, and every public module builds on it. That rule does not transfer\nhere.\n\n**cartridge imports nothing from the platform.** `grep -rn \"from 'cloudflare\" src/` returns nothing.\nThe primitive it is about — an emscripten `Module` with an `FS` — is instantiated by the **consumer**\nand handed in through `instantiate`, and the Durable Object state arrives as an optional\n`BlockingContext` with one method on it. So the invariant the two-layer split exists to protect is\nalready satisfied by there being no platform import to contain.\n\nWhat does transfer, and is applied: subpath exports from one package, an honest `sideEffects`\ndeclaration, a uniform error vocabulary, ergonomics with an escape hatch, and recipes each backed by\na green spec.\n\n---\n\n## 🧪 Testing\n\n```sh\nbun run typecheck\nbun run test # in workerd\nbun run test:coverage\nbun run test:interpreters # 40 assertions against real wasm builds, in node\n```\n\n**382 passing, 99% of statements.** The gate runs under `@cloudflare/vitest-pool-workers`, so it\nexecutes inside **workerd** rather than Node, and coverage uses `provider: 'istanbul'` rather than\n`v8`. That is a runtime fact, not a preference: the v8 provider reads coverage off the Node\ninspector, and these tests run inside workerd's isolate, so it attributes zero while every test\npasses.\n\nFour brackets, and each one is honest about what it can prove:\n\n| Bracket                     | Where                                                          | Proves                                          |\n| --------------------------- | -------------------------------------------------------------- | ----------------------------------------------- |\n| behaviour over a fake       | most specs                                                     | the contract, without a 40 MB binary in the way |\n| the real platform primitive | `mount.spec.ts` (real gzip, real `Response`, `Fetcher`)        | the inflate-and-write path end to end           |\n| source assertions           | `lazy-fs.spec.ts`, part of `mask.spec.ts`                      | invariants no fake can reach into               |\n| a real wasm interpreter     | `tests/interpreters/` (PHP, Lua, CPython, QuickJS, Ruby, Java) | that a real build satisfies `{ FS, callMain }`  |\n\nThat last lane is the only thing that makes `ADVANCED_USAGE.md` say **Verified**. Its builds are\npinned `devDependencies` so renovate bumps them, and `e2e.yml` runs it on every push with a guard\nthat fails if zero tests ran or any was skipped: a silently skipped lane reads as a pass.\n\n---\n\n## 🔗 Related Repositories\n\n| Repository                                                          | What it is                                                               |\n| ------------------------------------------------------------------- | ------------------------------------------------------------------------ |\n| [`drupflare/worker`](https://github.com/drupflare/worker)           | the consumer: Drupal 11 on Cloudflare Workers                            |\n| [`drupflare/durabledb`](https://github.com/drupflare/durabledb)     | imports `/gate` and `/mask` for its SQL bridge                           |\n| [`drupflare/phasm`](https://github.com/drupflare/phasm)             | builds the interpreter, including the VM interrupt patch the mask drives |\n| [`drupflare/stream-http`](https://github.com/drupflare/stream-http) | the PHP-side `https://` stream wrapper, over an injected fetch           |\n| [`drupflare/untarl`](https://github.com/drupflare/untarl)           | tar and tar.gz extraction with no Node APIs                              |\n\n---\n\n## 📄 License\n\nMIT (c) Gregory Mitchell 2026. See [LICENSE](LICENSE).\n","readmeFilename":"README.md"}