Crate anystore

source ·
Expand description

anystore

anystore is a polymorphic, type-safe, composable async framework for specifying API for arbitrary stores (including databases and configuration systems). It supports addressing arbitrary type-safe hierarchies of objects.

It is best used for prototyping and configuration. It is especially good for situations when a storage system is needed, but it’s not very important, and you want to be able to change the storage provider quickly, in case the requirements change.

It is good for when you don’t want to learn yet another API. (It also might be good if you don’t want to invent yet another API, as it gives enough structure, primitives, and utils to help you build a decent client.)

It is not intended to be used when you need high performance or reliability.

This crate is nightly-only. It heavily depends on the async_fn_in_trait feature, which is not stable yet.

Goals:

  • Provide many reasonable defaults and demand very few inconsequential choices.
  • Provide a variety of wrappers to compose or augment particular stores.
  • Type safety: as long as the type is statically determinable, it should be inferred.
  • Work nicely with autocompletion and type inference.
  • Make it easy and safe to switch the store used by a program.
  • Try to capture that blessed feeling of wonder when you implement a few small pieces and suddenly you have a lot of cool emerging features.
  • The values most passed around should be thread-safe, Sync, Send – for ergonomic reasons.

Hopes and dreams:

  • Generic dynamic configuration for any store.
  • Become a reasonable framework for developing API interfaces for stores.
  • Provide an extensive test framework to make testing of new store implementations easier.
  • Support CLI/TUI tools for navigating and editing arbitrary stores.

Non-goals:

  • Serious performance considerations beyond simple hygiene. This will never be a goal.
  • Supporting all the database features for a particular database. We’d rather support the most generally useful common subset.
  • Complete concurrency safety. We’re constrained here by the particulars of the stores used.
  • For now, avoiding unsafe/unstable features. This crate already uses async_fn_in_trait, and while it’s not stable, there’s no point in trying to keep everything stable. Hopefully one day it’ll stabilize and we can rethink this non-goal.

Quick examples

Check those out:

Main concepts

Location

location::Location is the object you’ll use the most, and it contains most of the important client API. It is simply a pair of an address and a store. This is the value you’d most typically pass around, and it has a bunch of helper methods. You can traverse it further:

let location = jsonlocation
                    .sub(JsonPathPart::Key("subkey".to_owned()))
                    .sub(JsonPathPart::Index(2));

At every point, Location tracks the type and is typically able to statically tell you what kind of values you can expect there. Stores can define a DefaultValue for the addresses, which allows you to get immediately the correct default type:

let val = location.getv().await?;

In cases when you need a more refined type than the default, you can use e.g. Location::get::<Type>, as long as the address supports that type.

let val = location.get::<Value>().await?;

In many cases, you can also use strings to traverse it, which is sometimes convenient but less typesafe.

let location = jsonlocation.path("subkey[2]")?.path("deeper.anotherone[12]")?;

Address

A storage system is defined around the concept of address::Address. An address uniquely identifies a piece of content. A storage system can support several types as Addresses: e.g. pointers to specific databases, tables, rows, cells, or even sub-values inside cells.

If a system understands a particular address, it implements traits like address::traits::AddressableGet. address::traits::AddressableGet<SomeType, SomeAddr> means that SomeAddr can be used to read a value of SomeType. Often there’s a bunch of SomeTypes that is useable with an address, including special values like address::primitive::Existence which is simply used to check whether something exists at that address.

In some cases, “address” and “value” are more or less the same thing. E.g., if you list your Airtable bases, you get the data about them, but then you can reuse it as an address.

Wrappers

The traits in this crate are designed to be easily composable without too much boilerplate. That allows the creation of abstract wrappers that add functionality to the existing stores or compose stores together.

Table of contents

Be aware that most of the things in this crate are hidden behind specific features.

Basic stores:

Wrappers:

Cloud services:

Memory:

Modules