Skip to content

Commit

Permalink
feat(*): major updates to the keyvalue interfaces (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mossaka authored Jan 19, 2024
1 parent 0bce169 commit 9cde2eb
Show file tree
Hide file tree
Showing 13 changed files with 574 additions and 469 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ jobs:
- uses: actions/checkout@v3
- name: ensure `./wit/deps` are in sync
run: |
curl -Lo 'wit-deps' https://github.com/bytecodealliance/wit-deps/releases/download/v0.3.3/wit-deps-x86_64-unknown-linux-musl
curl -Lo 'wit-deps' https://github.com/bytecodealliance/wit-deps/releases/download/v0.3.5/wit-deps-x86_64-unknown-linux-musl
chmod +x wit-deps
./wit-deps lock --check
- uses: WebAssembly/wit-abi-up-to-date@v16
- uses: WebAssembly/wit-abi-up-to-date@v17
with:
worlds: 'keyvalue keyvalue-handle-watch'
wit-bindgen: '0.13.0'
worlds: 'imports keyvalue-handle-watch'
wit-bindgen: '0.16.0'
363 changes: 72 additions & 291 deletions README.md

Large diffs are not rendered by default.

195 changes: 146 additions & 49 deletions keyvalue.md → imports.md

Large diffs are not rendered by default.

193 changes: 142 additions & 51 deletions keyvalue-handle-watch.md

Large diffs are not rendered by default.

19 changes: 15 additions & 4 deletions wit/atomic.wit
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
/// A keyvalue interface that provides atomic operations.
///
/// Atomic operations are single, indivisible operations. When a fault causes
/// an atomic operation to fail, it will appear to the invoker of the atomic
/// operation that the action either completed successfully or did nothing
/// at all.
interface atomic {
/// A keyvalue interface that provides atomic operations.
use types.{bucket, error, key};
Expand All @@ -9,12 +14,18 @@ interface atomic {
/// If the key does not exist in the bucket, it creates a new key-value pair
/// with the value set to the given delta.
///
/// If any other error occurs, it returns an error.
/// If any other error occurs, it returns an `Err(error)`.
increment: func(bucket: borrow<bucket>, key: key, delta: u64) -> result<u64, error>;

/// Atomically compare and swap the value associated with the key in the bucket.
/// It returns a boolean indicating if the swap was successful.
/// Compare-and-swap (CAS) atomically updates the value associated with the key
/// in the bucket if the value matches the old value. This operation returns
/// `Ok(true)` if the swap was successful, `Ok(false)` if the value did not match,
///
/// A successful CAS operation means the current value matched the `old` value
/// and was replaced with the `new` value.
///
/// If the key does not exist in the bucket, it returns an error.
/// If the key does not exist in the bucket, it returns `Ok(false)`.
///
/// If any other error occurs, it returns an `Err(error)`.
compare-and-swap: func(bucket: borrow<bucket>, key: key, old: u64, new: u64) -> result<bool, error>;
}
27 changes: 0 additions & 27 deletions wit/batch.wit

This file was deleted.

12 changes: 11 additions & 1 deletion wit/error.wit
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
interface wasi-cloud-error {
interface wasi-keyvalue-error {
/// An error resource type for keyvalue operations.
///
/// Common errors:
/// - Connectivity errors (e.g. network errors): when the client cannot establish
/// a connection to the keyvalue service.
/// - Authentication and Authorization errors: when the client fails to authenticate
/// or does not have the required permissions to perform the operation.
/// - Data errors: when the client sends incompatible or corrupted data.
/// - Resource errors: when the system runs out of resources (e.g. memory).
/// - Internal errors: unexpected errors on the server side.
///
/// Currently, this provides only one function to return a string representation
/// of the error. In the future, this will be extended to provide more information
/// about the error.
Expand Down
81 changes: 81 additions & 0 deletions wit/eventual-batch.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/// A keyvalue interface that provides eventually consistent batch operations.
///
/// A batch operation is an operation that operates on multiple keys at once.
///
/// Batch operations are useful for reducing network round-trip time. For example,
/// if you want to get the values associated with 100 keys, you can either do 100 get
/// operations or you can do 1 batch get operation. The batch operation is
/// faster because it only needs to make 1 network call instead of 100.
///
/// A batch operation does not guarantee atomicity, meaning that if the batch
/// operation fails, some of the keys may have been modified and some may not.
/// Transactional operations are being worked on and will be added in the future to
/// provide atomicity.
///
/// Data consistency in a key value store refers to the gaurantee that once a
/// write operation completes, all subsequent read operations will return the
/// value that was written.
///
/// The level of consistency in batch operations is **eventual consistency**, the same
/// with the readwrite interface. This interface does not guarantee strong consistency,
/// meaning that if a write operation completes, subsequent read operations may not return
/// the value that was written.
interface eventual-batch {
/// A keyvalue interface that provides batch get operations.
use types.{bucket, error, key, incoming-value, outgoing-value};

/// Get the values associated with the keys in the bucket. It returns a list of
/// incoming-value that can be consumed to get the value associated with the key.
///
/// If any of the keys do not exist in the bucket, it returns a `none` value for
/// that key in the list.
///
/// Note that the key-value pairs are guaranteed to be returned in the same order
///
/// MAY show an out-of-date value if there are concurrent writes to the bucket.
///
/// If any other error occurs, it returns an `Err(error)`.
get-many: func(bucket: borrow<bucket>, keys: list<key>) -> result<list<option<incoming-value>>, error>;

/// Get all the keys in the bucket. It returns a list of keys.
///
/// Note that the keys are not guaranteed to be returned in any particular order.
///
/// If the bucket is empty, it returns an empty list.
///
/// MAY show an out-of-date list of keys if there are concurrent writes to the bucket.
///
/// If any error occurs, it returns an `Err(error)`.
keys: func(bucket: borrow<bucket>) -> result<list<key>, error>;

/// Set the values associated with the keys in the bucket. If the key already
/// exists in the bucket, it overwrites the value.
///
/// Note that the key-value pairs are not guaranteed to be set in the order
/// they are provided.
///
/// If any of the keys do not exist in the bucket, it creates a new key-value pair.
///
/// If any other error occurs, it returns an `Err(error)`. When an error occurs, it
/// does not rollback the key-value pairs that were already set. Thus, this batch operation
/// does not guarantee atomicity, implying that some key-value pairs could be
/// set while others might fail.
///
/// Other concurrent operations may also be able to see the partial results.
set-many: func(bucket: borrow<bucket>, key-values: list<tuple<key, borrow<outgoing-value>>>) -> result<_, error>;

/// Delete the key-value pairs associated with the keys in the bucket.
///
/// Note that the key-value pairs are not guaranteed to be deleted in the order
/// they are provided.
///
/// If any of the keys do not exist in the bucket, it skips the key.
///
/// If any other error occurs, it returns an `Err(error)`. When an error occurs, it
/// does not rollback the key-value pairs that were already deleted. Thus, this batch operation
/// does not guarantee atomicity, implying that some key-value pairs could be
/// deleted while others might fail.
///
/// Other concurrent operations may also be able to see the partial results.
delete-many: func(bucket: borrow<bucket>, keys: list<key>) -> result<_, error>;
}
56 changes: 56 additions & 0 deletions wit/eventual.wit
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/// A keyvalue interface that provides eventually consistent CRUD operations.
///
/// A CRUD operation is an operation that acts on a single key-value pair.
///
/// The value in the key-value pair is defined as a `u8` byte array and the intention
/// is that it is the common denominator for all data types defined by different
/// key-value stores to handle data, ensuring compatibility between different
/// key-value stores. Note: the clients will be expecting serialization/deserialization overhead
/// to be handled by the key-value store. The value could be a serialized object from
/// JSON, HTML or vendor-specific data types like AWS S3 objects.
///
/// Data consistency in a key value store refers to the gaurantee that once a
/// write operation completes, all subsequent read operations will return the
/// value that was written.
///
/// The level of consistency in readwrite interfaces is **eventual consistency**,
/// which means that if a write operation completes successfully, all subsequent
/// read operations will eventually return the value that was written. In other words,
/// if we pause the updates to the system, the system eventually will return
/// the last updated value for read.
interface eventual {
/// A keyvalue interface that provides simple read and write operations.
use types.{bucket, error, incoming-value, key, outgoing-value};

/// Get the value associated with the key in the bucket.
///
/// The value is returned as an option. If the key-value pair exists in the
/// bucket, it returns `Ok(value)`. If the key does not exist in the
/// bucket, it returns `Ok(none)`.
///
/// If any other error occurs, it returns an `Err(error)`.
get: func(bucket: borrow<bucket>, key: key) -> result<option<incoming-value>, error>;

/// Set the value associated with the key in the bucket. If the key already
/// exists in the bucket, it overwrites the value.
///
/// If the key does not exist in the bucket, it creates a new key-value pair.
///
/// If any other error occurs, it returns an `Err(error)`.
set: func(bucket: borrow<bucket>, key: key, outgoing-value: borrow<outgoing-value>) -> result<_, error>;

/// Delete the key-value pair associated with the key in the bucket.
///
/// If the key does not exist in the bucket, it does nothing.
///
/// If any other error occurs, it returns an `Err(error)`.
delete: func(bucket: borrow<bucket>, key: key) -> result<_, error>;

/// Check if the key exists in the bucket.
///
/// If the key exists in the bucket, it returns `Ok(true)`. If the key does
/// not exist in the bucket, it returns `Ok(false)`.
///
/// If any other error occurs, it returns an `Err(error)`.
exists: func(bucket: borrow<bucket>, key: key) -> result<bool, error>;
}
11 changes: 8 additions & 3 deletions wit/handle-watch.wit
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
/// A keyvalue interface that provides handle-watch operations.
///
/// This interface is used to provide event-driven mechanisms to handle
/// keyvalue changes.
interface handle-watch {
/// A keyvalue interface that provides handle-watch operations.
use types.{bucket, key, incoming-value};

/// Handle the set event for the given bucket and key.
/// It returns a incoming-value that can be consumed to get the value.
/// Handle the `set` event for the given bucket and key.
/// It returns a `incoming-value` that represents the new value being set.
/// The new value can be consumed by the handler.
on-set: func(bucket: bucket, key: key, incoming-value: borrow<incoming-value>);

/// Handle the delete event for the given bucket and key.
/// Handle the `delete` event for the given bucket and key.
/// It returns a `key` that represents the key being deleted.
on-delete: func(bucket: bucket, key: key);
}
26 changes: 0 additions & 26 deletions wit/readwrite.wit

This file was deleted.

30 changes: 21 additions & 9 deletions wit/types.wit
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,55 @@ interface types {
/// In this interface, we use the term `bucket` to refer to a collection of key-value
// Soon: switch to `resource bucket { ... }`
resource bucket {
/// Opens a bucket with the given name.
///
/// If any error occurs, including if the bucket does not exist, it returns an `Err(error)`.
open-bucket: static func(name: string) -> result<bucket, error>;
}
/// A key is a unique identifier for a value in a bucket. The key is used to
/// retrieve the value from the bucket.
type key = string;

/// A list of keys
type keys = list<key>;

use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream, output-stream};
use wasi-cloud-error.{ error };
use wasi-keyvalue-error.{ error };
/// A value is the data stored in a key-value pair. The value can be of any type
/// that can be represented in a byte array. It provides a way to write the value
/// to the output-stream defined in the `wasi-io` interface.
// Soon: switch to `resource value { ... }`
resource outgoing-value {
new-outgoing-value: static func() -> outgoing-value;
/// Writes the value to the output-stream asynchronously.
/// If any other error occurs, it returns an `Err(error)`.
outgoing-value-write-body-async: func() -> result<outgoing-value-body-async, error>;
/// Writes the value to the output-stream synchronously.
/// If any other error occurs, it returns an `Err(error)`.
outgoing-value-write-body-sync: func(value: outgoing-value-body-sync) -> result<_, error>;
}
type outgoing-value-body-async = output-stream;
type outgoing-value-body-sync = list<u8>;


/// A incoming-value is a wrapper around a value. It provides a way to read the value
/// from the input-stream defined in the `wasi-io` interface.
/// from the `input-stream` defined in the `wasi-io` interface.
///
/// The incoming-value provides two ways to consume the value:
/// 1. `incoming-value-consume-sync` consumes the value synchronously and returns the
/// value as a list of bytes.
/// value as a `list<u8>`.
/// 2. `incoming-value-consume-async` consumes the value asynchronously and returns the
/// value as an input-stream.
/// value as an `input-stream`.
/// In addition, it provides a `incoming-value-size` function to get the size of the value.
/// This is useful when the value is large and the caller wants to allocate a buffer of
/// the right size to consume the value.
// Soon: switch to `resource incoming-value { ... }`
resource incoming-value {
/// Consumes the value synchronously and returns the value as a list of bytes.
/// If any other error occurs, it returns an `Err(error)`.
incoming-value-consume-sync: func() -> result<incoming-value-sync-body, error>;
/// Consumes the value asynchronously and returns the value as an `input-stream`.
/// If any other error occurs, it returns an `Err(error)`.
incoming-value-consume-async: func() -> result<incoming-value-async-body, error>;
size: func() -> u64;
/// The size of the value in bytes.
/// If the size is unknown or unavailable, this function returns an `Err(error)`.
incoming-value-size: func() -> result<u64, error>;
}
type incoming-value-async-body = input-stream;
type incoming-value-sync-body = list<u8>;
Expand Down
22 changes: 18 additions & 4 deletions wit/world.wit
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
package wasi:keyvalue;

world keyvalue {
import readwrite;
/// The `wasi:keyvalue/imports` world provides common APIs for interacting
/// with key-value stores. Components targeting this world will be able to
/// do
/// 1. CRUD (create, read, update, delete) operations on key-value stores.
/// 2. Atomic `increment` and CAS (compare-and-swap) operations.
/// 3. Batch operations that can reduce the number of round trips to the network.
world imports {
/// The `eventual` capability allows the component to perform
/// eventually consistent CRUD operations on the key-value store.
import eventual;

/// The `atomic` capability allows the component to perform atomic
/// `increment` and CAS (compare-and-swap) operations.
import atomic;
import batch;

/// The `eventual-batch` capability allows the component to perform eventually
/// consistent batch operations that can reduce the number of round trips to the network.
import eventual-batch;
}

world keyvalue-handle-watch {
include keyvalue;
include imports;
export handle-watch;
}

0 comments on commit 9cde2eb

Please sign in to comment.