storail
A super basic on-disc data store that uses JSON files to persist data.
It doesn’t have transactions, MVCC, or anything like that. It’s just writing files to disc.
Useful for tiny little projects, and for fun.
Types
A collection, similar to a table in a relational database.
Construct this type to be able to read and write values of type to the database.
pub type Collection(t) {
Collection(
name: String,
to_json: fn(t) -> Json,
decoder: de.Decoder(t),
config: Config,
)
}
Constructors
-
Collection( name: String, to_json: fn(t) -> Json, decoder: de.Decoder(t), config: Config, )
Arguments
-
name
The name of the collection. This needs to be suitable for use in file paths. Typically lowercase plural names are recommended, such as “cats” and “people”.
-
to_json
A function that encodes a value into JSON, ready to be written to the disc.
-
decoder
A decoder that transforms the JSON from disc back into the expected type.
If you change the structure of your JSON you will need to make sure this decoder supports both the new and the old format, otherwise it will fail when decoding older JSON.
-
config
The configuration for this collection. See the
Config
type for details.
-
The configuration for your storage. This can be different per-collection if you prefer, but typically you’d use the same for all collections.
pub type Config {
Config(data_directory: String, temporary_directory: String)
}
Constructors
-
Config(data_directory: String, temporary_directory: String)
Arguments
-
data_directory
The directory where the recorded data will be written to. The contents of this directory should be backed up.
-
temporary_directory
A directory where temporary data will be written to. This data does not need to be persisted and can be happily lost of deleted.
This should be on the same drive as the data directory in order to get atomic file moving into that directory.
-
A pointer into a collection, where an instance could be written to or read
from. Typically this would be constructed with the key
and
namespaced_key
functions.
pub type Key(t) {
Key(
collection: Collection(t),
namespace: List(String),
id: String,
)
}
Constructors
-
Key( collection: Collection(t), namespace: List(String), id: String, )
Arguments
-
collection
The collection this key is for.
-
namespace
A grouping that this key points into. All objects within a namespace can be queried at once.
A use for this may be to create “parents” for object. An “orders” collection may conceptually belong to a “customer” entity, so you may choose to give each order a namespace of
["customer", customer_id]
.Note that the namespace can be anything, you do not need a “customers” collection to use
"customers"
in a namespace list. -
id
The identifier for the object. These are unique per-namespace.
-
pub type StorailError {
ObjectNotFound(namespace: List(String), id: String)
CorruptJson(path: String, detail: json.DecodeError)
FileSystemError(path: String, detail: simplifile.FileError)
}
Constructors
-
ObjectNotFound(namespace: List(String), id: String)
No object was found for the given key, so there was nothing to read.
-
CorruptJson(path: String, detail: json.DecodeError)
The object could be read, but it could not be decoded in the desired type.
-
FileSystemError(path: String, detail: simplifile.FileError)
There was an error working with the filesystem.
Functions
pub fn delete(key: Key(a)) -> Result(Nil, StorailError)
Delete an object from the file system.
Examples
pub fn run(cats: Collection(Cat)) {
storail.key(cats, "nubi") |> storail.delete
// -> Ok(Nil)
}
pub fn namespaced_key(
collection: Collection(a),
namespace: List(String),
id: String,
) -> Key(a)
pub fn read(key: Key(a)) -> Result(a, StorailError)
Read an object from the file system.
Examples
pub fn run(cats: Collection(Cat)) {
storail.key(cats, "nubi") |> storail.read
// -> Ok(Cat(name: "Nubi", age: 5))
}
pub fn read_namespace(
collection: Collection(a),
namespace: List(String),
) -> Result(Dict(String, a), StorailError)
Read all objects from a namespace.
Examples
pub fn run(cats: Collection(Cat)) {
storail.read_namespace(cats, ["owner", "hayleigh"])
// -> Ok([
// Cat(name: "Haskell", age: 3),
// Cat(name: "Agda", age: 2),
// ])
}
pub fn write(key: Key(a), data: a) -> Result(Nil, StorailError)
Write an object to the file system.
Writing is done by writing the JSON to the temporary directory and then by moving to the data directory. Moving on the same file system is an atomic operation for most file systems, so this should avoid data corruption from half-written files when writing was interupted by the VM being killed, the computer being unplugged, etc.
Examples
pub fn run(cats: Collection(Cat)) {
let cat = Cat(name: "Nubi", age: 5)
storail.key(cats, "nubi") |> storail.write(cat)
// -> Ok(Nil)
}