@zenfs/core
Version:
A filesystem, anywhere
147 lines (123 loc) • 3.66 kB
text/typescript
import type { Ino } from '../../inode.js';
import { SyncTransaction, type Store } from './store.js';
/**
* An interface for simple synchronous stores that don't have special support for transactions and such.
*/
export interface SimpleSyncStore extends Store {
get(ino: Ino): Uint8Array | undefined;
set(ino: Ino, data: Uint8Array): void;
delete(ino: Ino): void;
}
/**
* An interface for simple asynchronous stores that don't have special support for transactions and such.
* This class adds caching at the store level.
*/
export abstract class SimpleAsyncStore implements SimpleSyncStore {
public abstract name: string;
protected cache: Map<Ino, Uint8Array> = new Map();
protected queue: Set<Promise<unknown>> = new Set();
protected abstract entries(): Promise<Iterable<[Ino, Uint8Array]>>;
public get(ino: Ino): Uint8Array | undefined {
return this.cache.get(ino);
}
public set(ino: Ino, data: Uint8Array): void {
this.cache.set(ino, data);
this.queue.add(this._set(ino, data));
}
protected abstract _set(ino: Ino, data: Uint8Array): Promise<void>;
public delete(ino: Ino): void {
this.cache.delete(ino);
this.queue.add(this._delete(ino));
}
protected abstract _delete(ino: Ino): Promise<void>;
public clearSync(): void {
this.cache.clear();
this.queue.add(this.clear());
}
public abstract clear(): Promise<void>;
public async sync(): Promise<void> {
for (const [ino, data] of await this.entries()) {
if (!this.cache.has(ino)) {
this.cache.set(ino, data);
}
}
for (const promise of this.queue) {
await promise;
}
}
public transaction(): SimpleTransaction {
return new SimpleTransaction(this);
}
}
/**
* Transaction for simple stores.
* @see SimpleSyncStore
* @see SimpleAsyncStore
*/
export class SimpleTransaction extends SyncTransaction<SimpleSyncStore> {
/**
* Stores data in the keys we modify prior to modifying them.
* Allows us to roll back commits.
*/
protected originalData: Map<Ino, Uint8Array | void> = new Map();
/**
* List of keys modified in this transaction, if any.
*/
protected modifiedKeys: Set<Ino> = new Set();
protected declare store: SimpleSyncStore;
public getSync(ino: Ino): Uint8Array {
const val = this.store.get(ino);
this.stashOldValue(ino, val);
return val!;
}
public setSync(ino: Ino, data: Uint8Array): void {
this.markModified(ino);
return this.store.set(ino, data);
}
public removeSync(ino: Ino): void {
this.markModified(ino);
this.store.delete(ino);
}
public commitSync(): void {
this.done = true;
}
public abortSync(): void {
if (!this.done) {
return;
}
// Rollback old values.
for (const key of this.modifiedKeys) {
const value = this.originalData.get(key);
if (!value) {
// Key didn't exist.
this.store.delete(key);
} else {
// Key existed. Store old value.
this.store.set(key, value);
}
}
this.done = true;
}
/**
* Stashes given key value pair into `originalData` if it doesn't already
* exist. Allows us to stash values the program is requesting anyway to
* prevent needless `get` requests if the program modifies the data later
* on during the transaction.
*/
protected stashOldValue(ino: Ino, value?: Uint8Array): void {
// Keep only the earliest value in the transaction.
if (!this.originalData.has(ino)) {
this.originalData.set(ino, value);
}
}
/**
* Marks `ino` as modified, and stashes its value if it has not been
* stashed already.
*/
protected markModified(ino: Ino): void {
this.modifiedKeys.add(ino);
if (!this.originalData.has(ino)) {
this.originalData.set(ino, this.store.get(ino));
}
}
}