ADR-0057: 図とスライドは、一覧に出す値と中身を 2 つの object store に分けて、訪問者のファイルと別のデータベースに保存する¶
Status¶
承認
Context¶
draw.io のツール、Excalidraw のツール、スライドのエディタが、IndexedDB に保存するデータの形を決める必要がある。
この形は、3 つのツールと、AI のエージェントからの操作で共通にする。
- 3 つのツールは、名前を付けた複数の図かスライドを IndexedDB に保存し、訪問者は一覧から開くものを選ぶ。保存するデータの形は、3 つのツールでそろえる。訪問者が記事の図をキャンバスで開いたときに保存する図は、新しい図にし、保存してあった図は変えない。保存するのは、訪問者が最初に編集したときである。
- draw.io の図は、親のページが XML で受け取る。Excalidraw の図は、
.excalidrawの JSON である。 - スライドのエディタは、Markdown を保存する。
- 訪問者のファイルは、
play.fukuemon.devの IndexedDB に、ツールの名前と埋め込みの ID の組をキーにして保存する。IndexedDB を読めないときは、保存したものが無いときと同じに扱い、保存できないことを画面に出し、ページを開いている間は書き込まない。 - 訪問者のファイルを保存するモジュール(
workspace/)は、apps/playground/src/の、ほかの関心ごとのディレクトリを import しない。 - 記事の図は、キャンバスに、図の名前で渡す。
.excalidrawの JSON は、訪問者が貼った画像を中に持つ。- IndexedDB は、レコードの一部の値だけを取り出して読むことができず、レコードの全体を読む。
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 つになる。