(ws: WebSocket, message: string | ArrayBuffer)
| 912 | } |
| 913 | |
| 914 | // --- Tree (full graph for visualization) --- |
| 915 | if (request.method === "GET" && path.match(/^\/notes\/[^/]+\/tree$/)) { |
| 916 | const noteId = path.split("/")[2]; |
| 917 | this.ensureDefaultBranch(noteId); |
| 918 | const branches = this.sql.exec( |
| 919 | "SELECT id, name, head_version_id, is_default, created_at FROM note_branches WHERE note_id = ?", noteId |
| 920 | ).toArray(); |
| 921 | const current = this.getCurrentBranch(noteId); |
| 922 | const versions = this.sql.exec( |
| 923 | `SELECT id, parent_id, branch_id, title, is_checkpoint, created_by, kind, summary, created_at FROM note_versions |
| 924 | WHERE note_id = ? ORDER BY created_at DESC LIMIT 1000`, noteId |
| 925 | ).toArray(); |
| 926 | const note = this.getNote(noteId); |
| 927 | return Response.json({ |
| 928 | branches: branches.map((b: any) => ({ ...b, is_current: b.id === current.id ? 1 : 0 })), |
| 929 | versions, |
| 930 | sync_mode: note?.sync_mode || "cloud", |
| 931 | }); |
| 932 | } |
| 933 | |
| 934 | // Note history — git-style version list |
| 935 | if (request.method === "GET" && path.match(/^\/notes\/[^/]+\/history$/)) { |
| 936 | const noteId = path.split("/")[2]; |
| 937 | const rows = this.sql.exec( |
| 938 | `SELECT id, note_id, title, is_checkpoint, branch_id, created_by, kind, summary, created_at FROM note_versions |
| 939 | WHERE note_id = ? ORDER BY created_at DESC LIMIT 1000`, noteId |
| 940 | ).toArray(); |
| 941 | return Response.json(rows); |
| 942 | } |
| 943 | // Reconstruct a specific version's full content |
| 944 | if (request.method === "GET" && path.match(/^\/notes\/[^/]+\/history\/[^/]+$/)) { |
| 945 | const parts = path.split("/"); |
| 946 | const noteId = parts[2]; |
| 947 | const versionId = parts[4]; |
| 948 | const row = this.sql.exec( |
| 949 | "SELECT id, note_id, title, is_checkpoint, created_by, kind, summary, created_at FROM note_versions WHERE id = ? AND note_id = ?", |
| 950 | versionId, noteId |
| 951 | ).toArray()[0]; |
| 952 | if (!row) return new Response("Version not found", { status: 404 }); |
| 953 | const content = await this.reconstructVersion(versionId, noteId); |
| 954 | return Response.json({ ...row, content }); |
| 955 | } |
| 956 | // Restore to a specific version |
| 957 | if (request.method === "POST" && path.match(/^\/notes\/[^/]+\/history\/restore$/)) { |
| 958 | const noteId = path.split("/")[2]; |
| 959 | const { version_id } = (await request.json()) as { version_id: string }; |
| 960 | const row = this.sql.exec( |
| 961 | "SELECT id FROM note_versions WHERE id = ? AND note_id = ?", version_id, noteId |
| 962 | ).toArray()[0]; |
| 963 | if (!row) return new Response("Version not found", { status: 404 }); |
| 964 | |
| 965 | const restoredContent = await this.reconstructVersion(version_id, noteId); |
| 966 | |
| 967 | // Version the current state before restoring (so restore itself is reversible) |
| 968 | const current = this.getNote(noteId); |
| 969 | if (current?.content) { |
| 970 | await this.createVersion(noteId, (current.title as string) || "Untitled", current.content as string, "auto-backup", "backup", "Auto-backup before restore"); |
| 971 | } |
nothing calls this directly
no test coverage detected