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(storage_path: String)
}
Constructors
-
Config(storage_path: String)
Arguments
-
storage_path
The directory where data will be written to. Within this directory Stóráil will create two directories:
data
andtemporary
. You should back-up thedata
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 list(
collection: Collection(a),
namespace: List(String),
) -> Result(List(String), StorailError)
List all objects in a namespace.
Examples
pub fn run(cats: Collection(Cat)) {
storail.list(cats, ["owner", "hayleigh"])
// -> Ok(["Haskell", "Agda"])
}
pub fn namespaced_key(
collection: Collection(a),
namespace: List(String),
id: String,
) -> Key(a)
pub fn optional_read(
key: Key(a),
) -> Result(Option(a), StorailError)
Read an object from the file system, returning None
if there was no
object with the given key.
Examples
pub fn run(cats: Collection(Cat)) {
storail.key(cats, "nubi") |> storail.optional_read
// -> Ok(Some(Cat(name: "Nubi", age: 5)))
storail.key(cats, "mills") |> storail.optional_read
// -> Ok(None)
}
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)
}