{"id":13478560,"url":"https://github.com/cloudflare/workers-rs","last_synced_at":"2026-01-16T17:47:04.353Z","repository":{"id":37426173,"uuid":"394789638","full_name":"cloudflare/workers-rs","owner":"cloudflare","description":"Write Cloudflare Workers in 100% Rust via WebAssembly","archived":false,"fork":false,"pushed_at":"2025-03-10T13:05:32.000Z","size":2171,"stargazers_count":2829,"open_issues_count":162,"forks_count":317,"subscribers_count":39,"default_branch":"main","last_synced_at":"2025-04-22T11:53:32.017Z","etag":null,"topics":["cloudflare","ffi","rust","serverless","webassembly","workers","workers-rs"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cloudflare.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-08-10T21:52:44.000Z","updated_at":"2025-04-21T19:41:49.000Z","dependencies_parsed_at":"2023-09-30T15:29:34.141Z","dependency_job_id":"3068e873-51a3-44be-9f39-472def651971","html_url":"https://github.com/cloudflare/workers-rs","commit_stats":{"total_commits":424,"total_committers":51,"mean_commits":8.313725490196079,"dds":0.6910377358490566,"last_synced_commit":"89d1619d0a64cd7b82436ed291845c9e3bf0f6d3"},"previous_names":[],"tags_count":35,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudflare%2Fworkers-rs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudflare%2Fworkers-rs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudflare%2Fworkers-rs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudflare%2Fworkers-rs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cloudflare","download_url":"https://codeload.github.com/cloudflare/workers-rs/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252590614,"owners_count":21772938,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloudflare","ffi","rust","serverless","webassembly","workers","workers-rs"],"created_at":"2024-07-31T16:01:58.723Z","updated_at":"2026-01-16T17:47:04.340Z","avatar_url":"https://github.com/cloudflare.png","language":"Rust","readme":"![workers-rs](.github/logo.png)\n[![crates.io](https://img.shields.io/crates/v/worker)](https://crates.io/crates/worker)\n[![docs.rs](https://img.shields.io/docsrs/worker)](https://docs.rs/worker)\n\n**Work-in-progress** ergonomic Rust bindings to Cloudflare Workers environment. Write your entire worker in Rust!\n\nRead the [Notes and FAQ](#notes-and-faq)\n\n## Example Usage\n\n```rust\nuse worker::*;\n\n#[event(fetch)]\npub async fn main(mut req: Request, env: Env, _ctx: worker::Context) -\u003e Result\u003cResponse\u003e {\n    console_log!(\n        \"{} {}, located at: {:?}, within: {}\",\n        req.method().to_string(),\n        req.path(),\n        req.cf().unwrap().coordinates().unwrap_or_default(),\n        req.cf().unwrap().region().unwrap_or(\"unknown region\".into())\n    );\n\n    if !matches!(req.method(), Method::Post) {\n        return Response::error(\"Method Not Allowed\", 405);\n    }\n\n    if let Some(file) = req.form_data().await?.get(\"file\") {\n        return match file {\n            FormEntry::File(buf) =\u003e {\n                Response::ok(\u0026format!(\"size = {}\", buf.bytes().await?.len()))\n            }\n            _ =\u003e Response::error(\"`file` part of POST form must be a file\", 400),\n        };\n    }\n\n    Response::error(\"Bad Request\", 400)\n}\n```\n\n## Getting Started\n\nThe project uses [wrangler](https://github.com/cloudflare/workers-sdk/tree/main/packages/wrangler) for running and publishing your Worker.\n\nUse [cargo generate](https://github.com/cargo-generate/cargo-generate) to start from a template:\n\n```bash\ncargo generate cloudflare/workers-rs\n```\n\nThere are several templates to chose from. You should see a new project layout with a `src/lib.rs`. \nStart there! Use any local or remote crates and modules (as long as they compile to the `wasm32-unknown-unknown` target).\n\nOnce you're ready to run your project, run your worker locally:\n\n```bash\nnpx wrangler dev\n```\n\nFinally, go live:\n\n```bash\n# configure your routes, zones \u0026 more in your worker's `wrangler.toml` file\nnpx wrangler deploy\n```\n\nIf you would like to have `wrangler` installed on your machine, see instructions in [wrangler repository](https://github.com/cloudflare/workers-sdk/tree/main/packages/wrangler).\n\n## `http` Feature\n\n`worker` `0.0.21` introduced an `http` feature flag which starts to replace custom types with widely used types from the [`http`](https://docs.rs/http/latest/http/) crate.\n\nThis makes it much easier to use crates which use these standard types such as `axum` and `hyper`. \n\nThis currently does a few things:\n\n1. Introduce `Body`, which implements `http_body::Body` and is a simple wrapper around `web_sys::ReadableStream`. \n1. The `req` argument when using the `[event(fetch)]` macro becomes `http::Request\u003cworker::Body\u003e`.\n1. The expected return type for the fetch handler is `http::Response\u003cB\u003e` where `B` can be any `http_body::Body\u003cData=Bytes\u003e`.\n1. The argument for `Fetcher::fetch_request` is `http::Request\u003cworker::Body\u003e`. \n1. The return type of `Fetcher::fetch_request` is `Result\u003chttp::Response\u003cworker::Body\u003e\u003e`.\n\nThe end result is being able to use frameworks like `axum` directly (see [example](./examples/axum)): \n\n```rust\npub async fn root() -\u003e \u0026'static str {\n    \"Hello Axum!\"\n}\n\nfn router() -\u003e Router {\n    Router::new().route(\"/\", get(root))\n}\n\n#[event(fetch)]\nasync fn fetch(\n    req: HttpRequest,\n    _env: Env,\n    _ctx: Context,\n) -\u003e Result\u003chttp::Response\u003caxum::body::Body\u003e\u003e {\n    Ok(router().call(req).await?)\n}\n```\n\nWe also implement `try_from` between `worker::Request` and `http::Request\u003cworker::Body\u003e`, and between `worker::Response` and `http::Response\u003cworker::Body\u003e`. This allows you to convert your code incrementally if it is tightly coupled to the original types.\n\n### Or use the `Router`:\n\nParameterize routes and access the parameter values from within a handler. Each handler function takes a\n`Request`, and a `RouteContext`. The `RouteContext` has shared data, route params, `Env` bindings, and more.\n\n```rust\nuse serde::{Deserialize, Serialize};\nuse worker::*;\n\n#[event(fetch)]\npub async fn main(req: Request, env: Env, _ctx: worker::Context) -\u003e Result\u003cResponse\u003e {\n\n    // Create an instance of the Router, which can use parameters (/user/:name) or wildcard values\n    // (/file/*pathname). Alternatively, use `Router::with_data(D)` and pass in arbitrary data for\n    // routes to access and share using the `ctx.data()` method.\n    let router = Router::new();\n\n    // useful for JSON APIs\n    #[derive(Deserialize, Serialize)]\n    struct Account {\n        id: u64,\n        // ...\n    }\n    router\n        .get_async(\"/account/:id\", |_req, ctx| async move {\n            if let Some(id) = ctx.param(\"id\") {\n                let accounts = ctx.kv(\"ACCOUNTS\")?;\n                return match accounts.get(id).json::\u003cAccount\u003e().await? {\n                    Some(account) =\u003e Response::from_json(\u0026account),\n                    None =\u003e Response::error(\"Not found\", 404),\n                };\n            }\n\n            Response::error(\"Bad Request\", 400)\n        })\n        // handle files and fields from multipart/form-data requests\n        .post_async(\"/upload\", |mut req, _ctx| async move {\n            let form = req.form_data().await?;\n            if let Some(entry) = form.get(\"file\") {\n                match entry {\n                    FormEntry::File(file) =\u003e {\n                        let bytes = file.bytes().await?;\n                    }\n                    FormEntry::Field(_) =\u003e return Response::error(\"Bad Request\", 400),\n                }\n                // ...\n\n                if let Some(permissions) = form.get(\"permissions\") {\n                    // permissions == \"a,b,c,d\"\n                }\n                // or call `form.get_all(\"permissions\")` if using multiple entries per field\n            }\n\n            Response::error(\"Bad Request\", 400)\n        })\n        // read/write binary data\n        .post_async(\"/echo-bytes\", |mut req, _ctx| async move {\n            let data = req.bytes().await?;\n            if data.len() \u003c 1024 {\n                return Response::error(\"Bad Request\", 400);\n            }\n\n            Response::from_bytes(data)\n        })\n        .run(req, env).await\n}\n```\n\n## Durable Object, KV, Secret, \u0026 Variable Bindings\n\nAll \"bindings\" to your script (Durable Object \u0026 KV Namespaces, Secrets, Variables and Version) are\naccessible from the `env` parameter provided to both the entrypoint (`main` in this example), and to\nthe route handler callback (in the `ctx` argument), if you use the `Router` from the `worker` crate.\n\n```rust\nuse worker::*;\n\n#[event(fetch, respond_with_errors)]\npub async fn main(req: Request, env: Env, _ctx: worker::Context) -\u003e Result\u003cResponse\u003e {\n    utils::set_panic_hook();\n\n    let router = Router::new();\n\n    router\n        .on_async(\"/durable\", |_req, ctx| async move {\n            let namespace = ctx.durable_object(\"CHATROOM\")?;\n            let stub = namespace.id_from_name(\"A\")?.get_stub()?;\n            // `fetch_with_str` requires a valid Url to make request to DO. But we can make one up!\n            stub.fetch_with_str(\"http://fake_url.com/messages\").await\n        })\n        .get(\"/secret\", |_req, ctx| {\n            Response::ok(ctx.secret(\"CF_API_TOKEN\")?.to_string())\n        })\n        .get(\"/var\", |_req, ctx| {\n            Response::ok(ctx.var(\"BUILD_NUMBER\")?.to_string())\n        })\n        .post_async(\"/kv\", |_req, ctx| async move {\n            let kv = ctx.kv(\"SOME_NAMESPACE\")?;\n\n            kv.put(\"key\", \"value\")?.execute().await?;\n\n            Response::empty()\n        })\n        .run(req, env).await\n}\n```\n\nFor more information about how to configure these bindings, see:\n\n- https://developers.cloudflare.com/workers/cli-wrangler/configuration#keys\n- https://developers.cloudflare.com/workers/learning/using-durable-objects#configuring-durable-object-bindings\n- https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/\n\n## Durable Objects\n\n### Define a Durable Object in Rust\n\nTo define a Durable Object using the `worker` crate you need to implement the `DurableObject` trait\non your own struct. Additionally, the `#[durable_object]` attribute macro must be applied to the struct definition.\n\n```rust\nuse worker::{durable_object, DurableObject, State, Env, Result, Request, Response};\n\n#[durable_object]\npub struct Chatroom {\n    users: Vec\u003cUser\u003e,\n    messages: Vec\u003cMessage\u003e,\n    state: State,\n    env: Env, // access `Env` across requests, use inside `fetch`\n}\n\nimpl DurableObject for Chatroom {\n    fn new(state: State, env: Env) -\u003e Self {\n        Self {\n            users: vec![],\n            messages: vec![],\n            state: state,\n            env,\n        }\n    }\n\n    async fn fetch(\u0026self, _req: Request) -\u003e Result\u003cResponse\u003e {\n        // do some work when a worker makes a request to this DO\n        Response::ok(\u0026format!(\"{} active users.\", self.users.len()))\n    }\n}\n```\n\nYou'll need to \"migrate\" your worker script when it's published so that it is aware of this new\nDurable Object, and include a binding in your `wrangler.toml`.\n\n- Include the Durable Object binding type in you `wrangler.toml` file:\n\n```toml\n# ...\n\n[durable_objects]\nbindings = [\n  { name = \"CHATROOM\", class_name = \"Chatroom\" } # the `class_name` uses the Rust struct identifier name\n]\n\n[[migrations]]\ntag = \"v1\" # Should be unique for each entry\nnew_classes = [\"Chatroom\"] # Array of new classes\n```\n\n### SQLite Storage in Durable Objects\n\nDurable Objects can use SQLite for persistent storage, providing a relational database interface. To enable SQLite storage, you need to use `new_sqlite_classes` in your migration and access the SQL storage through `state.storage().sql()`.\n\n```rust\nuse worker::{durable_object, DurableObject, State, Env, Result, Request, Response, SqlStorage};\n\n#[durable_object]\npub struct SqlCounter {\n    sql: SqlStorage,\n}\n\nimpl DurableObject for SqlCounter {\n    fn new(state: State, _env: Env) -\u003e Self {\n        let sql = state.storage().sql();\n        // Create table if it does not exist\n        sql.exec(\"CREATE TABLE IF NOT EXISTS counter(value INTEGER);\", None)\n            .expect(\"create table\");\n        Self { sql }\n    }\n\n    async fn fetch(\u0026self, _req: Request) -\u003e Result\u003cResponse\u003e {\n        #[derive(serde::Deserialize)]\n        struct Row {\n            value: i32,\n        }\n\n        // Read current value\n        let rows: Vec\u003cRow\u003e = self\n            .sql\n            .exec(\"SELECT value FROM counter LIMIT 1;\", None)?\n            .to_array()?;\n        let current = rows.get(0).map(|r| r.value).unwrap_or(0);\n        let next = current + 1;\n\n        // Update counter\n        self.sql.exec(\"DELETE FROM counter;\", None)?;\n        self.sql\n            .exec(\"INSERT INTO counter(value) VALUES (?);\", vec![next.into()])?;\n\n        Response::ok(format!(\"SQL counter is now {}\", next))\n    }\n}\n```\n\nConfigure your `wrangler.toml` to enable SQLite storage:\n\n```toml\n# ...\n\n[durable_objects]\nbindings = [\n  { name = \"SQL_COUNTER\", class_name = \"SqlCounter\" }\n]\n\n[[migrations]]\ntag = \"v1\" # Should be unique for each entry\nnew_sqlite_classes = [\"SqlCounter\"] # Use new_sqlite_classes for SQLite-enabled objects\n```\n\n- For more information about migrating your Durable Object as it changes, see the docs here:\n  https://developers.cloudflare.com/workers/learning/using-durable-objects#durable-object-migrations-in-wranglertoml\n\n## Queues\n\n### Enabling queues\nAs queues are in beta you need to enable the `queue` feature flag.\n\nEnable it by adding it to the worker dependency in your `Cargo.toml`:\n```toml\nworker = {version = \"...\", features = [\"queue\"]}\n```\n\n### Example worker consuming and producing messages:\n```rust\nuse worker::*;\nuse serde::{Deserialize, Serialize};\n#[derive(Serialize, Debug, Clone, Deserialize)]\npub struct MyType {\n    foo: String,\n    bar: u32,\n}\n\n// Consume messages from a queue\n#[event(queue)]\npub async fn main(message_batch: MessageBatch\u003cMyType\u003e, env: Env, _ctx: Context) -\u003e Result\u003c()\u003e {\n    // Get a queue with the binding 'my_queue'\n    let my_queue = env.queue(\"my_queue\")?;\n\n    // Deserialize the message batch\n    let messages = message_batch.messages()?;\n\n    // Loop through the messages\n    for message in messages {\n        // Log the message and meta data\n        console_log!(\n            \"Got message {:?}, with id {} and timestamp: {}\",\n            message.body(),\n            message.id(),\n            message.timestamp().to_string()\n        );\n\n        // Send the message body to the other queue\n        my_queue.send(message.body()).await?;\n\n        // Ack individual message\n        message.ack();\n\n        // Retry individual message\n        message.retry();\n    }\n\n    // Retry all messages\n    message_batch.retry_all();\n    // Ack all messages\n    message_batch.ack_all();\n    Ok(())\n}\n```\nYou'll need to ensure you have the correct bindings in your `wrangler.toml`:\n```toml\n# ...\n[[queues.consumers]]\nqueue = \"myqueueotherqueue\"\nmax_batch_size = 10\nmax_batch_timeout = 30\n\n\n[[queues.producers]]\nqueue = \"myqueue\"\nbinding = \"my_queue\"\n```\n\n## RPC Support\n\n`workers-rs` has experimental support for [Workers RPC](https://developers.cloudflare.com/workers/runtime-apis/rpc/).\nFor now, this relies on JavaScript bindings and may require some manual usage of `wasm-bindgen`. \n\nNot all features of RPC are supported yet (or have not been tested), including:\n- Function arguments and return values\n- Class instances\n- Stub forwarding\n\n### RPC Server\n\nWriting an RPC server with `workers-rs` is relatively simple. Simply export methods using `wasm-bindgen`. These\nwill be automatically detected by `worker-build` and made available to other Workers. See\n[example](./examples/rpc-server).\n\n### RPC Client\n\nCreating types and bindings for invoking another Worker's RPC methods is a bit more involved. You will need to\nwrite more complex `wasm-bindgen` bindings and some boilerplate to make interacting with the RPC methods more\nidiomatic. See [example](./examples/rpc-client/src/calculator.rs).\n\nWith manually written bindings, it should be possible to support non-primitive argument and return types, using\n`serde-wasm-bindgen`. \n\n### Generating Client Bindings\n\nThere are many routes that can be taken to describe RPC interfaces. Under the hood, Workers RPC uses\n[Cap'N Proto](https://capnproto.org/). A possible future direction is for Wasm guests to include Cap'N Proto\nserde support and speak directly to the RPC protocol, bypassing JavaScript. This would likely involve defining \nthe RPC interface in Cap'N Proto schema and generating Rust code from that.\n\nAnother popular interface schema in the WebAssembly community is\n[WIT](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md). This is a lightweight format\ndesigned for the WebAssembly Component model. `workers-rs` includes an **experimental** code generator which \nallows you to describe your RPC interface using WIT and generate JavaScript bindings as shown in the \n[rpc-client example](./examples/rpc-client/wit/calculator.wit). The easiest way to use this code generator is using a [build script](./examples/rpc-client/build.rs) as shown in the example.\nThis code generator is pre-alpha, with no support guarantee, and implemented only for primitive types at this time. \n\n## Testing with Miniflare\n\nIn order to test your Rust worker locally, the best approach is to use\n[Miniflare](https://github.com/cloudflare/miniflare). However, because Miniflare\nis a Node package, you will need to write your end-to-end tests in JavaScript or\nTypeScript in your project. The official documentation for writing tests using\nMiniflare is [available here](https://miniflare.dev). This documentation\nbeing focused on JavaScript / TypeScript codebase, you will need to configure\nas follows to make it work with your Rust-based, WASM-generated worker:\n\n### Step 1: Add Wrangler and Miniflare to your `devDependencies`\n\n```sh\nnpm install --save-dev wrangler miniflare\n```\n\n### Step 2: Build your worker before running the tests\n\nMake sure that your worker is built before running your tests by calling the\nfollowing in your build chain:\n\n```sh\nwrangler deploy --dry-run\n```\n\nBy default, this should build your worker under the `./build/` directory at the\nroot of your project.\n\n### Step 3: Configure your Miniflare instance in your JavaScript / TypeScript tests\n\nTo instantiate the `Miniflare` testing instance in your tests, make sure to\nconfigure its `scriptPath` option to the relative path of where your JavaScript\nworker entrypoint was generated, and its `moduleRules` so that it is able to\nresolve the `*.wasm` file imported from that JavaScript worker:\n\n```js\n// test.mjs\nimport assert from \"node:assert\";\nimport { Miniflare } from \"miniflare\";\n\nconst mf = new Miniflare({\n  scriptPath: \"./build/worker/shim.mjs\",\n  modules: true,\n  modulesRules: [\n    { type: \"CompiledWasm\", include: [\"**/*.wasm\"], fallthrough: true }\n  ]\n});\n\nconst res = await mf.dispatchFetch(\"http://localhost\");\nassert(res.ok);\nassert.strictEqual(await res.text(), \"Hello, World!\");\n```\n\n## D1 Databases\n\n### Enabling D1 databases\nAs D1 databases are in alpha, you'll need to enable the `d1` feature on the `worker` crate.\n\n```toml\nworker = { version = \"x.y.z\", features = [\"d1\"] }\n```\n\n### Example usage\n```rust\nuse worker::*;\n\n#[derive(Deserialize)]\nstruct Thing {\n\tthing_id: String,\n\tdesc: String,\n\tnum: u32,\n}\n\n#[event(fetch, respond_with_errors)]\npub async fn main(request: Request, env: Env, _ctx: Context) -\u003e Result\u003cResponse\u003e {\n\tRouter::new()\n\t\t.get_async(\"/:id\", |_, ctx| async move {\n\t\t\tlet id = ctx.param(\"id\").unwrap()?;\n\t\t\tlet d1 = ctx.env.d1(\"things-db\")?;\n\t\t\tlet statement = d1.prepare(\"SELECT * FROM things WHERE thing_id = ?1\");\n\t\t\tlet query = statement.bind(\u0026[id])?;\n\t\t\tlet result = query.first::\u003cThing\u003e(None).await?;\n\t\t\tmatch result {\n\t\t\t\tSome(thing) =\u003e Response::from_json(\u0026thing),\n\t\t\t\tNone =\u003e Response::error(\"Not found\", 404),\n\t\t\t}\n\t\t})\n\t\t.run(request, env)\n\t\t.await\n}\n```\n\n\n# Notes and FAQ\n\nIt is exciting to see how much is possible with a framework like this, by expanding the options\ndevelopers have when building on top of the Workers platform. However, there is still much to be\ndone. Expect a few rough edges, some unimplemented APIs, and maybe a bug or two here and there. It’s\nworth calling out here that some things that may have worked in your Rust code might not work here -\nit’s all WebAssembly at the end of the day, and if your code or third-party libraries don’t target\n`wasm32-unknown-unknown`, they can’t be used on Workers. Additionally, you’ve got to leave your\nthreaded async runtimes at home; meaning no Tokio or async_std support. However, async/await syntax\nis still available and supported out of the box when you use the `worker` crate.\n\nWe fully intend to support this crate and continue to build out its missing features, but your help\nand feedback is a must. We don’t like to build in a vacuum, and we’re in an incredibly fortunate\nposition to have brilliant customers like you who can help steer us towards an even better product.\n\nSo give it a try, leave some feedback, and star the repo to encourage us to dedicate more time and\nresources to this kind of project.\n\nIf this is interesting to you and you want to help out, we’d be happy to get outside contributors\nstarted. We know there are improvements to be made such as compatibility with popular Rust HTTP\necosystem types (we have an example conversion for [Headers](https://github.com/cloudflare/workers-rs/blob/3d5876a1aca0a649209152d1ffd52dae7bccda87/libworker/src/headers.rs#L131-L167) if you want to make one), implementing additional Web APIs, utility crates,\nand more. In fact, we’re always on the lookout for great engineers, and hiring for many open roles -\nplease [take a look](https://www.cloudflare.com/careers/).\n\n### FAQ\n\n1. Can I deploy a Worker that uses `tokio` or `async_std` runtimes?\n\n- Currently no. All crates in your Worker project must compile to `wasm32-unknown-unknown` target,\n  which is more limited in some ways than targets for x86 and ARM64. However, you should still be able to use runtime-agnostic primitives from those crates such as those from [tokio::sync](https://docs.rs/tokio/latest/tokio/sync/index.html#runtime-compatibility).\n\n2. The `worker` crate doesn't have _X_! Why not?\n\n- Most likely, it should, we just haven't had the time to fully implement it or add a library to\n  wrap the FFI. Please let us know you need a feature by [opening an issue](https://github.com/cloudflare/workers-rs/issues).\n\n3. My bundle size exceeds [Workers size limits](https://developers.cloudflare.com/workers/platform/limits/), what do I do?\n\n- We're working on solutions here, but in the meantime you'll need to minimize the number of crates\n  your code depends on, or strip as much from the `.wasm` binary as possible. Here are some extra\n  steps you can try: https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size\n\n### ⚠️ Caveats\n\n1. Upgrading worker package to version `0.0.18` and higher\n\n- While upgrading your worker to version `0.0.18` an error \"error[E0432]: unresolved import `crate::sys::IoSourceState`\" can appear.\n  In this case, upgrade `package.edition` to `edition = \"2021\"` in `wrangler.toml`\n\n```toml\n[package]\nedition = \"2021\"\n```\n\n# Releasing\n\n1. [Trigger](https://github.com/cloudflare/workers-rs/actions/workflows/create-release-pr.yml) a workflow to create a release PR.\n1. Review version changes and merge PR.\n1. A draft GitHub release will be created. Author release notes and publish when ready.\n1. Crates (`worker-sys`, `worker-macros`, `worker`) will be published automatically. \n\n# Contributing\n\nYour feedback is welcome and appreciated! Please use the issue tracker to talk about potential\nimplementations or make feature requests. If you're interested in making a PR, we suggest opening up\nan issue to talk about the change you'd like to make as early as possible.\n\n## Project Contents\n\n- **worker**: the user-facing crate, with Rust-familiar abstractions over the Rust\u003c-\u003eJS/WebAssembly\n  interop via wrappers and convenience library over the FFI bindings.\n- **worker-sys**: Rust extern \"C\" definitions for FFI compatibility with the Workers JS Runtime.\n- **worker-macros**: exports `event` and `durable_object` macros for wrapping Rust entry point in a\n  `fetch` method of an ES Module, and code generation to create and interact with Durable Objects.\n- **worker-sandbox**: a functioning Cloudflare Worker for testing features and ergonomics.\n- **worker-build**: a cross-platform build command for `workers-rs`-based projects.\n","funding_links":[],"categories":["Rust","📦 Crates","Frameworks \u0026 Libraries"],"sub_categories":["Language Support"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudflare%2Fworkers-rs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcloudflare%2Fworkers-rs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudflare%2Fworkers-rs/lists"}