diff --git a/libs/common/src/tools/ring-buffer.ts b/libs/common/src/tools/ring-buffer.ts new file mode 100644 index 00000000000..9dee6c42e90 --- /dev/null +++ b/libs/common/src/tools/ring-buffer.ts @@ -0,0 +1,55 @@ +/** A simple ring buffer; not in any way threadsafe */ +export class RingBuffer { + constructor(private capacity: number) { + this.buffer = new Array(capacity).fill(null); + } + + private buffer: Array; + private head: number = 0; + private tail: number = 0; + private _length: number = 0; + + /** The number of entries presently stored by the ring buffer */ + get length() { + return this._length; + } + + /** `true` when the buffer is full. */ + get full() { + return this.length === this.capacity; + } + + /** `true` when the buffer is empty */ + get empty() { + return !this.length; + } + + /** Adds an item to the head of the buffer + * @param value the item to add + * @returns `true` if the item was added, otherwise `false`. + */ + enqueue(value: T) { + if (this.full) { + return false; + } + + this.buffer[this.head] = value; + this.head = (this.head + 1) % this.capacity; + this._length++; + } + + /** Removes the item at the tail of the buffer + * @returns the tail item if the buffer contains any entries, + * otherwise `undefined`. + */ + dequeue(): T | undefined { + if (this.empty) { + return undefined; + } + + const value = this.buffer[this.tail]; + this.tail = (this.tail + 1) % this.capacity; + this._length--; + return value; + } +} diff --git a/libs/common/src/tools/state/spooler.ts b/libs/common/src/tools/state/spooler.ts new file mode 100644 index 00000000000..0f6d4252617 --- /dev/null +++ b/libs/common/src/tools/state/spooler.ts @@ -0,0 +1,47 @@ +import { firstValueFrom } from "rxjs"; + +import { StateProvider, UserKeyDefinition } from "../../platform/state"; +import { UserId } from "../../types/guid"; + +/** Utility for spooling data to and from append-only storage. */ +export class Spooler { + /** Instantiates a spooler + * @param state loads and stores the spool + * @param location where spooled records are stored + * @param userId user performing the spooling + */ + constructor( + private state: StateProvider, + private location: UserKeyDefinition, + private userId: UserId, + ) {} + + private buffer = new Array(); + + /** Append a value to append-only storage */ + async spool(value: T) { + // TODO: encrypt spooled records? Or should that be done by the calling code? + // either way, the value pushed to `this.buffer` should be ready-to-spool. + this.buffer.push(value); + + await this.state.setUserState(this.location, this.buffer, this.userId); + } + + /** Read all values from append-only storage */ + async read(): Promise { + // TODO: decrypt spooled records? Or should that be done by the calling code? + return await firstValueFrom(this.state.getUserState$(this.location, this.userId)); + } + + /** Read all values from append-only storage */ + async unspool(): Promise { + const results = await this.read(); + await this.clear(); + return results; + } + + /** Erase append-only storage */ + async clear() { + await this.state.setUserState(this.location, null, this.userId); + } +}