# do-proxy [![Crates.io][crates-badge]][crates-url] [![Docs.rs][docs-badge]][docs-url] [![MIT licensed][mit-badge]][mit-url] [crates-badge]: https://img.shields.io/crates/v/do-proxy.svg [crates-url]: https://crates.io/crates/do-proxy [docs-badge]: https://img.shields.io/docsrs/do-proxy/latest [docs-url]: https://docs.rs/do-proxy/latest/do_proxy/ [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg [mit-url]: https://github.com/fisherdarling/do-proxy/blob/master/LICENSE A library for writing type-safe [Durable Objects](https://developers.cloudflare.com/workers/learning/using-durable-objects/) (DOs) in Rust. With `do-proxy` you can: - Easily write type-safe APIs for Durable Objects. - Abstract over `fetch`, `alarm` and request-response glue code. ## Overview do-proxy provides a core trait `DoProxy` that abstracts over ser/de request response code, object initalization and loading, and Error handling glue code. After a struct implements `DoProxy`, the macro `do_proxy!` creates the [workers-rs](https://github.com/cloudflare/workers-rs)' `#[DurableObject]` struct which ends up generating the final object. ## Object Lifecycle This library provides two separate `Request` type. A normal `Request` which is what the durable object will be sent 99% of the time and an `Init` type, which is optionally sent to initialize the object. For example, lets say we have a `Person` object. The struct might look like: ```rust struct Person { birthday: DateTime, name: String } impl DoProxy for Person { // ... } do_proxy!(Person, PersonObject); ``` The `birthday` and `name` fields are non-optional and required. However, when constructing a durable object with `new` in Rust or `constructor` in TypeScript, the only information you get is the `State` and `Storage`. So when you're loading the `Person` object for the first time, you'll have to use bogus values for `name` and `birthday` because they haven't been set yet. The request that prompted the creation of the object _will likely_ be some kind of "create" command which sets `birthday` and `name` to something realistic. But that's not guaranteed. What if the person receives a command `CalculateNextBirthday` before its been created? Now you need to explicitly check that those values aren't bogus _or_ wrap everything in an `Option`. Both of those options are either prone to errors (bogus value) or unergonomic (`Option`). To solve this, `do-proxy` has two functions that are used to construct an object. - `init`: crates and saves all information necessary to construct an object in `load_from_storage`. When a user of the library sends a request to the `Person` object, they'll be able to optionally add `DoProxy::Init` data. If that data is present, `init` will be called _before_ `load_from_storage`. - `load_from_storage`: loads an object from storage. If the object is missing fields or hasn't been initialized, this function should error. In the following example, we send both initialization information along with a command. The object is first initialized _and then_ the command is handled: ```rust let proxy = env.obj::("bob@buzz.com"); let resp = proxy .init(Person::new("Bob", bobs_birthday)) .and_send(Command::CalculateNextBirthday).await?; ``` If you know that the object must be initialized or can't create initialization information, you can just send the command: ```rust let proxy = env.obj::("bob@buzz.com"); let resp = proxy.send(Command::CalculateNextBirthday).await?; ``` This approach lets you avoid options, bogus values _and_ the `init` function is `async`. ## Examples The crates under [./examples](./examples/) act as examples for the library, and in the future, they act as fully-fledged copy+paste building blocks for distributed systems. Each crate has a [hurl](https://hurl.dev/) script that show how the wrapping worker can be queried. - [`inserter`](./examples/inserter/): An InserterObject responds to a simple KV-like API for getting, inserting and deleting KV pairs in its storage. Example: ```sh POST http://localhost:8787/test_do { "insert": { "key": "hello", "value": "world!" } } POST http://localhost:8787/test_do { "get": { "key": "hello" } } # returns { "value": "world!" } ```