App-local file storage
day-part-fs reads and writes files beneath an app-data directory. Use it for documents,
cached responses, and other data that is too large for preferences.
Native targets use filesystem storage; web uses the browser’s Origin Private File System
(OPFS). Files persist across launches, subject to the platform’s storage policies.
This reference covers paths, errors, and backend behavior. The local storage guide shows how to use files from an app.
The UI snippets use generated res::str accessors from an app’s Fluent catalog; see the
example messages and catalog setup.
Authoring
// Blocking — Unsupported on web; keep large files off the UI thread.
day_part_fs::write("notes/today.txt", b"rain later")?;
let bytes = day_part_fs::read("notes/today.txt")?;
let names = day_part_fs::list("notes")?; // ["today.txt"], dirs get a trailing '/'
day_part_fs::remove("notes/today.txt")?;
// Async — uses the target's storage implementation, which may be unavailable. Await under day::task (docs/async.md).
// `day::task` takes a `Future<Output = ()>`, so each result is handled in the block
// instead of propagated with `?`:
day::task(async move {
match day_part_fs::write_future("notes/today.txt", text.into_bytes()).await {
Ok(()) => status.set(res::str::saved().format()),
Err(e) => status.set(res::str::operation_failed(e.to_string()).format()),
}
if let Ok(names) = day_part_fs::list_future("notes").await {
files.set(names.join(", "));
}
});
The contract points:
- Paths are checked lexically. Absolute paths, colons, and
./../empty segments returnFsError::BadPathbefore backend access. This does not reject symlinks within the root or create an OS sandbox. Native fallback directories use sharedday/day-fsnaming; provide an app-specificDAY_DATA_DIRwhen isolation is required.writecreates missing parents. list("")is the root, entries sorted, directories suffixed/. A never-written directory lists as empty, the ordinary first-run state, not an error.- The blocking calls follow the day-part-http rule: real on every native target, and
FsError::Unsupportedon web, where the single browser thread cannot wait. The*_asynctwins and*_futureforms are the portable surface.
Where files live
| Target | Root |
|---|---|
| Android / HarmonyOS | the host-provided DAY_DATA_DIR (the app’s private files dir) + day-fs/ |
| macOS / iOS | ~/Library/Application Support/day/day-fs/ (the iOS sandbox HOME makes this the app container) |
| Linux | $XDG_DATA_HOME/day/day-fs/ (else ~/.local/share/day/day-fs/) |
| Windows | %APPDATA%\day\day-fs\ |
| web-dom | the origin’s OPFS via the day-dom shim (day_dom_fs_start + the request-id completion exports). OPFS is the only store: a context without it (a pre-OPFS browser, or a private-browsing/ephemeral session, which WebKit gives no storage backing) answers Unsupported (no getDirectory at all) or Io (present but broken), never a silent alternate store |
| anything else | FsError::Unsupported |
DAY_DATA_DIR wins on native targets when set; the mobile hosts export it (DayActivity in day-android,
EntryAbility in day-arkui’s staged ArkTS host), and tests set it to a scratch directory.
day_part_fs::data_dir() returns that directory without the day-fs/ leaf on native targets,
for code that keeps files of its own beside the part’s: a download manager’s journal and partial
files, for instance (docs/downloads.md). day_part_fs::platform_data_dir()
returns the platform directory above it, the one every application’s data sits under, for an
app that opens a store another application keeps there (a Day client over a desktop app’s own
database); DAY_DATA_DIR does not redirect that one. The web has no such directory, so both
functions exist only off wasm32.
Error taxonomy
NotFound, BadPath, Io(message), Unsupported. The web tier collapses provider detail
into Io, except NotFoundError → NotFound and a context without OPFS → Unsupported.
What it shows about the extension system
This is the third part to ride the day-dom shim (after prefs and http), and the second to
complete back into wasm with the request-id pattern. Native needs no platform code at all: one
std::fs backend over an env-resolved root covers six targets, with the mobile hosts
contributing a single DAY_DATA_DIR line each.
v2 notes (out of scope)
The portable buffered API has no append, rename, recursive-remove, or metadata method.
Native-only open_read(path) already returns a seekable std::fs::File; use it off the UI
thread for large reads. A portable streaming writer and cancellation of accepted native IO
are not supplied by these future wrappers.
For macOS container storage, file permissions, migration, and SQLite sidecar restrictions, see macOS App Sandbox.
Native archive readers can use open_read(path) to obtain a read-only std::fs::File
implementing Read + Seek. It applies the same relative-path checks and day-fs/ storage root
as read and write, avoiding whole-file buffers and application-specific path reconstruction.
Opening and reading are blocking: perform this work off the UI thread. Web does not expose this
native handle; use read_future there, which currently returns the whole file as an owned buffer.
Lifetime and evidence
Code above is an integration fragment; its literal bytes are synthetic sample document data.
Use the compiled storage recipe for full setup.
Native async jobs run detached on the blocking pool. Dropping FsFuture discards delivery but
does not undo an accepted write. write creates/truncates and does not promise atomic replacement.
FsError is non-exhaustive; callers must allow new variants.
Source: public contracts and native implementation, reviewed 2026-10-22. Browser storage and mobile host integration were not runtime-tested by the recipe compile check.