@furystack/filesystem-store
Version:
Simple File System store implementation for FuryStack
93 lines • 3.96 kB
TypeScript
import type { Constructable, FilterType, FindOptions, PhysicalStore } from '@furystack/core';
import { EventHub, type ListenerErrorPayload } from '@furystack/utils';
import { promises } from 'fs';
/**
* {@link PhysicalStore} backed by a JSON file on disk.
*
* Reads on construction, holds entities in an {@link InMemoryStore}, and flushes
* pending writes on a `tickMs` interval (default 3000 ms). External edits to the
* file are picked up via an `fs.watch` watcher that triggers {@link reloadData}.
*
* Disposal is async — `[Symbol.asyncDispose]` flushes pending changes, closes
* the watcher, and clears the interval. Owners must `await` disposal or rely on
* `await using` / {@link defineStore}'s `onDispose` hook to avoid lost writes.
*
* **Init race:** the constructor schedules the initial reload in the
* background. Calls to `get` / `find` / `count` issued before the reload
* resolves see an empty cache. Failures during the background reload (other
* than `ENOENT`) are surfaced via `onLoadError` rather than thrown.
*
* Re-emits `onEntityAdded` / `onEntityUpdated` / `onEntityRemoved` from the
* underlying in-memory store, plus `onWatcherError` (sync watcher setup
* failure) and `onLoadError` (async file-load failure) for diagnostics.
*/
export declare class FileSystemStore<T, TPrimaryKey extends keyof T> extends EventHub<{
onEntityAdded: {
entity: T;
};
onEntityUpdated: {
id: T[TPrimaryKey];
change: Partial<T>;
};
onEntityRemoved: {
key: T[TPrimaryKey];
};
onWatcherError: {
error: unknown;
};
onLoadError: {
error: unknown;
};
onListenerError: ListenerErrorPayload;
}> implements PhysicalStore<T, TPrimaryKey, T> {
private readonly options;
private readonly watcher?;
readonly model: Constructable<T>;
readonly primaryKey: TPrimaryKey;
private readonly inMemoryStore;
private get cache();
remove(...keys: Array<T[TPrimaryKey]>): Promise<void>;
private tick;
private _hasChanges;
/** Whether the in-memory cache has unflushed mutations. Read-only externally. */
get hasChanges(): boolean;
get(key: T[TPrimaryKey], select?: Array<keyof T>): Promise<T | undefined>;
add(...entries: T[]): Promise<import("@furystack/core").CreateResult<T>>;
find<TFields extends Array<keyof T>>(filter: FindOptions<T, TFields>): Promise<import("@furystack/core").PartialResult<T, TFields>[]>;
count(filter?: FilterType<T>): Promise<number>;
/**
* Writes the in-memory cache to disk if {@link hasChanges} is set. No-op
* otherwise — the periodic tick calls this on every interval but only the
* first call after a mutation actually touches the filesystem.
*/
saveChanges(): Promise<void>;
/**
* Flushes pending changes, closes the FS watcher and clears the tick interval.
* Must be awaited — skipping `await` risks losing the final write.
*/
[Symbol.asyncDispose](): Promise<void>;
/**
* Replaces the in-memory cache with the contents of the backing file. Called
* on construction and on every FS watcher event. Missing file (`ENOENT`) is
* silently ignored so first-run writes succeed against a fresh path.
*/
reloadData(): Promise<void>;
update(id: T[TPrimaryKey], data: T): Promise<void>;
/**
* Test seam — overridable to fault-inject the read path. Defaults to
* `fs.promises.readFile`. Production code should not reassign.
*/
readFile: typeof promises.readFile;
/**
* Test seam — overridable to fault-inject the write path. Defaults to
* `fs.promises.writeFile`. Production code should not reassign.
*/
writeFile: typeof promises.writeFile;
constructor(options: {
fileName: string;
primaryKey: TPrimaryKey;
tickMs?: number;
model: Constructable<T>;
});
}
//# sourceMappingURL=filesystem-store.d.ts.map