PageSourceSearch

https://smartcone.ai/src/shell/types/mindmap/mindmapDb.js

js smartcone.ai collected 2026-10-05 15:07:23 UTC 6,626 bytes, 183 lines download raw bytes

1// Mind Map Database Object
2// Provides local and remote storage for Mind Map data
3// (including extended edge metadata and node field bindings).
4//
5// Local:  localStorage   (key: mindmap-db-${id}) — offline cache only
6// Remote: zim ws-studio  (eShellDoc store via shellDocStore) — system of record
7//
8// Usage:
9//   const db = createMindMapDb("my-mindmap-id");
10//   db.saveLocal(ndjson);                 // cache
11//   const ndjson = db.loadLocal();        // cache
12//   await db.saveApi(ndjson);             // persist to zim (write-through to cache)
13//   const ndjson = await db.loadApi();    // load from zim (null => fall back to cache)
14
15import { saveDoc, loadDoc, listDocs, listVersions, loadVersion } from "../../common/shellDocStore.js";
16
17const SHELL_TYPE   = "mindmap";
18const LOCAL_KEY    = (id) => `mindmap-db-${id || "default"}`;
19const UPDATE_EVENT = "mindmap-db:updated";
20
21/**
22 * createMindMapDb(id)
23 * Returns a database object scoped to this mind map ID.
24 * Synchronous methods for local storage; async methods for backend API.
25 *
26 * @param {string} id  Mind map identifier (used as storage key).
27 */
28export function createMindMapDb(id) {
29  const key = LOCAL_KEY(id);
30
31  return {
32    /** The mind map ID this db is scoped to. */
33    id,
34
35    // ── Local Storage ──────────────────────────────────────────────────────────
36
37    /**
38     * Save NDJSON string to localStorage.
39     * @param {string} ndjson  Serialised mind map.
40     * @returns {boolean}  true on success.
41     */
42    saveLocal(ndjson) {
43      try {
44        localStorage.setItem(key, ndjson);
45        window.dispatchEvent(
46          new CustomEvent(UPDATE_EVENT, { detail: { id, action: "save" } })
47        );
48        return true;
49      } catch (err) {
50        console.error("Mind Map local save failed:", err);
51        return false;
52      }
53    },
54
55    /**
56     * Load NDJSON string from localStorage.
57     * @returns {string|null}  NDJSON data or null if not found.
58     */
59    loadLocal() {
60      try {
61        return localStorage.getItem(key) || null;
62      } catch {
63        return null;
64      }
65    },
66
67    /**
68     * Delete saved data from localStorage.
69     */
70    clearLocal() {
71      try {
72        localStorage.removeItem(key);
73        window.dispatchEvent(
74          new CustomEvent(UPDATE_EVENT, { detail: { id, action: "clear" } })
75        );
76      } catch {}
77    },
78
79    // ── Backend API ────────────────────────────────────────────────────────────
80
81    /**
82     * Persist the mind map to the zim store (system of record), mirroring it to
83     * the local cache so an offline reload still works.
84     * @param {string} ndjson  Serialised mind map.
85     * @returns {Promise<{docId, savedCount, status}>}
86     */
87    async saveApi(ndjson, docName) {
88      // The map's NAME is its identity: docId = mindmap:<name>, so saving the same
89      // titled map upserts it and the Open picker lists one row per named map.
90      const name = String(docName || id || "mindmap").trim() || "mindmap";
91      const result = await saveDoc({
92        shellType: SHELL_TYPE,
93        instanceId: name,
94        docName: name,
95        body: ndjson,
96      });
97      try { localStorage.setItem(key, ndjson); } catch {}
98      return result;
99    },
100
101    /**
102     * List all saved mind maps for the tenant (for the Open-from-server picker).
103     * @returns {Promise<Array<{docId, docName, updatedAt, lineCount}>>}
104     */
105    async listApi() {
106      // Dedupe by docId defensively: the picker must show one entry per distinct
107      // map even if the store ever returns repeated/duplicate rows. Keep the
108      // newest (max updatedAt) per docId.
109      const rows = await listDocs({ shellType: SHELL_TYPE });
110      const byId = new Map();
111      for (const d of rows) {
112        const id = d && d.docId;
113        if (!id) continue;
114        const prev = byId.get(id);
115        if (!prev || String(d.updatedAt || "") > String(prev.updatedAt || "")) {
116          byId.set(id, d);
117        }
118      }
119      return [...byId.values()];
120    },
121
122    /**
123     * List all versions (latest + archives) of a named mind map.
124     * @param {string} docName  The map's name (its identity == clean base id).
125     * @returns {Promise<Array<{docId,version,updatedAt,lineCount,isLatest}>>}
126     */
127    async listVersions(docName) {
128      const name = String(docName || id || "mindmap").trim() || "mindmap";
129      return listVersions({ shellType: SHELL_TYPE, instanceId: name });
130    },
131
132    /**
133     * Load a specific archived version by its full version docId ("<base>::v<n>").
134     * @returns {Promise<string|null>}  NDJSON string or null if empty.
135     */
136    async loadVersion(versionDocId) {
137      const text = await loadVersion({ docId: versionDocId });
138      return (typeof text === "string" && text.trim()) ? text : null;
139    },
140
141    /**
142     * Load a specific saved mind map by its docId (chosen in the Open picker).
143     * @returns {Promise<string|null>}  NDJSON string or null if empty.
144     */
145    async loadApiById(docId) {
146      const text = await loadDoc({ shellType: SHELL_TYPE, docId });
147      if (typeof text === "string" && text.trim()) {
148        try { localStorage.setItem(key, text); } catch {}
149        return text;
150      }
151      return null;
152    },
153
154    /**
155     * Load the mind map from the zim store. Returns null when nothing is saved
156     * server-side, so the caller can fall back to loadLocal().
157     * @returns {Promise<string|null>}  NDJSON string or null.
158     */
159    async loadApi() {
160      const text = await loadDoc({ shellType: SHELL_TYPE, instanceId: id || "default" });
161      if (typeof text === "string" && text.trim()) {
162        try { localStorage.setItem(key, text); } catch {}
163        return text;
164      }
165      return null;
166    },
167
168    // ── Change Subscriptions ───────────────────────────────────────────────────
169
170    /**
171     * Subscribe to local DB changes (same-tab via CustomEvent).
172     * @param {function} handler  Called with { id, action } on any change.
173     * @returns {function}  Cleanup function to unsubscribe.
174     */
175    onChange(handler) {
176      const listener = (e) => {
177        if (e.detail?.id === id) handler(e.detail);
178      };
179      window.addEventListener(UPDATE_EVENT, listener);
180      return () => window.removeEventListener(UPDATE_EVENT, listener);
181    },
182  };
183}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.