Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(*): major updates to the keyvalue interfaces #30

Merged
merged 14 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}