JsonFile
in package
A JSON document on disk that survives restarts, crashes and a careless text editor — and that is written without stopping the event loop.
Store is this file with the bridges in it — which Discord channel every connector is relaying with, across every server the bot is in. Losing that silently is the worst thing the bot can do: a bridge that disappears looks exactly like one that was never configured, and the person who set it up is not watching the log.
It is deliberately generic rather than part of Store, because a connector
that needs its own state on disk should get these guarantees by using this
class, not by writing a third copy of them.
Surviving
- Writes are atomic. Content goes to a temp file and is renamed over the target, so a crash mid-write cannot leave a half-written document where the state used to be.
- The last good copy is kept beside it as
.bak, written after each successful save — not by copying the file about to be replaced, which would leave the backup one write behind. - A damaged file is never silently replaced. If the JSON does not parse,
the backup is tried; if that fails too, the file is preserved under a
.corrupt-<timestamp>name and the caller starts empty. Without that, one truncated file plus onelinkcommand is every server's bridges gone with nothing left to recover from. - Anything noticed on the way in is recorded in warnings(), which the bot logs at startup and reports to its owner.
Not blocking the loop
Loading is synchronous, once, during construction: it happens before run(),
when there is no loop to block. Saving is not — a link lands while the bot
is relaying everything else — so it goes through Filesystem, and the
caller never waits: save() records the new state and returns, writes that
arrive during one already in flight collapse into a single follow-up, and
only the newest state is written.
Table of Contents
Constants
- BACKUP_SUFFIX : mixed = '.bak'
- Where the last known-good copy is kept.
Properties
- $dirty : bool
- Whether {@see save()} was called while that write was in flight.
- $filesystem : Filesystem
- $path : string
- $pending : array<string|int, mixed>
- The newest state handed to {@see save()}.
- $warnings : array<int, string>
- $writing : PromiseInterface|null
- The write in flight, if any.
Methods
- __construct() : mixed
- filesystem() : Filesystem
- The backend in use, for the startup line.
- flush() : bool
- Writes the newest state now, blocking.
- load() : array<string|int, mixed>
- Reads the document, falling back to the backup and refusing to discard one it cannot understand.
- path() : string
- save() : void
- Records new state and schedules a write. Returns immediately.
- saved() : PromiseInterface
- Resolves when everything saved so far has reached the disk, following the queue to the end.
- warnings() : array<int, string>
- Anything that went wrong while reading, in the order it was found. Empty on a normal start.
- decode() : array<string|int, mixed>|null
- Reads and decodes one file, or `null` when it is missing, unreadable, or not a JSON object.
- encode() : string|null
- write() : PromiseInterface
- Encode, write a temp file, rename it over the target, then write the backup.
Constants
BACKUP_SUFFIX
Where the last known-good copy is kept.
public
mixed
BACKUP_SUFFIX
= '.bak'
Properties
$dirty
Whether {@see save()} was called while that write was in flight.
private
bool
$dirty
= false
$filesystem read-only
private
Filesystem
$filesystem
$path read-only
private
string
$path
$pending
The newest state handed to {@see save()}.
private
array<string|int, mixed>
$pending
= []
$warnings
private
array<int, string>
$warnings
= []
$writing
The write in flight, if any.
private
PromiseInterface|null
$writing
= null
Methods
__construct()
public
__construct(string $path[, Filesystem|null $filesystem = null ]) : mixed
Parameters
- $path : string
- $filesystem : Filesystem|null = null
filesystem()
The backend in use, for the startup line.
public
filesystem() : Filesystem
Return values
Filesystemflush()
Writes the newest state now, blocking.
public
flush() : bool
For shutdown: the loop is about to stop, so a queued write would never run. Everything else should use save().
Return values
boolload()
Reads the document, falling back to the backup and refusing to discard one it cannot understand.
public
load() : array<string|int, mixed>
Return values
array<string|int, mixed>path()
public
path() : string
Return values
stringsave()
Records new state and schedules a write. Returns immediately.
public
save(array<string|int, mixed> $data) : void
Parameters
- $data : array<string|int, mixed>
saved()
Resolves when everything saved so far has reached the disk, following the queue to the end.
public
saved() : PromiseInterface
Return values
PromiseInterfacewarnings()
Anything that went wrong while reading, in the order it was found. Empty on a normal start.
public
warnings() : array<int, string>
Return values
array<int, string>decode()
Reads and decodes one file, or `null` when it is missing, unreadable, or not a JSON object.
private
static decode(string $path) : array<string|int, mixed>|null
Parameters
- $path : string
Return values
array<string|int, mixed>|nullencode()
private
static encode(array<string|int, mixed> $data) : string|null
Parameters
- $data : array<string|int, mixed>
Return values
string|nullwrite()
Encode, write a temp file, rename it over the target, then write the backup.
private
write() : PromiseInterface
Bails without touching the live file when the data cannot be encoded — a module stashing a non-UTF-8 string, say — so a bad value never truncates the state.