Petrichor.Storage#
Petrichor.Storage is per-mod key/value storage, plus general file access scoped to your mod's save folder. Values stored via store() live in memory while your mod runs and are written to disk as JSON when you call save().
The host loads any existing store.json into memory when your mod starts, so retrieve can return data from a previous session.
Save location#
Each mod gets its own folder under the user data directory:
<user_data> is the Courage Reborn user folder (same root as mods/, saves/, and config.ini). The mod_saves folder name is configurable in config.ini as mod_saves (default: mod_saves).
mod_id matches the id field in your mod.json.
All Storage file operations (read_file, write_file, file_path, and the key/value store backed by store.json) are sandboxed to this folder. Mods cannot read or write outside mod_saves/<mod_id>/.
Bundled mod files live under <mod_dir>/assets/ and are read-only at runtime. Use Storage.asset(), Storage.asset_path(), and Storage.list_assets() to read them. There is no API to write into assets/. Ship anything static at build time instead.
Loading#
Petrichor.Storage is registered per mod when the mod loads. It is not a global prelude.
API#
Key/value store#
These operate on an in-memory table that is persisted as a single JSON file, store.json, in your mod's save folder.
Storage.store(key, value)#
Sets a value in the in-memory store. Not written to disk until Storage.save().
| Parameter | Type | Description |
|---|---|---|
key | string | Storage key |
value | JSON value | nil, boolean, number, string, or table (object or array) |
Storage.retrieve(key) -> value?#
Returns the stored value, or nil if the key is missing.
Storage.default(key, value) -> value#
If key is missing, sets it to value and returns value. If the key exists, returns the stored value without changing it.
Storage.save()#
This function is a no-op if nothing has changed since the last save.
Writes the in-memory store to disk as JSON (store.json in your mod's save folder).
You can also call self:save() on your mod class (from Petrichor.Mod) to persist storage from a hook or shutdown.
Storage.clear(key)#
Removes one key from the in-memory store. Call save() to persist the change.
Storage.clear_all()#
Empties the in-memory store. Call save() to overwrite store.json with an empty object.
General file access#
These let a mod read and write arbitrary named files inside its own save folder (mod_saves/<mod_id>/). This is useful for data that doesn't fit the single-JSON-blob model, like logs, exported data, or large/binary payloads.
Storage.read_file(name) -> string?#
Reads a file from your mod's save folder and returns its contents as a string. Returns nil if the file doesn't exist or name resolves outside the save folder.
| Parameter | Type | Description |
|---|---|---|
name | string | Filename, relative to your mod's save folder. Defaults to "store.json" if omitted. |
Storage.write_file(name, content) -> boolean#
Writes a string to a file in your mod's save folder, creating it if needed. Returns true on success, false if content isn't a string or name resolves outside the save folder.
| Parameter | Type | Description |
|---|---|---|
name | string | Filename, relative to your mod's save folder. Defaults to "store.json" if omitted. |
content | string | Data to write |
Storage.file_path(name) -> string?#
Returns the absolute filesystem path for a file in your mod's save folder, without reading it. Use this when an API expects a file path rather than file contents. Returns nil if name resolves outside the save folder.
Bundled assets (read-only)#
Storage.asset(path) -> string?#
Reads a bundled file from <mod_dir>/assets/<path> and returns its contents as a string. Returns nil if the file is missing.
Storage.list_assets(path?) -> string[]#
Lists filenames of files directly under <mod_dir>/assets/<path>. Defaults to assets root if path is omitted. Returns an empty array if the directory is missing.
Storage.asset_path(path) -> string#
Returns the absolute filesystem path for a bundled asset file without reading it. Use this when an API expects a file path rather than file contents (e.g. Audio.play).
| Parameter | Type | Description |
|---|---|---|
path | string | Path relative to <mod_dir>/assets/ |
JSON on disk#
Persistence uses Petrichor.Json. Only JSON-compatible values are safe to store via Storage.store() (booleans, numbers, strings, arrays, objects). Luau tables with mixed keys or non-string keys may not round-trip cleanly.
Example: remember a setting#
local Storage = require("Petrichor.Storage")
local MyMod = mod("my-mod")
function MyMod:init()
self.enabled = Storage.default("enabled", true)
end
function MyMod:toggle()
self.enabled = not self.enabled
Storage.store("enabled", self.enabled)
self:save()
end
function MyMod:shutdown()
self:save()
end
return MyMod
import * as Storage from "Petrichor.Storage"
declare function mod(name: string): any
const MyMod = mod("my-mod")
MyMod.init = function () {
this.enabled = Storage.default("enabled", true)
}
MyMod.toggle = function () {
this.enabled = !this.enabled
Storage.store("enabled", this.enabled)
this.save()
}
MyMod.shutdown = function () {
this.save()
}
export = MyMod
Example: writing a log file alongside store.json#
Notes#
- Changes made via
Storage.store()are in memory untilsave(). If the game exits without saving, they are automatically saved via.self:save()on the mod instance. Note this is not a catch-all: due to the nature of our mixin-like system, mods can take down the process at any time. Save important changes eagerly. Storage.write_file()writes immediately. It is not buffered like the key/value store, and has no separatesave()step.- One save folder per mod
idfrommod.json.read_file,write_file, andfile_pathare sandboxed to that folder; anamethat would resolve outside it fails (nil/false) rather than escaping. assets/is read-only. Useasset(),asset_path(), andlist_assets()to read bundled files; useread_file()/write_file()/ the key/value store for anything a mod needs to persist.asset_path()andfile_path()both return a path without reading the file. Use them when passing a path to another API such asAudio.play.