Giter Site home page Giter Site logo

cloudflare / workers-rs Goto Github PK

View Code? Open in Web Editor NEW
2.4K 2.4K 246.0 2.13 MB

Write Cloudflare Workers in 100% Rust via WebAssembly

License: Apache License 2.0

Rust 95.55% JavaScript 0.24% TypeScript 4.21%
cloudflare ffi rust serverless webassembly workers workers-rs

workers-rs's Introduction

workers-rs crates.io docs.rs

Work-in-progress ergonomic Rust bindings to Cloudflare Workers environment. Write your entire worker in Rust!

Read the Notes and FAQ

Example Usage

use worker::*;

#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
    console_log!(
        "{} {}, located at: {:?}, within: {}",
        req.method().to_string(),
        req.path(),
        req.cf().unwrap().coordinates().unwrap_or_default(),
        req.cf().unwrap().region().unwrap_or("unknown region".into())
    );

    if !matches!(req.method(), Method::Post) {
        return Response::error("Method Not Allowed", 405);
    }

    if let Some(file) = req.form_data().await?.get("file") {
        return match file {
            FormEntry::File(buf) => {
                Response::ok(&format!("size = {}", buf.bytes().await?.len()))
            }
            _ => Response::error("`file` part of POST form must be a file", 400),
        };
    }

    Response::error("Bad Request", 400)
}

Getting Started

The project uses wrangler for running and publishing your Worker.

Use cargo generate to start from a template:

$ cargo generate cloudflare/workers-rs

There are several templates to chose from. You should see a new project layout with a src/lib.rs. Start there! Use any local or remote crates and modules (as long as they compile to the wasm32-unknown-unknown target).

Once you're ready to run your project, run your worker locally:

npx wrangler dev

Finally, go live:

# configure your routes, zones & more in your worker's `wrangler.toml` file
npx wrangler deploy

If you would like to have wrangler installed on your machine, see instructions in wrangler repository.

http Feature

worker 0.0.21 introduced an http feature flag which starts to replace custom types with widely used types from the http crate.

This makes it much easier to use crates which use these standard types such as axum and hyper.

This currently does a few things:

  1. Introduce Body, which implements http_body::Body and is a simple wrapper around web_sys::ReadableStream.
  2. The req argument when using the [event(fetch)] macro becomes http::Request<worker::Body>.
  3. The expected return type for the fetch handler is http::Response<B> where B can be any http_body::Body<Data=Bytes>.
  4. The argument for Fetcher::fetch_request is http::Request<worker::Body>.
  5. The return type of Fetcher::fetch_request is Result<http::Response<worker::Body>>.

The end result is being able to use frameworks like axum directly (see example):

pub async fn root() -> &'static str {
    "Hello Axum!"
}

fn router() -> Router {
    Router::new().route("/", get(root))
}

#[event(fetch)]
async fn fetch(
    req: HttpRequest,
    _env: Env,
    _ctx: Context,
) -> Result<http::Response<axum::body::Body>> {
    Ok(router().call(req).await?)
}

We also implement try_from between worker::Request and http::Request<worker::Body>, and between worker::Response and http::Response<worker::Body>. This allows you to convert your code incrementally if it is tightly coupled to the original types.

Or use the Router:

Parameterize routes and access the parameter values from within a handler. Each handler function takes a Request, and a RouteContext. The RouteContext has shared data, route params, Env bindings, and more.

use serde::{Deserialize, Serialize};
use worker::*;

#[event(fetch)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {

    // Create an instance of the Router, which can use parameters (/user/:name) or wildcard values
    // (/file/*pathname). Alternatively, use `Router::with_data(D)` and pass in arbitrary data for
    // routes to access and share using the `ctx.data()` method.
    let router = Router::new();

    // useful for JSON APIs
    #[derive(Deserialize, Serialize)]
    struct Account {
        id: u64,
        // ...
    }
    router
        .get_async("/account/:id", |_req, ctx| async move {
            if let Some(id) = ctx.param("id") {
                let accounts = ctx.kv("ACCOUNTS")?;
                return match accounts.get(id).json::<Account>().await? {
                    Some(account) => Response::from_json(&account),
                    None => Response::error("Not found", 404),
                };
            }

            Response::error("Bad Request", 400)
        })
        // handle files and fields from multipart/form-data requests
        .post_async("/upload", |mut req, _ctx| async move {
            let form = req.form_data().await?;
            if let Some(entry) = form.get("file") {
                match entry {
                    FormEntry::File(file) => {
                        let bytes = file.bytes().await?;
                    }
                    FormEntry::Field(_) => return Response::error("Bad Request", 400),
                }
                // ...

                if let Some(permissions) = form.get("permissions") {
                    // permissions == "a,b,c,d"
                }
                // or call `form.get_all("permissions")` if using multiple entries per field
            }

            Response::error("Bad Request", 400)
        })
        // read/write binary data
        .post_async("/echo-bytes", |mut req, _ctx| async move {
            let data = req.bytes().await?;
            if data.len() < 1024 {
                return Response::error("Bad Request", 400);
            }

            Response::from_bytes(data)
        })
        .run(req, env).await
}

Durable Object, KV, Secret, & Variable Bindings

All "bindings" to your script (Durable Object & KV Namespaces, Secrets, and Variables) are accessible from the env parameter provided to both the entrypoint (main in this example), and to the route handler callback (in the ctx argument), if you use the Router from the worker crate.

use worker::*;

#[event(fetch, respond_with_errors)]
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
    utils::set_panic_hook();

    let router = Router::new();

    router
        .on_async("/durable", |_req, ctx| async move {
            let namespace = ctx.durable_object("CHATROOM")?;
            let stub = namespace.id_from_name("A")?.get_stub()?;
            // `fetch_with_str` requires a valid Url to make request to DO. But we can make one up!
            stub.fetch_with_str("http://fake_url.com/messages").await
        })
        .get("/secret", |_req, ctx| {
            Response::ok(ctx.secret("CF_API_TOKEN")?.to_string())
        })
        .get("/var", |_req, ctx| {
            Response::ok(ctx.var("BUILD_NUMBER")?.to_string())
        })
        .post_async("/kv", |_req, ctx| async move {
            let kv = ctx.kv("SOME_NAMESPACE")?;

            kv.put("key", "value")?.execute().await?;

            Response::empty()
        })
        .run(req, env).await
}

For more information about how to configure these bindings, see:

Durable Objects

Define a Durable Object in Rust

To define a Durable Object using the worker crate you need to implement the DurableObject trait on your own struct. Additionally, the #[durable_object] attribute macro must be applied to both your struct definition and the trait impl block for it.

use worker::*;

#[durable_object]
pub struct Chatroom {
    users: Vec<User>,
    messages: Vec<Message>,
    state: State,
    env: Env, // access `Env` across requests, use inside `fetch`
}

#[durable_object]
impl DurableObject for Chatroom {
    fn new(state: State, env: Env) -> Self {
        Self {
            users: vec![],
            messages: vec![],
            state: state,
            env,
        }
    }

    async fn fetch(&mut self, _req: Request) -> Result<Response> {
        // do some work when a worker makes a request to this DO
        Response::ok(&format!("{} active users.", self.users.len()))
    }
}

You'll need to "migrate" your worker script when it's published so that it is aware of this new Durable Object, and include a binding in your wrangler.toml.

  • Include the Durable Object binding type in you wrangler.toml file:
# ...

[durable_objects]
bindings = [
  { name = "CHATROOM", class_name = "Chatroom" } # the `class_name` uses the Rust struct identifier name
]

[[migrations]]
tag = "v1" # Should be unique for each entry
new_classes = ["Chatroom"] # Array of new classes

Queues

Enabling queues

As queues are in beta you need to enable the queue feature flag.

Enable it by adding it to the worker dependency in your Cargo.toml:

worker = {version = "...", features = ["queue"]}

Example worker consuming and producing messages:

use worker::*;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Debug, Clone, Deserialize)]
pub struct MyType {
    foo: String,
    bar: u32,
}

// Consume messages from a queue
#[event(queue)]
pub async fn main(message_batch: MessageBatch<MyType>, env: Env, _ctx: Context) -> Result<()> {
    // Get a queue with the binding 'my_queue'
    let my_queue = env.queue("my_queue")?;

    // Deserialize the message batch
    let messages = message_batch.messages()?;

    // Loop through the messages
    for message in messages {
        // Log the message and meta data
        console_log!(
            "Got message {:?}, with id {} and timestamp: {}",
            message.body(),
            message.id(),
            message.timestamp().to_string()
        );

        // Send the message body to the other queue
        my_queue.send(message.body()).await?;

        // Ack individual message
        message.ack();

        // Retry individual message
        message.retry();
    }

    // Retry all messages
    message_batch.retry_all();
    // Ack all messages
    message_batch.ack_all();
    Ok(())
}

You'll need to ensure you have the correct bindings in your wrangler.toml:

# ...
[[queues.consumers]]
queue = "myqueueotherqueue"
max_batch_size = 10
max_batch_timeout = 30


[[queues.producers]]
queue = "myqueue"
binding = "my_queue"

RPC Support

workers-rs has experimental support for Workers RPC. For now, this relies on JavaScript bindings and may require some manual usage of wasm-bindgen.

Not all features of RPC are supported yet (or have not been tested), including:

  • Function arguments and return values
  • Class instances
  • Stub forwarding

RPC Server

Writing an RPC server with workers-rs is relatively simple. Simply export methods using wasm-bindgen. These will be automatically detected by worker-build and made available to other Workers. See example.

RPC Client

Creating types and bindings for invoking another Worker's RPC methods is a bit more involved. You will need to write more complex wasm-bindgen bindings and some boilerplate to make interacting with the RPC methods more idiomatic. See example.

With manually written bindings, it should be possible to support non-primitive argument and return types, using serde-wasm-bindgen.

Generating Client Bindings

There are many routes that can be taken to describe RPC interfaces. Under the hood, Workers RPC uses Cap'N Proto. A possible future direction is for Wasm guests to include Cap'N Proto serde support and speak directly to the RPC protocol, bypassing JavaScript. This would likely involve defining the RPC interface in Cap'N Proto schema and generating Rust code from that.

Another popular interface schema in the WebAssembly community is WIT. This is a lightweight format designed for the WebAssembly Component model. workers-rs includes an experimental code generator which allows you to describe your RPC interface using WIT and generate JavaScript bindings as shown in the rpc-client example. The easiest way to use this code generator is using a build script as shown in the example. This code generator is pre-alpha, with no support guarantee, and implemented only for primitive types at this time.

Testing with Miniflare

In order to test your Rust worker locally, the best approach is to use Miniflare. However, because Miniflare is a Node package, you will need to write your end-to-end tests in JavaScript or TypeScript in your project. The official documentation for writing tests using Miniflare is available here. This documentation being focused on JavaScript / TypeScript codebase, you will need to configure as follows to make it work with your Rust-based, WASM-generated worker:

Step 1: Add Wrangler and Miniflare to your devDependencies

npm install --save-dev wrangler miniflare

Step 2: Build your worker before running the tests

Make sure that your worker is built before running your tests by calling the following in your build chain:

wrangler deploy --dry-run

By default, this should build your worker under the ./build/ directory at the root of your project.

Step 3: Configure your Miniflare instance in your JavaScript / TypeScript tests

To instantiate the Miniflare testing instance in your tests, make sure to configure its scriptPath option to the relative path of where your JavaScript worker entrypoint was generated, and its moduleRules so that it is able to resolve the *.wasm file imported from that JavaScript worker:

// test.mjs
import assert from "node:assert";
import { Miniflare } from "miniflare";

const mf = new Miniflare({
  scriptPath: "./build/worker/shim.mjs",
  modules: true,
  modulesRules: [
    { type: "CompiledWasm", include: ["**/*.wasm"], fallthrough: true }
  ]
});

const res = await mf.dispatchFetch("http://localhost");
assert(res.ok);
assert.strictEqual(await res.text(), "Hello, World!");

D1 Databases

Enabling D1 databases

As D1 databases are in alpha, you'll need to enable the d1 feature on the worker crate.

worker = { version = "x.y.z", features = ["d1"] }

Example usage

use worker::*;

#[derive(Deserialize)]
struct Thing {
	thing_id: String,
	desc: String,
	num: u32,
}

#[event(fetch, respond_with_errors)]
pub async fn main(request: Request, env: Env, _ctx: Context) -> Result<Response> {
	Router::new()
		.get_async("/:id", |_, ctx| async move {
			let id = ctx.param("id").unwrap()?;
			let d1 = ctx.env.d1("things-db")?;
			let statement = d1.prepare("SELECT * FROM things WHERE thing_id = ?1");
			let query = statement.bind(&[id])?;
			let result = query.first::<Thing>(None).await?;
			match result {
				Some(thing) => Response::from_json(&thing),
				None => Response::error("Not found", 404),
			}
		})
		.run(request, env)
		.await
}

Notes and FAQ

It is exciting to see how much is possible with a framework like this, by expanding the options developers have when building on top of the Workers platform. However, there is still much to be done. Expect a few rough edges, some unimplemented APIs, and maybe a bug or two here and there. Itโ€™s worth calling out here that some things that may have worked in your Rust code might not work here - itโ€™s all WebAssembly at the end of the day, and if your code or third-party libraries donโ€™t target wasm32-unknown-unknown, they canโ€™t be used on Workers. Additionally, youโ€™ve got to leave your threaded async runtimes at home; meaning no Tokio or async_std support. However, async/await syntax is still available and supported out of the box when you use the worker crate.

We fully intend to support this crate and continue to build out its missing features, but your help and feedback is a must. We donโ€™t like to build in a vacuum, and weโ€™re in an incredibly fortunate position to have brilliant customers like you who can help steer us towards an even better product.

So give it a try, leave some feedback, and star the repo to encourage us to dedicate more time and resources to this kind of project.

If this is interesting to you and you want to help out, weโ€™d be happy to get outside contributors started. We know there are improvements to be made such as compatibility with popular Rust HTTP ecosystem types (we have an example conversion for Headers if you want to make one), implementing additional Web APIs, utility crates, and more. In fact, weโ€™re always on the lookout for great engineers, and hiring for many open roles - please take a look.

FAQ

  1. Can I deploy a Worker that uses tokio or async_std runtimes?
  • Currently no. All crates in your Worker project must compile to wasm32-unknown-unknown target, which is more limited in some ways than targets for x86 and ARM64.
  1. The worker crate doesn't have X! Why not?
  • Most likely, it should, we just haven't had the time to fully implement it or add a library to wrap the FFI. Please let us know you need a feature by opening an issue.
  1. My bundle size exceeds Workers size limits, what do I do?

โš ๏ธ Caveats

  1. Upgrading worker package to version 0.0.18 and higher
  • While upgrading your worker to version 0.0.18 an error "error[E0432]: unresolved import crate::sys::IoSourceState" can appear. In this case, upgrade package.edition to edition = "2021" in wrangler.toml
[package]
edition = "2021"

Releasing

  1. Trigger a workflow to create a release PR.
  2. Review version changes and merge PR.
  3. A draft GitHub release will be created. Author release notes and publish when ready.
  4. Crates (worker-sys, worker-macros, worker) will be published automatically.

Contributing

Your feedback is welcome and appreciated! Please use the issue tracker to talk about potential implementations or make feature requests. If you're interested in making a PR, we suggest opening up an issue to talk about the change you'd like to make as early as possible.

Project Contents

  • worker: the user-facing crate, with Rust-familiar abstractions over the Rust<->JS/WebAssembly interop via wrappers and convenience library over the FFI bindings.
  • worker-sys: Rust extern "C" definitions for FFI compatibility with the Workers JS Runtime.
  • worker-macros: exports event and durable_object macros for wrapping Rust entry point in a fetch method of an ES Module, and code generation to create and interact with Durable Objects.
  • worker-sandbox: a functioning Cloudflare Worker for testing features and ergonomics.
  • worker-build: a cross-platform build command for workers-rs-based projects.

workers-rs's People

Contributors

aseure avatar avsaase avatar bastidood avatar bytealex avatar cmoore-darwinium avatar dakom avatar dbw9580 avatar dependabot[bot] avatar fisherdarling avatar fkettelhoit avatar flareline avatar github-actions[bot] avatar jakubadamw avatar jasper-bekkers avatar jdon avatar jyn514 avatar kakapio avatar kflansburg avatar kiannh avatar kpcyrd avatar leoorshansky avatar mendess avatar nilslice avatar ocsfrank avatar sebastiaanyn avatar slester avatar voidstar0 avatar witchof0x20 avatar xtuc avatar zebp avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

workers-rs's Issues

Mutable Request/Response in place

Hi! ๐Ÿ‘‹

I'm currently trying to port my Cloudflare worker from TypeScript to Rust. I'm using a middleware pattern where I want pass a worker::Request to various functions that will transform them (e.g. transform the URL, strip certain headers, etc.) before sending to the origin. Similarly, I want to transform the worker::Response through multiple middlewares before sending it back to the end-user.

As far as I can, as the fields of both objects are private, I can only access those values through functions (e.g. req.path(), req.headers()), which means I can't mutate those values in place.

Is there any plan to support this type of pattern in Rust? Or is there anything I'm missing that would allow me to do that?

Use `log` instead of `console_log!`

This will mean all the logging from libraries that developers don't control will still show up in the JS console. We could implement it by registering a log handler which outputs to the JS console.

could not find `worker-build` in registry `crates-io` with version `*`

I'm quite new to Rust development so it's entirely possible I'm doing something obviously wrong but I've tried pulling down this example and running according to the README and am getting this error. Anything obvious I need to adjust? Is my local env not setup appropriately?

% wrangler build                       
๐ŸŒ€  Running cargo install -q worker-build && worker-build --release
error: could not find `worker-build` in registry `crates-io` with version `*`
Error: Build failed! Status Code: 101

wrangler build: "Error: linker `cc` not found"

Steps to Reproduce Error:

  1. Fresh install of Ubuntu 21.10 (codename: impish, followed by update and upgrade )
  2. Installed NPM 8.1.0
  3. Installed Node v16.13.0
  4. Installed Wrangler 1.19.5
  5. Installed Rustup 1.24.3 / Cargo 1.56.0
  6. Wrangler generate --type=rust project_name

  7. cd project_name

  8. wrangler build

Following error displays:

Running cargo install -q worker-build && worker-build --release
error: linker `cc` not found
  |
  = note: No such file or directory (os error 2)

error: failed to compile `worker-build v0.0.3`, intermediate artifacts can be found at `/tmp/cargo-installarcZxR`

Caused by:
  could not compile `anyhow` due to previous error
Error: Build failed! Status Code: 101

Attempting to Google the error doesn't result in anything substantial to work on.

Why not use `http`'s StatusCode to represent status code?

It seems weird for me that functions like Response::error still takes a u16 as the response status code, and impl Into<String> as the response description, since the crate http has already been a dep. So I'm expecting to have sigs like

pub fn error(code: http::StatusCode) -> Result<Self>;
pub fn status_code(&self) -> http::StatusCode;

Wrapping around http::StatusCode to have custom types like ErrorStatusCode will also allow us us to lift the following error to type-level & reduce runtime cost (well, maybe just a little). And I believe this is an error that should not be thrown in runtime.

if !(400..=599).contains(&status) {
    return Err(Error::Internal(
        "error status codes must be in the 400-599 range! see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status for more".into(),
    ));
}

Enable files to be set as value in `FormData`

It's quite strange to have FormData::get returning FormEntry with FormData::set and FormData::append accepting only &str. It would be great if we can insert File or something like it directly into FormData.

Custom status code support

I'd like to be able to set arbitrary status codes (201, 202, 204, 302, etc.) and currently, that is impossible because Response::status_code is private and there is no method to create a custom response or modify a response like with_status(self, code: u16) (similar to with_headers.)

This will go away with #13.

Confusing error when trying to define both a durable worker and a fetch handler in the same file

Hi there,

I'm trying to play around with Durable Workers, so I copied in the Chatroom example from the README into my pre-existing lib.rs (which was mostly the default example after running wrangler generate).

My entire src/lib.rs file looks like this:

use worker::*;

#[durable_object]
pub struct Chatroom {
    state: State,
    env: Env, // access `Env` across requests, use inside `fetch`
}

#[durable_object]
impl DurableObject for Chatroom {
    fn new(state: State, env: Env) -> Self {
        Self { state: state, env }
    }

    async fn fetch(&mut self, _req: Request) -> Result<Response> {
        todo!()
    }
}

#[event(fetch)]
pub async fn main(req: Request, env: Env) -> Result<Response> {
    Response::error("Bad Request", 400)
}

Running wrangler build, I get this error:

[INFO]: Compiling to Wasm...
   Compiling project_name v0.1.0 (/home/achin/tmp/70/project_name)
error[E0428]: the name `__WASM_BINDGEN_GENERATED_0bdc7e4367fd39cb` is defined multiple times
  --> src/lib.rs:20:1
   |
3  | #[durable_object]
   | ----------------- previous definition of the value `__WASM_BINDGEN_GENERATED_0bdc7e4367fd39cb` here
...
20 | #[event(fetch)]
   | ^^^^^^^^^^^^^^^ `__WASM_BINDGEN_GENERATED_0bdc7e4367fd39cb` redefined here
   |
   = note: `__WASM_BINDGEN_GENERATED_0bdc7e4367fd39cb` must be defined only once in the value namespace of this module
   = note: this error originates in the attribute macro `event` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0428`.
error: could not compile `project_name` due to previous error
Error: Compiling your crate to WebAssembly failed
Caused by: failed to execute `cargo build`: exited with exit status: 101
  full command: "cargo" "build" "--lib" "--release" "--target" "wasm32-unknown-unknown"
Error: wasm-pack exited with status exit status: 1
Error: Build failed! Status Code: 1

I think it's illegal to define a fetch handler and also a durable object in the same file. Is that true?
If so, I would be great if this could be detected better (better error message), and even greater if the README stated this a bit more clearly :)

Version info:

rustc: rustc 1.58.0-nightly (dd549dcab 2021-11-25)
wrangler: wrangler 1.19.5
workers-rs: 0.0.7

Make `worker::Error` public

Even if we don't make it constructable, it would still be nice to make the type itself public so you can see what traits are implemented, something like this:

pub struct Error(ErrorInner);
enum ErrorInner { ... }

`Response::error` is a footgun

Response::error("Error", 200) is perfectly valid and is not correct at all.

Actually, looking at the implementation it would be fine if we just renamed this to Response::with_status. @nilslice what do you think? We could still have Response::internal_error or Response::request_error convenience wrappers if you think they're helpful.

(This may become a moot point after implementing #13.)

Question about KVStore's put vs put_bytes

When trying to use KVStore's put to store bytes, I get this runtime error:

let kv = ctx.kv("UPLOAD")?;
kv.put("test", "test".as_bytes())?.execute().await?;
TypeError: KV put() accepts only strings, ArrayBuffers, ArrayBufferViews, and ReadableStreams as values.

Two bits of confusion on this:

  • The signature for put is:
pub fn put<T: ToRawKvValue>(
    &self,
    name: &str,
    value: T
) -> Result<PutOptionsBuilder, KvError>;

And there is an impl ToRawKvValue for [u8], so this code compiles. But if this function doesn't support byte slices, then why does it accept them?

  • Why can't put accept bytes? Stated another way: why is there both a put() and put_bytes() ?

Thanks!

Returning JSON

Discussed in #70

Originally posted by vorcigernix October 16, 2021
I am trying to follow example code from the readme. For some reason it returns

KvError::Serialization: expected value at line 1 column 1
panicked at 'KvError::Serialization: expected value at line 1 column 1', src/lib.rs:20:1

where error line is where I serialize object to json like
Some(jmeniny) => Response::from_json(&jmeniny.as_json::<Jmeniny>()?),
When I format the response as a string, it works fine:
Some(jmeniny) => Response::ok(&format!("response: {:?}", &jmeniny)),
results in
response: KvValue("Hynek")
It seems that from_json use serde internally, so it should work fine. Or I am overlooking something basic in KV.
Jmeniny struct is like this:

#[derive(Deserialize, Serialize)]
struct Jmeniny {
    name: String,
}

I think that it is some stupid mistake, but I'll appreciate any help.

Hello World example

Current examples in https://github.com/cloudflare/workers-rs/blame/main/README.md#L9, https://blog.cloudflare.com/workers-rust-sdk/ and project generated by wrangler generate --type=rust all provide some advanced usage of using router, console_log, DO, POST handling. None of them worked out of the box for me.

For those whose just start out, like me, would it be possible to provide and example worker that just returns Hello world string so we could start from there? Thanks a lot!

Usage of Router inside a DurableObject: need to move env, which is required to be a reference

Hi,

Apologies in advance if I'm missing something obvious here, I'm new to Rust. I'm trying to use Router in a #[durable_object]s fetch method like so:

use worker::*;

#[durable_object]
pub struct Chatroom {
    messages: Vec<String>,
    state: State,
    env: Env,
}

#[durable_object]
impl DurableObject for Chatroom {
    fn new(state: State, env: Env) -> Self {
        Self {
            messages: vec![],
            state,
            env,
        }
    }

    async fn fetch(&mut self, req: Request) -> worker::Result<Response> {
        let router = Router::with_data(&self.messages);

        router
            .get("/", |_req, context| {
                Response::ok(&format!("{} messages", context.data().len()))
            })
            .run(req, self.env)
            .await
    }
}

This fails with the error:

error[E0507]: cannot move out of `self.env` which is behind a mutable reference
  --> src/chatroom.rs:27:23
   |
27 |             .run(req, self.env)
   |                       ^^^^^^^^ move occurs because `self.env` has type `worker::Env`, which does not implement the `Copy` trait

This makes sense to me, as Router#run takes ownership of it's env argument, and since I'm taking a &mut self as a reference I can't move self.env. The #[durable_object] macro forces fetch to be &mut self so I can only give references of env. I can't work out how you would use Router inside it?

Workers Sites support

Hi ๐Ÿ‘‹

I had tried to re-implement the Workers Sites using Rust, but I was stuck by KV access.

As far as I know, the kv-asset-handler package uses the STATIC_CONTENT_MANIFEST variable from global context generated by wrangler. Am I right?

If it is, can you provide some ways to accessing the manifest or Rust version of getAssetFromKV function?

add GitHub Actions to make it easier for contributors and maintainers

Would suggest adding GitHub Actions (clippy and build check) which runs on every PR/merge to main that makes it easier for maintainers to review Pull Requests as well as for contributors to improve their coding style when they contribute to this repository.

Would be happy to raise a PR for the same ๐Ÿ˜„

Questions/Feedback after using this crate for Cloudflare Internship Hiring Assignment

Hi,
In the last week I applied to and attempted the Cloudflare Internship Hiring Assignment https://apply.cloudflareworkers.com/ with this crate. I would say I completed a majority of it but at some point I was too unsatisfied with how my code looked so I switched to using JavaScript for my worker. I had some general questions about what the expected/idiomatic way of doing certain things were for which I couldn't find any documentation/examples for -

  • What would be the idiomatic way to implement Error Handling in my worker ? My understanding of how it is setup currently is that any error from my handler would be converted to a worker::Error which by default causes an error response of some sort with an HTML body. If I were developing a REST API with JSON responses what would be the cleanest way to convert my errors to the appropriate JSON responses with a unique error code ?
  • Maybe related to the above - there doesn't seem to be any support for adding middleware to my Router. This proved troublesome when I had to add CORS headers to all my responses or to selectively authenticate some of my handlers. Is the expectation to use one of the existing crates in the ecosystem to handle this ? Or is it just something thats on the roadmap.

I hope my message doesn't come of as too aggressive. I realize that Rust support is in very early stages and would be happy to contribute to this crates code / documentation / examples etc.

RequestInit::new() is hanging

Hi!

It seems like something's wrong with RequestInit::new(). Here is the simple code, it creates empty RequestInit and then returns empty response. And it stucks.

use worker::*;

mod utils;

fn log_request(req: &Request) {
    console_log!(
        "{} - [{}], located at: {:?}, within: {}",
        Date::now().to_string(),
        req.path(),
        req.cf().coordinates().unwrap_or_default(),
        req.cf().region().unwrap_or("unknown region".into())
    );
}

#[event(fetch)]
pub async fn main(req: Request, env: Env) -> Result<Response> {
    log_request(&req);

    utils::set_panic_hook();

    let _init = RequestInit::new(); // the code stucks here

    Response::empty()
}

And here's the logs from wrangler dev:

๐Ÿ‘‚  Listening on http://127.0.0.1:8787
Sun Sep 12 2021 12:06:47 GMT+0000 (Coordinated Universal Time) - [/], located at: (59.8983, 30.2618), within: St.-Petersburg
Error: Worker exceeded CPU time limit. at line 0, col -2
{
  "exceptionDetails": {
    "columnNumber": -2,
    "exception": {
      "className": "Error",
      "description": "Error: Worker exceeded CPU time limit.",
      "preview": {
        "description": "Error: Worker exceeded CPU time limit.",
        "entries": null,
        "overflow": false,
        "properties": [],
        "subtype": "error",
        "type": "object"
      },
      "subtype": "error",
      "type": "object",
      "value": null
    },
    "lineNumber": 0,
    "text": "Uncaught (in response)",
    "url": "undefined"
  },
  "timestamp": 1631448407649
}
[2021-09-12 15:06:46] GET response-bot-dev.idebugger.workers.dev/ HTTP/1.1 503 Service Unavailable

`with_headers` on `Response` does nothing when inner is `ResponseBody::Stream`

A response from Fetch can't be modified:

Fetch::Request(request).send().await?.with_headers(Headers::new())

The code compiles but doesn't actually update the headers.

My workaround:

use wasm_bindgen::{prelude::*, JsCast};
use worker::worker_sys;

#[wasm_bindgen]
extern "C" {
    pub type Response;

    #[wasm_bindgen(catch, constructor, js_class=Response)]
    pub fn new_with_opt_stream_and_init(
        body: Option<web_sys::ReadableStream>,
        init: &web_sys::ResponseInit,
    ) -> std::result::Result<Response, JsValue>;
}

impl Response {
    pub fn dup(response: worker::Response, headers: &mut worker::Headers) -> crate::Result<worker::Response> {
        let mut response_init = web_sys::ResponseInit::new();
        response_init.headers(&mut headers.0);

        let response: worker_sys::Response = response.into();
        let response = Response::new_with_opt_stream_and_init(response.body(), &response_init)?;
        let response = response.unchecked_into::<worker_sys::Response>();
        let body = worker::ResponseBody::Stream(response);

        Ok(worker::Response::from_body(body)?)
    }
}

Reorganize before an official release

The naming of the crates in this project are a bit everywhere. It'd be nice to consolidate the names of everything and reorganize before committing to any crates.io releases.

As it stands there are currently five crates each with wildly different names:

  • edgeworker-sys
  • worker-rs-macros
  • libworker
  • worker
  • rust-worker-build

libworker could be dropped in favor of just defining everything in worker instead of using worker to re-export everything. Currently libworker is separate so it can be imported by worker-rs-macros however the macros crate never uses it so it's an unnecessary dependency. With the base name "worker", we can easily turn this into four separate easily identifiable crates.

  • worker-sys - For low-level FFI bindings
  • worker-macros - For macros
  • worker - For bringing everything together with convenience types
  • worker-build - As a build command

On top of this, it may be favorable to drop the rust-sandbox crate. Instead of testing or experimenting in a sandbox, things normally written in the sandbox would be better if written as an example in the worker crate. Then things written as examples for testing during development can be referenced later by users.

KVStore corrupts binary data

I'm trying to store binary data in the KV, but the data appears to getting corrupted. Consider this code:

.get_async("/test", |req, ctx| async move {
            let kv = ctx.kv("UPLOAD")?;
            let bytes = [0xd2, 0xda, 0x1e, 0x77, 0x3c, 0x50, 0xd9, 0x35, 0x9a, 0x89, 0xd5, 0x53, 0xc3, 0x81, 0x6d, 0x20];
            kv.put_bytes("test", &bytes)?.execute().await?;

            let bytes_from_kv = kv.get("test").await?.unwrap();
            assert_eq!(&bytes, bytes_from_kv.as_bytes());

            Response::empty()
        })

Trying to run this yields this assertion failure:

panicked at 'assertion failed: `(left == right)`
  left: `[210, 218, 30, 119, 60, 80, 217, 53, 154, 137, 213, 83, 195, 129, 109, 32]`,
 right: `[239, 191, 189, 239, 191, 189, 30, 119, 60, 80, 239, 191, 189, 53, 239, 191, 189, 239, 191, 189, 239, 191, 189, 83, 195, 129, 109, 32]`', src/lib.rs:56:13

However when I download this value via the https://dash.cloudflare.com/, the data is correct (so it seems like the corruption is happening during the fetch on the rust side)

Version info

  • worker-rs version v0.0.7
  • wrangler version 1.19.6
  • rustc version 1.59.0-nightly (7abab1efb 2021-12-17)

Feature request: No matching route closure

It would be really helpful to be able to pass in a closure to the router that is run when there is no matching route. In my use case, I'd like to be able to trigger logging and analytics. I'm using the worker as a gateway and I'd like to be sure that all the requests are being handled.

Can't run wrker-build, assumes ~/.cargo/bin is in PATH and fails otherwise

๐Ÿ› Bug report

Describe the bug

This is just developer friction / newbie trap.

Wrangler 1.19.2 installed by building via cargo.

Reproduce the bug

With the template Rust workers demo from https://blog.cloudflare.com/workers-rust-sdk/

$ wrangler dev 
๐ŸŒ€  Running cargo install -q worker-build && worker-build --release
sh: worker-build: command not found
Error: Build failed! Status Code: 127

$ PATH="$PATH:$HOME/.cargo/bin"
$ wrangler dev 
๐ŸŒ€  Running cargo install -q worker-build && worker-build --release
Installing wasm-pack...

Expected behavior

Ideally:

  1. Don't rely on user $PATH.
  2. Don't even attempt to install things on the fly automatically. (My opinion.)

Environment and versions

Fill out the following information about your environment.

  • operating system: Linux, NixOS 21.05
  • output of wrangler -V: wrangler 1.19.2
  • output of node -v: node: command not found
  • content of wrangler.toml: unedited from template

`#[event(fetch)]` should name the generated function after the input

This error is confusing:

error[E0428]: the name `fetch` is defined multiple times
  --> rust-sandbox/src/lib.rs:38:1
   |
36 | fn fetch() {}
   | ---------- previous definition of the value `fetch` here
37 | 
38 | #[event(fetch, respond_with_errors)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `fetch` redefined here
   |
   = note: `fetch` must be defined only once in the value namespace of this module
   = note: this error originates in the attribute macro `event` (in Nightly builds, run with -Z macro-backtrace for more info)

It would be better to name the generated function whatever it was originally (in this case, main).

Unprivate or move `worker::response::ResponseBody`

At the moment you can't create custom responses without worker::response::ResponseBody which is in a private module.

I think the best course of action would be to move worker::response::ResponseBody to worker::ResponseBody but as I am pretty new to Rust i'll leave this up to the maintainers.

Consider making Router Data not an Option

It seems the only reason Router and RouteContext data method return an Option<Data> is because you've decided to implement Default for a router. Nothing seems to use that default. Could I convince you to remove the Default and make data be not optional? That would remove a .unwrap() from practically every handler of every app using data.

For inspiration/justification/examples:

Have Response implement Clone

Currently, it appears Responses received from fetching are immutable. It would be ideal if the Response could be Cloned, modified, and then sent to a recipient.

The use case would be instances where the worker is authorized to make a request, but the authorization information in the request should not be shared with the final recipient.

If I am wrong or there is a workaround, please let me know.

What's the release cadence?

It has been two months since the last release and master contains fixes that I need.

Is there a reason to not release patch versions after every merge?

Responses being mangled

Iโ€™m experimenting with using the async-graphql crate inside a worker. When I use wee_alloc as the global allocator, the first few bytes of each response from the worker are being consistently โ€˜mangledโ€™ for some reason. Here is a repository with a minimal worker which replicates the issue: https://github.com/thomasfoster96/wealloc-issue and here is the worker in action: https://wealloc-issue.thomasfoster.workers.dev/.

Here I what I would expect my worker to produce:

{"data":null,"errors":[{"message":"Bad Request"}]}

Instead, this is what I get:

๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝta":null,"errors":[{"message":"Bad Request"}]}

The bytes are mangled the same way on every request. Calling serde_json::to_string on the GraphQL request and then logging it with console_log! produces the correct output โ€” this is only happening when I use Response::from_json or similar methods.

Creating a response body in other ways produces similar results. For example, using serde_json::to_string produces the correct output but using Response::from_html then mangles it, as does using serde_json::to_vec and then Response::from_bytes.

(Apologies if any of this is unclear, Iโ€™m very much a beginner with Rust and Cloudflare Workers)

Cache

How do I access Cloudflare cache?

re-export worker_kv::KvStore type

The type for KvStore is not available from the worker crate. I need to add the worker_kv dependency to be able to get the KvStore type. It might be a good idea to re-export the KvStore type. This will insure that the KvStore type used in one's project is the same type being used by the worker crate.

Auto-generated bindings

Now that @mrbbot has added support for doing this in TypeScript (cloudflare/workers-types#112), we have the infrastructure for doing this for Rust too. He actually did both but the Rust one just hasn't been integrated. Not sure what actually needs to be done here but opening this issue just to track it.

gRPC support

This is a probably a very big task, since JS Workers don't seem to have an existing library or a nice way to do it. There are some existing crates like tonic which support gRPC but these likely wouldn't work without a lot of changes. gRPC also has streams, which some platforms don't support while still supporting unary requests (possibly for similar reasons that might prevent Workers from supporting them). gRPC-web might be easier, but do not currently support streams.

Another possible issue is Cloudflare's support for gRPC since HTTP/2 isn't just enabled for everything by default and Workers might not currently support HTTP/2(?).

API Improvement ideas

Hello,

I've been working with the workers-rs api recently and noticed it has got a few rough edges in my opinion.

I've thought how that could be improved thus I am suggesting this here, willing to create a PR once the idea has been approved.

  1. Responses
  • The constructors of responses return a Result, which forces the user to call map to access response functions.

=> Should be the Self-Object not conatained in a Result, user should call Ok(Response) later.
I am aware of the error-possibility from set_headers, those can be wrapped into an Response-internal error object which is unwrapped in the final error handling. (See next point)

  1. Error handling using ? operator
  • Currently the router takes a function with a worker::Result, and everything which is not Ok() will be rendered as error with panic. (CF error page)
  • It's cool, that the worker::Error type is a thiserror:Error derive and can be constructed using the From trait from basically every possible Rest error, but it's still missing flexibility.

=>Make an error handling function built into the router which
a) supports any generic error type with a "FromWorkerError" trait
b) has the signature set_error_handler<T: FromWorkerError, E: 'static + Copy + Fn(T) -> Response>(fun: E).
This function will unwrap any possible error into a custom error message, if desired. The default error handler can just be a panic!().

For reference: My current "workaround" code
pub struct ResponseWrapper {
  body: Option<Vec<u8>>,
  headers: worker::Headers,
  status: u16,
  error: Option<worker::Error>,
}

impl ResponseWrapper {
  pub fn json<T: Serialize>(data: &T) -> Result<Self, serde_json::Error> {
      let data = serde_json::to_vec(data)?;
      Ok(Self::bytes(data)
          .append_header("content-type", "application/json"))
  }

  pub fn bytes(data: Vec<u8>) -> Self {
      Self {
          body: Some(data),
          headers: worker::Headers::new(),
          status: 200,
          error: None,
      }
  }

  pub fn text<S: Into<String>>(data: S) -> Self {
      Self {
          body: Some(data.into().into_bytes()),
          headers: worker::Headers::new(),
          status: 200,
          error: None,
      }
  }

  pub fn empty() -> Self {
      Self {
          body: None,
          headers: worker::Headers::new(),
          status: 204,
          error: None,
      }
  }

  pub fn with_status(mut self, status: u16) -> Self {
      self.status = status;
      self
  }

  pub fn append_header(mut self, key: &str, val: &str) -> Self {
      if let Some(err) = self.headers.append(key, val).err() {
          self.error = Some(err);
      }
      self
  }
}

impl Into<worker::Result<worker::Response>> for ResponseWrapper {
  fn into(self) -> worker::Result<Response> {
      if let Some(err) = self.error {
          Err(err)
      } else {
          let headers = self.headers;
          let status = self.status;
          if let Some(data) = self.body {
              Response::from_body(ResponseBody::Body(data))
                  .map(|resp| resp.with_headers(headers))
                  .map(|resp| resp.with_status(status))
          } else {
              Response::from_body(ResponseBody::Empty)
                  .map(|resp| resp.with_headers(headers))
                  .map(|resp| resp.with_status(status))
          }
      }
  }
}


pub struct RequestWrapper<T, D> {
  func: fn(worker::Request, worker::RouteContext<D>) -> T,
}

pub type ToResponseFn<'a, D> = Rc<dyn Fn(worker::Request, worker::RouteContext<D>) -> LocalBoxFuture<'a, ResponseWrapper>>;

pub trait ToResponseJson<'a, R, D, E> {
  fn to_response_json(self, err: E) -> ToResponseFn<'a, D>;
}

pub trait ToResponsePlain<'a, R, D, E> {
  fn to_response_plain(self, err: E) -> ToResponseFn<'a, D>;
}

pub trait ToResponseEmpty<'a, R, D, E> {
  fn to_response_empty(self, err: E) -> ToResponseFn<'a, D>;
}

impl<R: 'static, T: 'static + Future<Output=Result<R, crate::model::Error>>, D: 'static> RequestWrapper<T, D> {
  pub fn from(func: fn(worker::Request, worker::RouteContext<D>) -> T) -> Self {
      Self {
          func,
      }
  }
}

impl<
  'a,
  R: 'static + Serialize,
  T: 'static + Future<Output=Result<R, crate::model::Error>>,
  D: 'static,
  E: 'static + Copy + Fn(crate::model::Error) -> ResponseWrapper
> ToResponseJson<'a, R, D, E> for RequestWrapper<T, D> {
  fn to_response_json(self, err: E) -> ToResponseFn<'a, D> {
      Rc::new(move |req, ctx| Box::pin(to_response_json(req, ctx, err, self.func)))
  }
}

impl<
  'a,
  R: 'static + Into<String>,
  T: 'static + Future<Output=Result<R, crate::model::Error>>,
  D: 'static,
  E: 'static + Copy + Fn(crate::model::Error) -> ResponseWrapper
> ToResponsePlain<'a, R, D, E> for RequestWrapper<T, D> {
  fn to_response_plain(self, err: E) -> ToResponseFn<'a, D> {
      Rc::new(move |req, ctx| Box::pin(to_response_plain(req, ctx, err, self.func)))
  }
}

impl<
  'a,
  R: 'static + None,
  T: 'static + Future<Output=Result<R, crate::model::Error>>,
  D: 'static,
  E: 'static + Copy + Fn(crate::model::Error) -> ResponseWrapper
> ToResponseEmpty<'a, R, D, E> for RequestWrapper<T, D> {
  fn to_response_empty(self, err: E) -> ToResponseFn<'a, D> {
      Rc::new(move |req, ctx| Box::pin(to_response_empty(req, ctx, err, self.func)))
  }
}

pub fn default_error_wrapper(err: crate::model::Error) -> ResponseWrapper {
  ResponseWrapper::text(format!("Error: {}", err))
      .with_status(500)
}

async fn to_response_json<
  R: 'static + Serialize,
  T: Future<Output=Result<R, crate::model::Error>>,
  D,
  E: Fn(crate::model::Error) -> ResponseWrapper
>(req: worker::Request, ctx: worker::RouteContext<D>, error_wrapper: E, func: fn(worker::Request, worker::RouteContext<D>) -> T) -> ResponseWrapper {
  let result = func(req, ctx).await;
  match result {
      Ok(data) => {
          match ResponseWrapper::json(&data) {
              Ok(resp) => resp,
              Err(err) => {
                  // todo: error_wrapper?
                  ResponseWrapper::text(format!("{}", err))
                      .with_status(500)
              }
          }
      }
      Err(err) => {
          error_wrapper(err)
      }
  }
}

async fn to_response_plain<
  R: 'static + Into<String>,
  T: Future<Output=Result<R, crate::model::Error>>,
  D,
  E: Fn(crate::model::Error) -> ResponseWrapper
>(req: worker::Request, ctx: worker::RouteContext<D>, error_wrapper: E, func: fn(worker::Request, worker::RouteContext<D>) -> T) -> ResponseWrapper {
  let result = func(req, ctx).await;
  match result {
      Ok(data) => ResponseWrapper::text(data),
      Err(err) => {
          error_wrapper(err)
      }
  }
}

async fn to_response_empty<
  R: 'static + None,
  T: Future<Output=Result<R, crate::model::Error>>,
  D,
  E: Fn(crate::model::Error) -> ResponseWrapper
>(req: worker::Request, ctx: worker::RouteContext<D>, error_wrapper: E, func: fn(worker::Request, worker::RouteContext<D>) -> T) -> ResponseWrapper {
  let result = func(req, ctx).await;
  match result {
      Ok(_) => ResponseWrapper::empty(),
      Err(err) => {
          error_wrapper(err)
      }
  }
}

trait None {}

impl None for () {}

scheduled event failed

lib.rs:

mod utils;
use worker::*;
// could not find `Schedule` in `worker`rustc(E0433)
#[event(scheduled)]
pub async fn main(ty: String, schedule: u64, cron: String) -> Result<()> {
    println!("test");
    Ok(())
}

Is there something I missed?

Confusing 403 error with `wrangler dev`

I had wrangler logined to my Cloudflare account already, but when trying to wrangler dev I received a 403 error that was not very useful in helping me determine the cause:

wrangler dev
๐ŸŒ€  Running cargo install -q worker-build && worker-build --release
[INFO]: Checking for the Wasm target...
[INFO]: Compiling to Wasm...
    Finished release [optimized] target(s) in 0.03s
[INFO]: Installing wasm-bindgen...
[INFO]: Optimizing wasm binaries with `wasm-opt`...
[INFO]: Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended
[INFO]: :-) Done in 2.10s
[INFO]: :-) Your wasm pkg is ready to publish at /home/jason/src/my-project/build.
Error: HTTP status client error (403 Forbidden) for url (https://api.cloudflare.com/client/v4/accounts/ae126deafb76cf635e5e028f594434ac/workers/subdomain/edge-preview)

I think I tracked it down to not enabling the free cloudworker component in my account, but 1) I wasn't expecting a local dev cli command to require that and 2) it would have been good to spell that out in the returned 403.

Now it seems to be working past that (but getting a different error I'm trying to make sense of). Thanks for the cool project!

No Implementation of `redirect()` for `Response`

Hey There! ๐Ÿ‘‹

Was just exploring the use of worker-rs where i eventually figured out that there is no redirect() method implementation for the Response struct, which means that i cannot redirect to a URL (302,308,etc) for an response recieved. It also becomes inconsistent with the documentation found at https://developers.cloudflare.com/workers/runtime-apis/response#instance-methods where it specifies an instance method for redirect().

Proposal:
Implement a function in the Impl of Response struct with the apt headers, and struct fields which becomes available for the user to use

Would be happy to raise a PR for the same ๐Ÿ˜ƒ

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.