-
Notifications
You must be signed in to change notification settings - Fork 0
Backend less Cloud Sync
The Boosters の同期スキームの完全なロジック記述です。アプリはサーバを持たず、
ノートをローカルのプレーンファイルとして保存し、その保存フォルダを OS のクラウド
同期クライアントに任せる、という設計です。これは新規構想ではなく 既に実装・
文書化済みの現行アーキテクチャで、docs/ONEDRIVE-DESKTOP-SETUP.md に OneDrive
向けの手順があり、設定画面(Storages タブ)からリンクされています。
Device A Device B
┌────────────┐ ┌────────────┐
│ The Boosters│ │ The Boosters│
│ (Electron) │ │ (Electron) │
└─────┬──────┘ └──────┬─────┘
│ read/write local files │
▼ ▼
┌──────────────────────────────────────────────┐
│ クラウド同期フォルダ内の storage/ │
│ ├─ boostnote.json (folders[], version) │
│ ├─ notes/<key>.cson (1 note = 1 file) │
│ └─ attachments/<noteKey>/... (画像等) │
└──────────────────────────────────────────────┘
▲ ▲
└──── OneDrive / Dropbox / iCloud ───┘
Google Drive などが実ファイルを双方向同期
アプリの責務は「ローカルフォルダを読み書きする」ことだけ。ネットワーク通信・ 認証・サーバは一切ありません。同期の実体は OS のクラウドクライアントです。
補足: リポジトリ内で手順が明文化されているのは OneDrive(
docs/ONEDRIVE-DESKTOP-SETUP.md)。 Dropbox / iCloud Drive / Google Drive も 同一の仕組み(任意のフォルダ同期 クライアント)で同じように動きますが、個別手順の明文化は OneDrive のみです。
1 つの「ストレージ」= ディスク上の 1 フォルダ。フォルダのメタデータは
<storagePath>/boostnote.json に入ります。
- 参照実装:
resolveStorageData.js(存在しなければ{ folders: [], version: '1.0' }を自動生成) - 書き込みは
_.pick(storage, ['folders','version'])なので、この 2 キーだけが保存される。
各ノートは <storagePath>/notes/<key>.cson。serialize/parse は @rokt33r/season(CSON)。
# <storagePath>/notes/<uuid>.cson (MARKDOWN_NOTE の例)
createdAt: '2026-07-16T00:00:00.000Z'
updatedAt: '2026-07-16T00:00:00.000Z'
type: 'MARKDOWN_NOTE'
folder: '<folderKey>'
title: 'First note'
tags: [ 'work', 'idea' ]
isStarred: false
isTrashed: false
content: '# First note\n\n本文…'
linesHighlighted: []-
keyとstorageはファイルに書かれない。読み込み時に ファイル名(<key>.cson)と 親ストレージから復元される(resolveStorageNotes.jsがdata.key = basename(file,'.cson')、data.storage = storage.keyを注入)。生の.csonを開いても key が無いのはこのため。 -
SNIPPET_NOTE の場合は
content/linesHighlightedの代わりにdescriptionとsnippets[](各{ name, mode, content, linesHighlighted })を持つ。 - キー生成: ノートは
keygen(true)= uuid v4、ストレージはkeygen()= 10 バイト hex。
<storagePath>/attachments/<noteKey>/<filename>。本文中では 相対プレースホルダ
:storage/<noteKey>/<filename> で参照し、描画時にのみ実 file://…/attachments/… に
書き換える(attachmentManagement.js、fixLocalURLS)。
重要: 参照が相対プレースホルダなので、フォルダごと移動・同期してもリンクは 壊れません。逆に言うと、同期は
notes/・attachments/・boostnote.jsonを 3 点セットで運ぶ必要があります。
Main.js: dataApi.init() ──▶ INIT_ALL { storages, notes } を dispatch
├─ fetchStorages(): localStorage['storages'] を読む
│ └─ fs.existsSync(path) が false のストレージは除外(!)
├─ resolveStorageData(): 各 boostnote.json から folders/version を読む
└─ fetchNotes(): resolveStorageNotes() が notes/*.cson を全読み込み
-
dataApi.init()の呼び出しは 起動時の 1 回のみ(Main.js)。 - 以降、Redux ストアは アプリ自身の書き込みの戻り値でのみ更新される。 セッション中に notes ディレクトリを再スキャンしない。
-
createNote: 衝突しない uuid を選び、_.omit(noteData, ['key','storage'])を CSON で書く。 -
updateNote: 既存.csonを読み、Object.assign(…, input, { updatedAt: new Date() })で上書き。 -
deleteNote:sander.unlinkSyncで削除 →attachments/<noteKey>/も削除。
いずれも書き込み後、戻り値のノートオブジェクトで UPDATE_NOTE 等を dispatch し、
UI に反映する。ディスクを再読込しないのがポイント。
| データ | 場所 | 同期される? |
|---|---|---|
| ノート本文+メタ | <storagePath>/notes/*.cson |
✅(クラウドフォルダ内なら) |
| フォルダ定義・version | <storagePath>/boostnote.json |
✅ |
| 添付ファイル | <storagePath>/attachments/** |
✅ |
| ストレージ一覧(開く場所) | localStorage['storages'] |
❌ マシンローカル |
| アプリ設定・テーマ・ホットキー・AI キー |
localStorage['config'] + ~/.boostnoterc
|
❌ マシンローカル |
帰結: 新デバイスでは、クラウド同期フォルダ内の storage を **一度手動で「ストレージを 追加」**する必要がある(同期されるのはフォルダの中身であって「どのフォルダを開くか」 の設定ではない)。テーマ・ホットキー・AI キーも各デバイスで個別設定になる。
このスキームには アプリ内の競合解決が存在しません。意図的な割り切りです。
-
ライブ監視なし: notes ディレクトリのファイル変更を監視する watcher は無い
(唯一の
fs.watchFileは dev ビルドバンドル用で、ノートとは無関係)。 → 他デバイスがクラウド経由で書き込んでも、アプリ再起動まで反映されない。 -
競合解決なし:
updatedAt比較やマージは行わない。2 台でオフライン同時編集すると、 クラウドクライアント側がファイル単位の競合コピー(例:name-PC名.cson)を作る。 ユーザーが手動でどちらを残すか決める。 - Last-Write-Wins のサイレント損失の可能性(オンライン同時上書き時)。
- 設定は非同期(§4)。
- 1 台での編集を終えてから、クラウド同期の完了を待って別デバイスを開く。
- 別デバイスで編集する前に アプリを再起動して最新をロードする。
- 競合コピーができたら手動で解消する(片方を残す)。
- ストレージフォルダは常に存在させる(起動時
fs.existsSyncで消えたパスは一覧から除外される)。
| 得られるもの | 代償 |
|---|---|
| サーバ運用ゼロ・コストゼロ・認証情報ゼロ | リアルタイム同時編集は不可 |
| プレーンファイルなので Git 連携・grep・バックアップが容易 | 競合解決は手動 |
| プレーンテキストが自分の管理下から出ない(プライバシー) | セッション中の外部変更を自動反映しない |
| 実装が最小(クラウド同期は OS に委譲) | 新デバイス登録・設定は手動 |
将来リアルタイム多デバイス編集が必須要件になった場合は、Yjs CRDT +
self-hosted Hocuspocus への移行が別トラックで検討されている(docs/MODERNIZATION-2026-stack-selection.md)。
現行のフォルダ同期はそのベースライン。
-
ESM/CJS × Vite:
deleteNote.js/moveNote.js/deleteFolder.jsは必ずexport defaultを使う。importを含むファイルにmodule.exports =を書くと、 Vite ビルドで 黙って空モジュールになる(jest/ava は緑のまま)。CommonJS に 「整理」してはいけない。 - key/storage はファイルに無い(§2.2)。ファイル名と親ストレージから復元される。
- 起動時の存在チェック: 一時的に未マウント / クラウドで「容量解放」されたパスは 起動時に一覧から落ちる。フォルダ自体は常に存在させる。
関連ページ: 技術スタック ・ Home
リポジトリ内資料: docs/ONEDRIVE-DESKTOP-SETUP.md, docs/MODERNIZATION-2026-stack-selection.md