Back to Atlas map viewer
SOURCE / PINNED RELEASE

Made of little things.

Atlas map viewer

Release
1d2807e80b44…
Author-recorded commit
43810ed2b368…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-4757dbabc86

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

packages/format/src/store.ts
import { PinnedBlockStore } from "./pinned-store";
import { isRoutingCell } from "./index";
import type { Block } from "./index";
export interface SavedIndex {
  header: Uint8Array;
  index: Uint8Array;
}
export interface BlockStore {
  available: boolean;
  getIndex(id: string): Promise<SavedIndex | undefined>;
  putIndex(id: string, value: SavedIndex): Promise<void>;
  get(snapshot: string, block: Block): Promise<Uint8Array | undefined>;
  put(snapshot: string, block: Block, bytes: Uint8Array): Promise<void>;
  keys(snapshot: string): Promise<string[]>;
}
const eligible = (key: string) =>
  key.startsWith("hashtree/") ||
  key.startsWith("tile/") ||
  key.startsWith("raster/") ||
  key.startsWith("labels/") ||
  key.startsWith("search/") ||
  key.startsWith("features/") ||
  key.startsWith("boundaries/") ||
  key.startsWith("objects/") ||
  key.startsWith("query/") ||
  key.startsWith("index/") ||
  isRoutingCell(key);
const request = <T>(r: IDBRequest<T>) =>
  new Promise<T>((resolve, reject) => {
    r.onsuccess = () => resolve(r.result);
    r.onerror = () => reject(r.error);
  });
const done = (tx: IDBTransaction) => {
  const completion = new Promise<void>((resolve, reject) => {
    tx.oncomplete = () => resolve();
    tx.onerror = tx.onabort = () => reject(tx.error);
  });
  void completion.catch(() => {});
  return completion;
};
/** Compressed, hash-checked immutable blocks. 128 MiB LRU across all snapshots. */
export class BrowserBlockStore implements BlockStore {
  available = true;
  private pins = new PinnedBlockStore();
  private queue: Promise<unknown> = Promise.resolve();
  private db: Promise<IDBDatabase>;
  private listeners = new Set<() => void>();
  subscribe(listener: () => void) {
    this.listeners.add(listener);
    return () => {
      this.listeners.delete(listener);
    };
  }
  private changed() {
    for (const listener of this.listeners) listener();
  }
  /** Drain queued writes, then atomically clear every snapshot and saved index.
   * Callers must dispose readers/workers first so they cannot enqueue new writes.
   * Unlike best-effort caching, a failed purge must be reported to the caller. */
  async clear() {
    const job = this.queue.then(async () => {
      const db = await this.db;
      const tx = db.transaction(["data", "meta", "indexes"], "readwrite");
      const end = done(tx);
      for (const name of ["data", "meta", "indexes"])
        tx.objectStore(name).clear();
      await end;
      this.available = true;
      this.changed();
    });
    this.queue = job.catch(() => {});
    await job;
    await this.pins.clear();
  }
  constructor(readonly limit = 128 * 1024 * 1024) {
    this.db = new Promise((resolve, reject) => {
      const r = indexedDB.open("atlas-blocks-v1", 1);
      r.onupgradeneeded = () => {
        for (const name of ["data", "meta", "indexes"])
          r.result.createObjectStore(name);
      };
      r.onsuccess = () => resolve(r.result);
      r.onerror = () => reject(r.error);
    });
    this.db.catch(() => {
      this.available = false;
    });
  }
  private async safe<T>(
    fn: (db: IDBDatabase) => Promise<T>,
  ): Promise<T | undefined> {
    if (!this.available) return;
    try {
      return await fn(await this.db);
    } catch {
      this.available = false;
      return;
    }
  }
  async getIndex(id: string) {
    return this.safe((db) =>
      request<SavedIndex | undefined>(
        db.transaction("indexes").objectStore("indexes").get(id),
      ),
    );
  }
  async putIndex(id: string, value: SavedIndex) {
    await this.safe(async (db) => {
      const tx = db.transaction("indexes", "readwrite"),
        end = done(tx);
      tx.objectStore("indexes").put(value, id);
      await end;
    });
  }
  private id(snapshot: string, b: Block) {
    return `${snapshot}:${b.key}:${b.hash}`;
  }
  async get(snapshot: string, b: Block) {
    const pinned = await this.pins.get(snapshot, b).catch(() => undefined);
    if (pinned) return pinned;
    if (!eligible(b.key)) return;
    return this.safe(async (db) => {
      const id = this.id(snapshot, b),
        tx = db.transaction(["data", "meta"], "readwrite"),
        end = done(tx);
      const [bytes, meta] = await Promise.all([
        request<Uint8Array | undefined>(tx.objectStore("data").get(id)),
        request<any>(tx.objectStore("meta").get(id)),
      ]);
      if (meta) tx.objectStore("meta").put({ ...meta, used: Date.now() }, id);
      await end;
      return bytes;
    });
  }
  async put(snapshot: string, b: Block, bytes: Uint8Array) {
    if (!eligible(b.key) || bytes.length > this.limit) return;
    const job = this.queue.then(() =>
      this.safe(async (db) => {
        const id = this.id(snapshot, b),
          tx = db.transaction(["data", "meta"], "readwrite"),
          end = done(tx);
        const meta = tx.objectStore("meta");
        const entries = await request<
          Array<{ id: string; bytes: number; used: number }>
        >(meta.getAll());
        let size = entries.reduce(
          (n, e) => n + (e.id === id ? 0 : e.bytes),
          bytes.length,
        );
        for (const e of entries.sort((a, b) => a.used - b.used)) {
          if (size <= this.limit) break;
          if (e.id === id) continue;
          meta.delete(e.id);
          tx.objectStore("data").delete(e.id);
          size -= e.bytes;
        }
        tx.objectStore("data").put(bytes, id);
        meta.put(
          { id, snapshot, key: b.key, bytes: bytes.length, used: Date.now() },
          id,
        );
        await end;
        this.changed();
      }),
    );
    this.queue = job.catch(() => {});
    await job;
  }
  async keys(snapshot: string) {
    const pinned = await this.pins.keys(snapshot).catch(() => []);
    const cached =
      (await this.safe(async (db) => {
        const entries = await request<Array<{ snapshot: string; key: string }>>(
          db.transaction("meta").objectStore("meta").getAll(),
        );
        return entries.filter((e) => e.snapshot === snapshot).map((e) => e.key);
      })) ?? [];
    return [...new Set([...pinned, ...cached])];
  }
}