class Sepia::Storage

Overview

Central storage management class.

The Storage class manages pluggable storage backends and provides both a modern class-based API and backward compatibility with the original singleton pattern.

⚠️ WARNING: The Storage API and backend interfaces are subject to change. The on-disk format for the filesystem backend is not stable.

Supported Backends

Usage

# Configure storage backend
Sepia::Storage.configure(:filesystem, {"path" => "./data"})

# Or use in-memory storage
Sepia::Storage.configure(:memory)

# Class-based API (recommended)
Sepia::Storage.save(my_object)
loaded = Sepia::Storage.load(MyClass, "object-id")

# Legacy singleton API (still supported)
Sepia::Storage::INSTANCE.save(my_object)
loaded = Sepia::Storage::INSTANCE.load(MyClass, "object-id")

Defined in:

sepia/storage.cr

Constant Summary

INSTANCE = new

Legacy singleton instance for backward compatibility.

Provides the same API as the class methods for existing code that relies on the singleton pattern.

Class Method Summary

Instance Method Summary

Class Method Detail

def self.backend #

Returns the current storage backend.

Returns

The currently active StorageBackend instance.

Example

backend = Sepia::Storage.backend
puts backend.class # => FileStorage or InMemoryStorage

[View source]
def self.backend=(backend : StorageBackend) #

Sets the current storage backend.

Allows switching to a different backend implementation at runtime.

Parameters

  • backend : A StorageBackend instance to use

Example

# Switch to custom backend
custom_backend = MyCustomStorage.new
Sepia::Storage.backend = custom_backend

[View source]
def self.backup(objects : Array(Sepia::Object), output_path : String) : String #

Creates a backup of the specified objects.

This is a convenience method that wraps the Sepia::Backup.create method and validates that the current storage backend supports backup operations.

Parameters

  • objects : Array of objects to include in the backup
  • output_path : Path where the backup tar file will be created

Returns

The path to the created backup file

Example

# Backup specific objects
documents = [doc1, doc2, doc3]
backup_path = Sepia::Storage.backup(documents, "docs_backup.tar")

# Backup a user's entire object tree
user_data = [user_object]
backup_path = Sepia::Storage.backup(user_data, "user_backup_#{Time.utc.to_unix}.tar")

Raises

  • Sepia::Backup::BackendNotSupportedError if current backend doesn't support backups
  • Sepia::Backup::BackupCreationError if backup creation fails

[View source]
def self.backup(object : Sepia::Object, output_path : String) : String #

Creates a backup of a single object and its references.

Convenience method for backing up one object tree.

Parameters

  • object : Root object to backup (includes all referenced objects)
  • output_path : Path where the backup tar file will be created

Returns

The path to the created backup file

Example

# Backup a project and all its documents
project = Sepia::Storage.load(Project, "project-123")
backup_path = Sepia::Storage.backup(project, "project_backup.tar")

[View source]
def self.backup_all(output_path : String, progress_callback = nil) : String #

Creates a backup of all objects in the current storage.

This method attempts to backup all objects currently stored in the storage backend. Be aware that this can be resource-intensive for large storage systems.

Parameters

  • output_path : Path where the backup tar file will be created
  • progress_callback : Optional callback for progress updates

Returns

The path to the created backup file

Example

# Backup everything with progress updates
backup_path = Sepia::Storage.backup_all("full_backup.tar") do |progress|
  puts "Backup progress: #{progress} objects processed"
end

[View source]
def self.backup_supported? : Bool #

Checks if the current storage backend supports backup operations.

Currently, only FileStorage supports backup operations as it provides access to the underlying file system structure.

Returns

true if backup is supported, false otherwise

Example

if Sepia::Storage.backup_supported?
  puts "Backup operations are available"
else
  puts "Switch to FileStorage to enable backup features"
end

[View source]
def self.build_generation_path(base_path : String | Nil, generation_id : String) : String | Nil #

Build a generation-specific path from a base path and generation ID.


[View source]
def self.clear #

Bulk operations


[View source]
def self.configure(backend : Symbol, config : Hash(String, String | Bool | Hash(String, String)) = {} of String => String) #

Configures storage using a named backend.

Provides a convenient way to configure common backends without instantiating them manually.

Parameters

  • backend : Symbol identifying the backend type (:filesystem or :memory)
  • config : Optional configuration hash for the backend

Configuration Options

For :filesystem backend:

  • "path": Root directory path (defaults to system temp directory)
  • "watch": Enable file system watcher (default: false)
    • true: Enable watcher with default settings
    • false: Disable watcher
    • Hash: Custom watcher configuration options

For :memory backend:

  • No configuration options available

Examples

# Configure filesystem storage with custom path
Sepia::Storage.configure(:filesystem, {"path" => "./app_data"})

# Configure filesystem storage with watcher enabled
Sepia::Storage.configure(:filesystem, {"watch" => true})

# Configure filesystem storage with custom watcher settings
Sepia::Storage.configure(:filesystem, {
  "path"  => "./app_data",
  "watch" => {
    "recursive" => true,
    "latency"   => 0.1,
  },
})

# Configure in-memory storage
Sepia::Storage.configure(:memory)

[View source]
def self.count(object_class : Class) : Int32 #

[View source]
def self.delete(object : Serializable | Container, cache : Bool = true, metadata = nil) #

[View source]
def self.delete(class_name : String, id : String, cache : Bool = true) #

[View source]
def self.exists?(object_class : Class, id : String) : Bool #

[View source]
def self.export_data : Hash(String, Array(Hash(String, String))) #

[View source]
def self.gc(roots : Enumerable(Sepia::Object), dry_run : Bool = false) : Hash(String, Array(String)) #

[View source]
def self.get_latest_generation(object_class : Class, base_id : String) #

Get the latest generation object for a given base ID.

Returns the object with the highest generation number, or nil if no generations exist.


[View source]
def self.import_data(data : Hash(String, Array(Hash(String, String)))) #

[View source]
def self.last_event(object_class : Class, id : String) : LogEvent | Nil #

Get the last event for a specific object.

Parameters

  • object_class : The class of the object
  • id : The object's unique identifier

Returns

The last event for the object, or nil if no events exist

Example

last_event = Sepia::Storage.last_event(MyDocument, "doc-123")
if last_event
  puts "Last modified: #{last_event.timestamp}"
end

[View source]
def self.list_all(object_class : Class) : Array(String) #

Discovery API - delegates to current backend


[View source]
def self.list_all_objects : Hash(String, Array(String)) #

[View source]
def self.load(object_class : T.class, id : String, path : String | Nil = nil, cache : Bool = true) : T forall T #

[View source]
def self.next_generation_number(object_class : Class, base_id : String) : Int32 #

Get the next generation number for an object based on existing files.

Scans the filesystem to find the highest existing generation number and returns the next one. This is independent of event logging.

Parameters

  • object_class : The class of the object
  • base_id : The base ID without generation suffix

Returns

The next generation number (1 if no generations exist)


[View source]
def self.object_events(object_class : Class, id : String) : Array(LogEvent) #

Get all events for a specific object.

Parameters

  • object_class : The class of the object
  • id : The object's unique identifier

Returns

Array of events for the specified object, ordered by timestamp

Example

events = Sepia::Storage.object_events(MyDocument, "doc-123")
events.each { |event| puts "#{event.timestamp}: #{event.event_type}" }

[View source]
def self.save(object : Serializable, path : String | Nil = nil, cache : Bool = true, metadata = nil, *, force_new_generation : Bool = false) #

Class methods with cache integration (caching is default)


[View source]
def self.save(object : Container, path : String | Nil = nil, cache : Bool = true, metadata = nil, *, force_new_generation : Bool = false) #

[View source]

Instance Method Detail

def backup(objects : Array(Sepia::Object), output_path : String) : String #

Instance version of backup method for backward compatibility.

Example

Sepia::Storage::INSTANCE.backup([doc1, doc2], "backup.tar")

[View source]
def backup(object : Sepia::Object, output_path : String) : String #

Instance version of single object backup method.


[View source]
def backup_all(output_path : String, progress_callback = nil) : String #

Instance version of backup_all method.


[View source]
def backup_supported? : Bool #

Instance version of backup_supported? method.


[View source]
def delete(object : Serializable | Container, cache : Bool = true, metadata = nil) #

[View source]
def load(object_class : T.class, id : String, path : String | Nil = nil, cache : Bool = true) : T forall T #

Loads an object using the current backend.

First checks the cache for the object. If not found, loads from backend and automatically caches the result for future retrievals. Set cache: false to disable caching for this operation.

Parameters

  • object_class : The class of object to load
  • id : The object's unique identifier
  • path : Optional custom load path
  • cache : Whether to use cache (default: true)

Returns

An instance of type T loaded from storage.

Example

# Load with caching (default)
doc = Sepia::Storage.load(MyDocument, "doc-uuid")

# Load without caching
doc = Sepia::Storage.load(MyDocument, "doc-uuid", cache: false)

# Type is inferred, no casting needed
puts doc.content # doc is typed as MyDocument

[View source]
def path : String #

Legacy path property (only works with FileStorage)


[View source]
def path=(path : String) #

[View source]
def save(object : Serializable, path : String | Nil = nil, cache : Bool = true, metadata = nil, *, force_new_generation : Bool = false) #

Saves a Serializable object using the current backend.

Automatically caches the object for faster retrieval. Set cache: false to disable caching for this operation. Optionally logs the save operation if the object's class has event logging enabled.

Parameters

  • object : The Serializable object to save
  • path : Optional custom save path
  • cache : Whether to cache the object (default: true)
  • metadata : Optional metadata for event logging

Example

doc = MyDocument.new("Hello")
Sepia::Storage.save(doc)                                # Save with caching (default)
Sepia::Storage.save(doc, cache: false)                  # Save without caching
Sepia::Storage.save(doc, metadata: {"user" => "alice"}) # Save with event logging

[View source]
def save(object : Container, path : String | Nil = nil, cache : Bool = true, metadata = nil, *, force_new_generation : Bool = false) #

Saves a Container object using the current backend.

Automatically caches the object for faster retrieval. Set cache: false to disable caching for this operation. Optionally logs the save operation if the object's class has event logging enabled.

Parameters

  • object : The Container object to save
  • path : Optional custom save path
  • cache : Whether to cache the object (default: true)
  • metadata : Optional metadata for event logging

Example

board = Board.new("My Board")
Sepia::Storage.save(board)                                # Save with caching (default)
Sepia::Storage.save(board, cache: false)                  # Save without caching
Sepia::Storage.save(board, metadata: {"user" => "alice"}) # Save with event logging

[View source]