コンテンツにスキップ

ADR-0057: 図とスライドは、一覧に出す値と中身を 2 つの object store に分けて、訪問者のファイルと別のデータベースに保存する

Status

承認

Context

draw.io のツール、Excalidraw のツール、スライドのエディタが、IndexedDB に保存するデータの形を決める必要がある。
この形は、3 つのツールと、AI のエージェントからの操作で共通にする。

Decision

1. データベースと object store

図とスライドは、play.fukuemon.dev の IndexedDB の、訪問者のファイルと別のデータベース(playground-documents)に保存する。
データベースは、2 つの object store を持つ。

object store キー レコード index
documents id 一覧に出す値(DocumentMeta) [tool, updatedAt]
contents id 図かスライドの中身(DocumentContent) 無い
type ToolKind = "drawio" | "excalidraw" | "slides";

type DocumentMeta = {
  id: string;
  tool: ToolKind;
  name: string;
  createdAt: number;
  updatedAt: number;
  source?: { kind: "figure"; name: string };
};

type DocumentContent = {
  id: string;
  data: string;
};
  • id は、crypto.randomUUID() で作る。2 つの store のレコードは、同じ id で対応する。
  • name は、重なってよい。図とスライドは、id で見分ける。
  • createdAt と updatedAt は、エポックからのミリ秒の数値にする。一覧は、[tool, updatedAt] の index で、ツールごとに、更新した時刻の順に読む。
  • source は、記事の図から作った図だけが持つ。source.name は、記事の図の名前である。
  • data は、ツールのファイルの形式の文字列を、そのまま入れる。draw.io は図の XML、Excalidraw は .excalidraw の JSON、スライドは Markdown である。
  • 作る、中身を保存する、消す、の 3 つの操作は、2 つの store を 1 つのトランザクションで書く。名前を変える操作は、documents だけを書く。
  • サムネイルは持たない。
  • 件数と容量の上限は置かない。

2 つの store に分ける理由は、一覧が中身を読まずに済むためである。
data をファイルの形式の文字列にする理由は、ファイルに保存する操作と、ファイルを開く操作が、同じ文字列を使えるためである。
別のデータベースにする理由は、workspace/ と、図とスライドを保存するモジュールが、データベースのバージョンを別々に上げられるためである。

2. ツールごとに足す値

ツールが、data のほかに保存する値を持つときは、DocumentContent に、省略できる値を足す。
DocumentMeta には、一覧が使う値だけを置く。
一覧が、ツールごとの値を読まずに済むようにするためである。

3. 読み書きできないとき

  • IndexedDB を読めないときは、保存した図とスライドが無いときと同じに扱い、保存できないことを画面に出す。その後は、ページを開いている間、IndexedDB に書き込まない。
  • 保存の容量が足りなくて書き込めないときも、保存できないことを画面に出す。このときは、その後の書き込みを止めない。訪問者が、図かスライドを消して、容量を空けられるようにするためである。

4. 2 つのタブで同じ図かスライドを開いたとき

保存の前に、ほかのタブの変更を確認しない。後から保存したタブの中身が残る。
確認しない理由は、どちらのタブの中身を残すかを選ぶ画面を、作らずに済むためである。

Considered Options

レコードの分け方の選択肢は次のとおり。

選択肢 強み 弱み
採用: 一覧に出す値と中身を、2 つの object store に分ける 一覧が、中身を読まない。サムネイルのような値を、後から store で足せる 作る、保存する、消す操作が、2 つの store を 1 つのトランザクションで書く
1 つのレコードに、名前、識別子、ツールの種類、中身、時刻を持つ 作るものが少ない 一覧を出すたびに、全ての図とスライドの中身を読む。画像を貼った Excalidraw の図は、中身が大きい

データベースの選択肢は次のとおり。

選択肢 強み 弱み
採用: 訪問者のファイルと別のデータベースにする 2 つのモジュールが、データベースのバージョンを別々に上げられる データベースが 2 つになる
訪問者のファイルと同じデータベースに、store を足す データベースが 1 つで済む store を足すたびに上げるバージョンを 1 か所で持つので、workspace/ と、図とスライドを保存するモジュールが、バージョンを持つ 1 つのモジュールに依存する

2 つのタブで開いたときの選択肢は次のとおり。

選択肢 強み 弱み
採用: 確認しない。後から保存したタブの中身が残る 作るものが無い 片方のタブの編集が、知らせなしに消える
保存の前に更新した時刻を比べ、ほかのタブが先に保存していたら、保存せずに知らせる 片方のタブの編集が、知らせなしに消えない 知らせた後に、どちらを残すかを選ぶ画面が要る

Consequences

Positive

  • 一覧は、図とスライドの数に応じた大きさの値だけを読む。
  • 3 つのツールと、AI のエージェントからの操作が、同じ形のレコードを読み書きする。
  • 保存した中身は、ツールのファイルとして、そのまま書き出せる。

Negative

  • 図とスライドの中身は、編集のたびに、全体を 1 つの文字列として書き直す。
  • 2 つのタブで同じ図かスライドを編集すると、片方の編集が、知らせなしに消える。
  • 訪問者が保存した図とスライドが増えると、ブラウザの保存の容量を使う。容量が足りなくなるまで、訪問者は気づかない。
  • play.fukuemon.dev の IndexedDB のデータベースが、2 つになる。