Settings, secrets and work in the background
Freddy's app now remembers his theme, keeps a token where tokens belong, exports without freezing the window and empties the bin on a schedule. Each of these is a Laravel facade he already knows, scaled down to one process on one machine.
Settings: the store
For settings and light state there is a key-value store, always available, no
feature flag. Values are any JSON, saved to a settings.json in the OS
config directory:
import { store } from "@elyra/runtime";
await store.set("theme", "dark");
await store.set("lastNotebook", 3);
const theme = await store.get("theme"); // "dark" | null
Writes are atomic, through a temporary file and a rename, with the previous good
version kept as settings.json.bak and used if the main file cannot be
parsed. A store set on every keystroke does not write the file per keystroke,
because flushes are debounced. store.clear() from the page needs a
capability that is opt-in. Notes themselves stay in the database: the store is for
settings.
Secrets: the keychain
A token does not belong in a clear-text settings file. With the
secrets feature, credentials go to the macOS Keychain, the Windows
Credential Manager or the Linux Secret Service:
App::new().provider(SecretsProvider::for_app("Freddy Notes"))
// in a #[command]
let secrets = ctx.get::<Secrets>();
secrets.set("sync_token", &token)?;
let token = secrets.get("sync_token")?; // Option<Secret>
A read returns a Secret, not a String: it is used like a
string, prints Secret(***) in a stray log line, and wipes its bytes
when dropped. There is deliberately no route to secrets from the page, because a
value any script in the webview can read is not a secret: read it in a command and
return only what the UI needs.
Secrets for the
user's own credentials. Chapter 10 comes back to this.
A disk and a cache
The storage facade is Laravel's Storage:: over a local directory,
with every path jailed to its root, so .. and absolute paths are
refused. Freddy roots it in his app's data folder, and his exports go to
exports/ on it. The cache is Cache:: in one process,
TTL-aware, with remember, add, counters and the rest,
which suits the word counts on his dashboard:
App::new()
.provider(StorageProvider::at(&data_dir))
.provider(CacheProvider);
let stats = ctx.get::<Cache>().remember("stats", Some(Duration::from_secs(60)), || compute_stats());
The queue
Exporting a thousand notes should not happen on the UI's time. The queue is
Laravel's Queue:: for one process: push a named job, register a
handler in a provider's boot, and it runs on a background task.
struct Jobs;
impl Provider for Jobs {
fn boot(&self, ctx: &Ctx) {
ctx.get::<Queue>().on("export_notes", |payload| async move {
// write the files to the storage disk
Ok(())
});
}
}
App::new().provider(QueueProvider::with_workers(2).durable()).provider(Jobs);
In memory, jobs are lost on exit; durable() keeps every job in the
app's database until it succeeds or finally fails, so an export queued before Freddy
quits runs at the next launch, with no migration to write. A job can have attempts,
a backoff and a timeout, failed jobs are listed and can be retried, and the queue is
bounded, so push answers false rather than growing until
the process dies. The page can push jobs too and watches their status through
onQueue; the job code itself stays in Rust, as Laravel's jobs stay on
the server.
The scheduler
let sched = ctx.get::<Scheduler>();
sched.daily_at("03:00", "empty_bin", || async { /* force-delete notes trashed 30 days ago */ });
The scheduler is Laravel's Schedule, running for the life of the app.
One detail catches Laravel developers: intervals are measured from app start, so
daily() means every 24 hours, not at midnight. For a time of day, as
Freddy wants, there are clock jobs such as daily_at,
weekdays_at and weekly_on.
The guides are Settings store, Secrets, Storage, Cache, Queue and Scheduler.
What you learned
- The store keeps settings as JSON with atomic writes and a backup; the database keeps data.
Secretsuses the OS keychain, returns aSecretthat cannot leak through a log, and is never reachable from the page.- AI provider keys do not belong in a desktop binary at all.
- The queue runs jobs in the background, durably if asked; the scheduler's intervals count from app start, and clock jobs run at a time of day.