Back to Body Journal
SOURCE / PINNED RELEASE

Made of little things.

Body Journal

Release
e8049f8da4e2…
Author-recorded commit
61c04bf0e8aa…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-6c4554844c7

Archive hash verified: 4369d778da046a8b…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/session-store.ts
// One writer per mounted session. Edits during a write are coalesced into the next snapshot.
export class SessionStore<T> {
  loading = true;
  blocked = false;
  clearing = false;
  dirty = false;
  message = 'Restoring session…';
  private writing?: Promise<void>;
  private stopped = false;
  constructor(private io: {
    read: () => Promise<string | null>; write: (raw: string) => Promise<void>; remove: () => Promise<void>;
    parse: (raw: string) => T; snapshot: () => string; restore: (record: T) => void; reset: () => void; changed: () => void;
  }) {}
  async init(): Promise<void> {
    this.loading = true; this.io.changed();
    try {
      const raw = await this.io.read();
      if (this.stopped) return;
      if (raw) this.io.restore(this.io.parse(raw));
      this.blocked = false; this.dirty = false; this.message = raw ? 'Session restored · autosave on' : 'Autosave on';
    } catch { this.blocked = true; this.message = 'Session could not be restored. Stored data was left untouched. Retry restoring, or save your current session to replace it.'; }
    finally { this.loading = false; if (!this.stopped) this.io.changed(); }
  }
  change(): void {
    if (this.loading || this.clearing || this.stopped) return;
    this.dirty = true;
    if (!this.blocked) void this.flush();
  }
  async flush(retry = false): Promise<void> {
    if (this.loading || this.clearing || this.stopped) return;
    if (retry) { this.blocked = false; this.dirty = true; }
    if (this.blocked) return;
    if (this.writing) return this.writing;
    // The microtask lets all synchronous handlers finish before taking a snapshot.
    this.writing = Promise.resolve().then(async () => {
      while (this.dirty && !this.clearing && !this.stopped) {
        this.dirty = false; this.message = 'Saving session…'; this.io.changed();
        try { const raw = this.io.snapshot(); await this.io.write(raw); this.message = 'Session saved'; }
        catch { this.dirty = true; this.blocked = true; this.message = 'Session not saved. Your work is still here. Open Session and retry saving.'; break; }
      }
    }).finally(() => {
      this.writing = undefined;
      if (!this.stopped) this.io.changed();
      if (this.dirty && !this.blocked && !this.clearing && !this.stopped) void this.flush();
    });
    return this.writing;
  }
  async clear(): Promise<void> {
    if (this.loading || this.clearing || this.stopped) return;
    this.clearing = true; this.message = 'Clearing session…'; this.io.changed();
    await this.writing;
    try {
      await this.io.remove();
      this.io.reset(); this.dirty = false; this.blocked = false; this.message = 'Session cleared · autosave on';
    } catch { this.blocked = true; this.message = 'Session could not be cleared. Your work is still here. Try again.'; }
    finally { this.clearing = false; this.io.changed(); }
  }
  stop(): void { this.stopped = true; }
}