@storic/cloudflare
v0.4.0
Published
`JsEvaluator` implementation for Storic using Cloudflare [Dynamic Worker Loaders](https://developers.cloudflare.com/workers/runtime-apis/bindings/worker-loader/).
Downloads
534
Readme
@storic/cloudflare
JsEvaluator implementation for Storic using Cloudflare Dynamic Worker Loaders.
Each evaluation generates a worker module with the expression and bindings injected directly into the source. The module is loaded into an isolated worker with no network access (globalOutbound: null), providing true sandboxing.
Installation
bun add @storic/cloudflareSetup
1. Add the worker loader binding to your wrangler config
# wrangler.toml
[[worker_loaders]]
binding = "EVALUATOR"2. Wire the layer in your worker
import { Effect, Layer } from "effect";
import { Store } from "@storic/core";
import { CloudflareJsEvaluator, WorkerLoaderBinding } from "@storic/cloudflare";
export default {
async fetch(request: Request, env: Env) {
const StoreLive = Store.layer.pipe(
Layer.provide(
Layer.mergeAll(
SqlLive,
CloudflareJsEvaluator.layer.pipe(Layer.provide(WorkerLoaderBinding.layer(env.EVALUATOR))),
),
),
);
// use StoreLive ...
},
};How it works
When evaluate(jsExpr, bindings) is called:
- Binding names are validated (must be valid JS identifiers)
- Binding values are JSON-serialized (must be serializable)
- The expression is wrapped in an IIFE with bindings as parameters/arguments
- A fresh dynamic worker is spawned via the Worker Loader API
- The expression evaluates at module initialization time
- The result is returned via a minimal
fetchhandler
For example, evaluating a + b with { a: 10, b: 20 } generates:
let __result;
let __error;
try {
__result = ((a, b) => a + b)(10, 20);
} catch (e) {
__error = e instanceof Error && e.message ? e.message : String(e);
}
export default {
fetch() {
if (__error !== undefined) {
return Response.json({ error: __error }, { status: 400 });
}
const t = typeof __result;
if (t === "function" || t === "symbol" || t === "undefined") {
return Response.json({ error: "Result is not JSON-serializable: got " + t }, { status: 400 });
}
try {
return Response.json({ result: __result });
} catch (e) {
const msg = e instanceof Error && e.message ? e.message : String(e);
return Response.json({ error: "Result is not JSON-serializable: " + msg }, { status: 400 });
}
},
};This mirrors core's new Function(...names, 'return (expr)')(...values) pattern, but runs in an isolated worker with globalOutbound: null — no network access, no bindings, no access to the parent worker's environment.
API
CloudflareJsEvaluator.layer
Layer.Layer<JsEvaluator, never, WorkerLoaderBinding>
Provides the JsEvaluator service. Requires a WorkerLoaderBinding.
WorkerLoaderBinding.layer(loader)
(loader: WorkerLoader) => Layer.Layer<WorkerLoaderBinding>
Creates a WorkerLoaderBinding layer from the env.EVALUATOR binding in your worker.
WorkerLoader
Minimal type for the Cloudflare Worker Loader binding. Matches the get() method from the Worker Loader API.
EvaluatorModuleError
Thrown synchronously by generateEvaluatorModule when bindings contain invalid identifier names or non-JSON-serializable values. Wrapped into a TransformError by the layer.
Notes
- Closed beta: Dynamic Worker Loaders require signing up for the closed beta. They work locally with
wrangler dev. - Bindings are JSON-serialized: Functions, classes, symbols,
undefined,BigInt, and circular references cannot be passed as bindings. Binding names must be valid JavaScript identifiers. - Results must be JSON-serializable: If the expression evaluates to a function, symbol,
undefined, or other non-serializable value, the evaluator returns aTransformError. - Fresh isolate per evaluation: Each call uses a random worker ID to avoid stale cached results.
- Syntax errors: If the expression has a syntax error, the dynamic worker module fails to parse and the error surfaces through the Worker Loader API (caught as a
TransformError).
Testing
# Unit tests
bun test test/
# E2E tests (requires wrangler)
bun run test:e2eLicense
MIT
