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 key(collection: Collection(a), id: String) -> Key(a)
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)
}
Search Document