この記事を作った動機

 2025/9/10 から、今日に至るまでの間で OneNote 代替に関して、実装をするなど研究を進めているうちにわかったことなどを簡易的に記録する。

Networking

フロントエンドがレスポンスを識別できない可能性

 作っている途中でわかったこととして、今回の websocket による実装だと、無数の React コンポーネントがデータサーバに対してリクエストを送った場合、どのコンポーネントがリクエストを送ったのかレスポンスが帰ってきたときにわからないという問題があった。

 具体的には、リクエストに対して、”誰”がリクエストを送ったのかという識別子がないことと、バックエンドからメッセージが帰ってくると一斉に、addEventListener で onmessage イベントを待っているコンポーネント全体にメッセージが届いてしまい、結果としてレスポンスの受け取りで整合性が取れなくなるというものである。

 そこで今回は、各リクエストに対し、UUIDを付与し、データサーバから返信するときはそのUUIDとリクエストしたコマンド名をレスポンスに含めるようにすることで、フロントエンドの各コンポーネントが自分が送ったリクエストかどうか識別できるようにした。

 ちなみに今回のような問題は HTTP プロトコルで実装すると起こらなそうである。

パフォーマンスの懸念

 フロントエンド側の websocket の addEventListener の onmessage イベントは現状の実装だと、コンポーネントの数次第でその数だけコールバック関数があり、処理が走ることになる。

 そのため、その数だけ自分に対するリクエストのレスポンスかどうか識別することになり、パフォーマンスにおいて懸念がある。とりあえずは顕著になるまではこの問題はおいておこうと思う。

データサーバとフロントエンドが分離しているアーキテクチャについて

 現状のアーキテクチャだと、例えばリモートのデータサーバにアクセスしていて、必ずしもネットワークが安定していなかったり、一時的にオフラインになる可能性がある環境において、安定しないことが考えられた。

 そこで、ローカルでスタンドアロンで動かすときと、データサーバがVPN経由などで遠隔地にある場合の2つで異なる考え方をすることにしてみた。

スタンドアロンでデータサーバもローカルで動いているとき

 単純にデータサーバが一つ起動し接続される。  

データサーバがリモートにあるとき

 フロントエンドが動いているクライアント側にもcacheとしてデータサーバーがある。開いたページやインデックスを保持したりする。

 フロントエンドは、リモートのデータサーバと直接通信せず、フロントエンド側にあるデータサーバからリダイレクトしてもらうように指示する。

 cacheは通常のノートブックと同じようなデータ形式で保存する。

 cacheには、notebook を保存する場所にしてされたフォルダ内に、サーバーごとに専用のフォルダが作られる。

 serverName はホスト名やIPアドレスとする。  

  • notebooksFolderRoot
    • localNotebook1
    • localNotebook2
    • localNotebook3 …
    • serverName-cache
      • remoteNotebook1
      • remoteNotebook2
      • remoteNotebook3 …

各ページと各ファイルのメタデータ

 メタデータの扱いで悩んだこと。info と pageInfo コマンドを作っていて悩んだことがある。

各ページのメタデータ

 今の仕組みだとpageとしては、markdown 形式と、独自に自由に定義できる json 形式のものがあり、json形式の物に関してはどんなファイルが添付されているかとか、どんなタグが付いているかとか、独自に定義して、容易に管理することが可能である。

{
    "pageType": "free",
    "tags": ["This","is","testpage"],
    "files": ["testfile.txt"],
    "pageData":{ /* page data */ }
}

 しかし、markdown については、あんまり markdown に特殊なデータを埋め込みたくないことから、専用の.metadataファイルでも設置しようかと悩んだ。

 ただそれだと処理が煩雑になるので、markdown 内にメタデータを埋め込むということも考えたが、現状の実装ではとりあえず markdown については、メタデータの実装はスキップする方針でいる。

 ちょっと markdown にメタデータを埋め込むならば、Obsidian あたりを真似して、コードブロックみたいなのに JSON でも埋め込んでしまうとかでもいいかもしれない。

```metadata-OneNoteAlternative
{
    "tags": ["This","is","testpage"],
    "files": ["testfile.txt"]
}
```

各ファイルのメタデータ

 ファイルについてはただファイル単体が存在しているだけだと、実装上都合が悪い場合は、拡張子を .metadata として専用のメタデータ保持ファイルを作ろうかと考えてはいる。もし専用のメタデータファイルを生成するようにするなら、中身は基本 JSON で統一する予定である。

 これだと、じゃあ対象のファイル自体が .metadata という拡張子を持っていたらどうするかという問題はあり、現状ではまだ深くは考えていない。何かしら識別する仕組みが必要なことは、現状把握していることではある。

file.txt
file.txt.metadata

サンプルデータ作成

ファイル構造

 なお、index フォルダは廃止して、シンプルに全部一つの notebook 相当の、metadata.json に統合しようかと考えている。

├── notebookData
│   └── test
│       ├── contents
│       │   ├── test.json
│       │   └── test.md
│       ├── files
│       │   └── testfile.txt
│       ├── index
│       │   ├── files.json
│       │   └── tags.json
│       └── metadata.json

 あとから、index フォルダーについて記述を見返したら思ったより複雑でタグ管理機能と密接に関係があることを思い出した。index フォルダーはやっぱり廃止しないことにした。

index data format

tags.json
{
    "tagName1": ["/path/to/content.md","/path/to/content.json",...],
    "tagName2": ["/path/to/content.md","/path/to/content.json",...]
    ...
}
files.json
{
    "fileName1":{
        "original"  : "/path/to/originalFileResourcesWhereAdded.md",
        "refs"      : ["/path/to/ThisFileResourceBeingUsed","/path/to/ThisFileResourceBeingUsed1"...],
        "filePath"  : "/path/to/originalFileSavedWithFileNameAndExtention"
    },
    "fileName2":{
        "original"  : "/path/to/originalFileResourcesWhereAdded.md",
        "refs"      : ["/path/to/ThisFileResourceBeingUsed","/path/to/ThisFileResourceBeingUsed1"...],
        "filePath"  : "/path/to/originalFileSavedWithFileNameAndExtention"
    }
    ...
}

tagging system

  • for each files and contents, have a record what tags are belongs to.
  • for each tags, have a record what contents and files are belongs to.

内容

metadata.json

 name の項目は、notebook のルートフォルダー名でなければならない。id の項目は単なる UUIDである。どんなページやファイルが notebook 内に存在するか示すメタデータがある。

{
    "name"      : "test",
    "createDate": "2025/09/13",
    "updateDate": "2025/09/13",
    "id"        : "bb6f7d1c-c722-495c-923b-ab9e9574ef5b",
    "pages"     : ["test.md","test.json"],
    "files"     : ["testfile.txt"]
}

contents

 z という項目は 2D における z-index という意味で、3D 対応とかそういうことではない。

  • test.json
{
    "pageType": "free",
    "tags": ["This","is","testpage"],
    "files": ["testfile.txt"],
    "pageData":{
        "items":[
            {
                "ID": "3d3751c3-20ee-4124-90df-464860620f4a",
                "type": "text",
                "data": "This is test text. Yukkuri reimu said 'ゆっくりしていってね'.",
                "position":{
                    "x": 200,
                    "y": 100,
                    "z": 0
                }
            },
            {
                "ID": "ef3429f5-f150-4ad2-a325-80257fad8f6b",
                "type": "text",
                "data": "This is test text. Yukkuri marisa abuse Yukkuri reimu.",
                "position":{
                    "x": 200,
                    "y": 150,
                    "z": 1
                }
            }
        ]
    }
}
  • test.md
# hello world
This is test content.

files

  • testfile.txt
nyanmo husahusa

index

 廃止予定なので、特に載せることはない。

エラーハンドリングの実装

コマンド毎に必要なキーがない時のエラーをすべてのコマンドに共通で実装

  • レスポンス例
{
    "status": "error",
    "errorMessage": "Mandatory data keys are missing or malformed.",
    "UUID":"UUID string",
    "command": "[commandName]",
    "data":{
        "missing": ["missingOrMalformed","key","names"]
    }
}

参考にしたサイトとか