datanotes 2.0.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/NOTICE +9 -0
  2. package/README.md +207 -28
  3. package/SECURITY.md +92 -45
  4. package/dist/THIRD-PARTY-NOTICES.txt +682 -0
  5. package/dist/api/catalog.d.cts +69 -0
  6. package/dist/api/catalog.d.ts +50 -2
  7. package/dist/api/cli-options.d.cts +39 -0
  8. package/dist/api/cli-options.d.ts +39 -0
  9. package/dist/api/client.d.cts +89 -0
  10. package/dist/api/client.d.ts +19 -30
  11. package/dist/api/docs.d.cts +211 -0
  12. package/dist/api/docs.d.ts +211 -0
  13. package/dist/api/guidance.d.cts +81 -0
  14. package/dist/api/guidance.d.ts +81 -0
  15. package/dist/api/index.cjs +6610 -2247
  16. package/dist/api/index.d.cts +13 -0
  17. package/dist/api/index.d.ts +10 -7
  18. package/dist/api/index.js +6610 -2247
  19. package/dist/api/local.cjs +118 -6
  20. package/dist/api/local.d.cts +95 -0
  21. package/dist/api/local.d.ts +70 -6
  22. package/dist/api/local.js +104 -6
  23. package/dist/api/mcp.cjs +17384 -0
  24. package/dist/api/mcp.d.cts +108 -0
  25. package/dist/api/mcp.d.ts +108 -0
  26. package/dist/api/mcp.js +17334 -0
  27. package/dist/api/otlp.d.cts +65 -0
  28. package/dist/api/redact.d.cts +17 -0
  29. package/dist/api/redact.d.ts +17 -0
  30. package/dist/api/requests.d.cts +26 -0
  31. package/dist/api/requests.d.ts +14 -16
  32. package/dist/api/results.d.cts +16 -0
  33. package/dist/api/results.d.ts +2 -1
  34. package/dist/api/tokens.d.cts +7 -0
  35. package/dist/datanotes.cjs +62511 -28069
  36. package/dist/docs/administration/configuration.md +59 -0
  37. package/dist/docs/administration/docker.md +80 -0
  38. package/dist/docs/administration/git-sync.md +71 -0
  39. package/dist/docs/administration/limits.md +69 -0
  40. package/dist/docs/administration/run-the-service.md +219 -0
  41. package/dist/docs/administration/status.md +61 -0
  42. package/dist/docs/administration/use-with-obsidian.md +75 -0
  43. package/dist/docs/api/operations/add_column.md +49 -0
  44. package/dist/docs/api/operations/add_constraint.md +55 -0
  45. package/dist/docs/api/operations/adopt_rows.md +50 -0
  46. package/dist/docs/api/operations/align_relations.md +41 -0
  47. package/dist/docs/api/operations/alter_column.md +46 -0
  48. package/dist/docs/api/operations/alter_table.md +67 -0
  49. package/dist/docs/api/operations/batch.md +48 -0
  50. package/dist/docs/api/operations/changes_stream.md +28 -0
  51. package/dist/docs/api/operations/check_table.md +29 -0
  52. package/dist/docs/api/operations/clone_table.md +48 -0
  53. package/dist/docs/api/operations/comment_on.md +42 -0
  54. package/dist/docs/api/operations/configure_database.md +52 -0
  55. package/dist/docs/api/operations/create_table.md +78 -0
  56. package/dist/docs/api/operations/create_view.md +54 -0
  57. package/dist/docs/api/operations/create_webhook.md +37 -0
  58. package/dist/docs/api/operations/define_base.md +52 -0
  59. package/dist/docs/api/operations/delete_rows.md +43 -0
  60. package/dist/docs/api/operations/delete_webhook.md +31 -0
  61. package/dist/docs/api/operations/describe_database.md +30 -0
  62. package/dist/docs/api/operations/describe_history.md +40 -0
  63. package/dist/docs/api/operations/describe_semantic_index.md +24 -0
  64. package/dist/docs/api/operations/describe_table.md +38 -0
  65. package/dist/docs/api/operations/docs.md +13 -0
  66. package/dist/docs/api/operations/drop_column.md +41 -0
  67. package/dist/docs/api/operations/drop_constraint.md +39 -0
  68. package/dist/docs/api/operations/drop_table.md +43 -0
  69. package/dist/docs/api/operations/drop_view.md +38 -0
  70. package/dist/docs/api/operations/engine_status.md +23 -0
  71. package/dist/docs/api/operations/graph_neighbors.md +32 -0
  72. package/dist/docs/api/operations/graph_path.md +32 -0
  73. package/dist/docs/api/operations/graph_traverse.md +43 -0
  74. package/dist/docs/api/operations/identify_session.md +24 -0
  75. package/dist/docs/api/operations/infer_table.md +43 -0
  76. package/dist/docs/api/operations/insert_row.md +48 -0
  77. package/dist/docs/api/operations/list_changes.md +35 -0
  78. package/dist/docs/api/operations/list_folder.md +28 -0
  79. package/dist/docs/api/operations/list_link_targets.md +44 -0
  80. package/dist/docs/api/operations/list_migrations.md +35 -0
  81. package/dist/docs/api/operations/list_tables.md +32 -0
  82. package/dist/docs/api/operations/list_webhooks.md +23 -0
  83. package/dist/docs/api/operations/manual_index.md +14 -0
  84. package/dist/docs/api/operations/manual_page.md +22 -0
  85. package/dist/docs/api/operations/merge_rows.md +44 -0
  86. package/dist/docs/api/operations/move_rows.md +52 -0
  87. package/dist/docs/api/operations/openapi.md +13 -0
  88. package/dist/docs/api/operations/openapi_post.md +20 -0
  89. package/dist/docs/api/operations/read_note.md +29 -0
  90. package/dist/docs/api/operations/recompute_columns.md +44 -0
  91. package/dist/docs/api/operations/record_tool_call.md +36 -0
  92. package/dist/docs/api/operations/rename_column.md +44 -0
  93. package/dist/docs/api/operations/rename_value.md +45 -0
  94. package/dist/docs/api/operations/restore_database.md +51 -0
  95. package/dist/docs/api/operations/restore_table.md +46 -0
  96. package/dist/docs/api/operations/search_notes.md +28 -0
  97. package/dist/docs/api/operations/select_rows.md +61 -0
  98. package/dist/docs/api/operations/semantic_search.md +34 -0
  99. package/dist/docs/api/operations/semantic_search_docs.md +26 -0
  100. package/dist/docs/api/operations/set_table_bases.md +44 -0
  101. package/dist/docs/api/operations/status.md +13 -0
  102. package/dist/docs/api/operations/stop_engine.md +23 -0
  103. package/dist/docs/api/operations/sync_semantic_index.md +33 -0
  104. package/dist/docs/api/operations/table_changes.md +31 -0
  105. package/dist/docs/api/operations/tags/auxiliary.md +14 -0
  106. package/dist/docs/api/operations/tags/change-feed.md +8 -0
  107. package/dist/docs/api/operations/tags/history.md +17 -0
  108. package/dist/docs/api/operations/tags/queries.md +15 -0
  109. package/dist/docs/api/operations/tags/rows-dml.md +16 -0
  110. package/dist/docs/api/operations/tags/schema-ddl.md +22 -0
  111. package/dist/docs/api/operations/tags/service.md +15 -0
  112. package/dist/docs/api/operations/tags/tokens.md +13 -0
  113. package/dist/docs/api/operations/tokens_create.md +34 -0
  114. package/dist/docs/api/operations/tokens_delete.md +29 -0
  115. package/dist/docs/api/operations/tokens_list.md +21 -0
  116. package/dist/docs/api/operations/tokens_me.md +20 -0
  117. package/dist/docs/api/operations/tokens_rotate.md +29 -0
  118. package/dist/docs/api/operations/tokens_update.md +34 -0
  119. package/dist/docs/api/operations/undo_migration.md +46 -0
  120. package/dist/docs/api/operations/update_row.md +60 -0
  121. package/dist/docs/api/operations/update_rows.md +46 -0
  122. package/dist/docs/api/operations/vacuum_table.md +37 -0
  123. package/dist/docs/api.md +135 -0
  124. package/dist/docs/develop/build-an-app.md +320 -0
  125. package/dist/docs/develop/build-on-datanotes.md +138 -0
  126. package/dist/docs/develop/change-feed-and-webhooks.md +70 -0
  127. package/dist/docs/develop/client.md +116 -0
  128. package/dist/docs/develop/concurrency.md +153 -0
  129. package/dist/docs/develop/http-api.md +79 -0
  130. package/dist/docs/develop/telemetry.md +86 -0
  131. package/dist/docs/guides/agents/docs-offline.md +99 -0
  132. package/dist/docs/guides/agents/how-agents-use-datanotes.md +124 -0
  133. package/dist/docs/guides/agents/prompts.md +495 -0
  134. package/dist/docs/guides/agents/reading-errors.md +112 -0
  135. package/dist/docs/guides/data-quality/check-constraints.md +379 -0
  136. package/dist/docs/guides/data-quality/check-table.md +63 -0
  137. package/dist/docs/guides/data-quality/hand-edits.md +507 -0
  138. package/dist/docs/guides/data-quality/sync-between-devices.md +51 -0
  139. package/dist/docs/guides/data-quality/validation.md +90 -0
  140. package/dist/docs/guides/database-objects/bases.md +480 -0
  141. package/dist/docs/guides/database-objects/catalogs-and-schemas.md +897 -0
  142. package/dist/docs/guides/database-objects/columns.md +1332 -0
  143. package/dist/docs/guides/database-objects/relations.md +1454 -0
  144. package/dist/docs/guides/database-objects/tables.md +1404 -0
  145. package/dist/docs/guides/database-objects/views.md +1003 -0
  146. package/dist/docs/guides/history/back-up.md +195 -0
  147. package/dist/docs/guides/history/change-data-feed.md +247 -0
  148. package/dist/docs/guides/history/journal-and-undo.md +262 -0
  149. package/dist/docs/guides/history/restore.md +341 -0
  150. package/dist/docs/guides/history/table-history.md +657 -0
  151. package/dist/docs/guides/history/vacuum.md +35 -0
  152. package/dist/docs/guides/read-and-write/automatic-links.md +768 -0
  153. package/dist/docs/guides/read-and-write/batch.md +139 -0
  154. package/dist/docs/guides/read-and-write/import-notes.md +896 -0
  155. package/dist/docs/guides/read-and-write/joins-and-aggregates.md +251 -0
  156. package/dist/docs/guides/read-and-write/read-and-filter.md +536 -0
  157. package/dist/docs/guides/read-and-write/write-rows.md +573 -0
  158. package/dist/docs/guides/search/link-graph.md +146 -0
  159. package/dist/docs/guides/search/semantic.md +133 -0
  160. package/dist/docs/guides/search/text.md +142 -0
  161. package/dist/docs/guides/security/security-model.md +110 -0
  162. package/dist/docs/guides/security/tokens.md +60 -0
  163. package/dist/docs/guides/security/who-did-what.md +42 -0
  164. package/dist/docs/index.md +113 -0
  165. package/dist/docs/llms.txt +33 -0
  166. package/dist/docs/reference/cli.md +136 -0
  167. package/dist/docs/reference/compatibility.md +63 -0
  168. package/dist/docs/reference/errors.md +72 -0
  169. package/dist/docs/reference/glossary.md +161 -0
  170. package/dist/docs/reference/operations/auxiliary.md +116 -0
  171. package/dist/docs/reference/operations/history.md +168 -0
  172. package/dist/docs/reference/operations/queries.md +169 -0
  173. package/dist/docs/reference/operations/rows.md +185 -0
  174. package/dist/docs/reference/operations/schema.md +316 -0
  175. package/dist/docs/reference/operations.md +96 -0
  176. package/dist/docs/reference/settings.md +31 -0
  177. package/dist/docs/release-notes.md +67 -0
  178. package/dist/docs/start/connect-agents.md +204 -0
  179. package/dist/docs/start/how-it-works.md +317 -0
  180. package/dist/docs/start/quick-start.md +126 -0
  181. package/package.json +58 -14
@@ -30,8 +30,22 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/api/local.ts
31
31
  var local_exports = {};
32
32
  __export(local_exports, {
33
+ DEFAULT_DATABASE_FOLDER: () => DEFAULT_DATABASE_FOLDER,
34
+ LOCK_FILE: () => LOCK_FILE,
33
35
  LOCK_STALE_MS: () => LOCK_STALE_MS,
36
+ METASTORE: () => METASTORE,
37
+ REGISTRY_FILE: () => REGISTRY_FILE,
38
+ SETTINGS_FILE: () => SETTINGS_FILE,
39
+ STORE_RECORD_FILE: () => STORE_RECORD_FILE,
40
+ dataHome: () => dataHome,
41
+ databaseIdIn: () => databaseIdIn,
34
42
  localEngine: () => localEngine,
43
+ readDatabaseId: () => readDatabaseId,
44
+ readRegistry: () => readRegistry,
45
+ storeLocation: () => storeLocation,
46
+ vaultDatabaseFolder: () => vaultDatabaseFolder,
47
+ vaultKey: () => vaultKey,
48
+ vaultMetastores: () => vaultMetastores,
35
49
  vaultSettingsPath: () => vaultSettingsPath
36
50
  });
37
51
  module.exports = __toCommonJS(local_exports);
@@ -39,29 +53,127 @@ var import_node_fs = __toESM(require("node:fs"), 1);
39
53
  var import_node_os = __toESM(require("node:os"), 1);
40
54
  var import_node_path = __toESM(require("node:path"), 1);
41
55
  var LOCK_STALE_MS = 3e4;
42
- function vaultSettingsPath(vault) {
43
- return import_node_path.default.join(vault, ".datanotes", "service.json");
56
+ var SETTINGS_FILE = "settings.json";
57
+ var LOCK_FILE = "engine.lock";
58
+ var STORE_RECORD_FILE = "store.json";
59
+ var REGISTRY_FILE = "vaults.json";
60
+ var DEFAULT_DATABASE_FOLDER = "datanotes";
61
+ var METASTORE = "metastore.yaml";
62
+ function dataHome(env = process.env, platform = process.platform, home = import_node_os.default.homedir()) {
63
+ if (env.DATANOTES_DATA_HOME) return import_node_path.default.resolve(env.DATANOTES_DATA_HOME);
64
+ if (platform === "win32") return import_node_path.default.join(env.LOCALAPPDATA || import_node_path.default.join(home, "AppData", "Local"), "Datanotes");
65
+ if (platform === "darwin") return import_node_path.default.join(home, "Library", "Application Support", "Datanotes");
66
+ return import_node_path.default.join(env.XDG_DATA_HOME || import_node_path.default.join(home, ".local", "share"), "datanotes");
67
+ }
68
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
69
+ function readDatabaseId(text) {
70
+ const m = /^id:[ \t]*(.*)$/m.exec(text);
71
+ const value = m ? m[1].replace(/#.*$/, "").trim().replace(/^["']|["']$/g, "") : "";
72
+ return UUID.test(value) ? value.toLowerCase() : null;
73
+ }
74
+ function databaseIdIn(vault, database) {
75
+ try {
76
+ return readDatabaseId(import_node_fs.default.readFileSync(import_node_path.default.join(vault, ...database.split("/"), METASTORE), "utf8"));
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+ function vaultMetastores(vault) {
82
+ const out = [];
83
+ const walk = (dir, rel) => {
84
+ let ents;
85
+ try {
86
+ ents = import_node_fs.default.readdirSync(dir, { withFileTypes: true });
87
+ } catch {
88
+ return;
89
+ }
90
+ for (const e of ents) {
91
+ if (e.name.startsWith(".") || e.name === "node_modules") continue;
92
+ const r = rel ? `${rel}/${e.name}` : e.name;
93
+ if (e.isDirectory()) walk(import_node_path.default.join(dir, e.name), r);
94
+ else if (e.name === METASTORE) out.push(rel);
95
+ }
96
+ };
97
+ walk(vault, "");
98
+ return out.sort();
99
+ }
100
+ function vaultKey(vault, platform = process.platform) {
101
+ let real = import_node_path.default.resolve(vault);
102
+ try {
103
+ real = import_node_fs.default.realpathSync.native(real);
104
+ } catch {
105
+ }
106
+ return platform === "win32" ? real.toLowerCase() : real;
44
107
  }
45
108
  function readJson(file) {
46
109
  try {
47
- return JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
110
+ const v = JSON.parse(import_node_fs.default.readFileSync(file, "utf8"));
111
+ return v && typeof v === "object" && !Array.isArray(v) ? v : null;
48
112
  } catch {
49
113
  return null;
50
114
  }
51
115
  }
116
+ function readRegistry(home) {
117
+ const raw = readJson(import_node_path.default.join(home, REGISTRY_FILE)) ?? {};
118
+ const out = {};
119
+ for (const [k, v] of Object.entries(raw)) {
120
+ const r = v;
121
+ if (r && typeof r.id === "string" && UUID.test(r.id) && typeof r.database === "string") out[k] = { id: r.id, database: r.database };
122
+ }
123
+ return out;
124
+ }
125
+ function vaultDatabaseFolder(vault) {
126
+ const found = vaultMetastores(vault);
127
+ return found.length === 1 ? found[0] : DEFAULT_DATABASE_FOLDER;
128
+ }
129
+ function storeLocation(vault, opts = {}) {
130
+ const env = opts.env ?? process.env;
131
+ const explicit = opts.store ?? env.DATANOTES_STORE;
132
+ if (explicit) {
133
+ const store = import_node_path.default.resolve(explicit);
134
+ const rec = readJson(import_node_path.default.join(store, STORE_RECORD_FILE));
135
+ const database2 = typeof rec?.database === "string" ? rec.database : vaultDatabaseFolder(vault);
136
+ return { store, id: typeof rec?.id === "string" ? rec.id : databaseIdIn(vault, database2), database: database2 };
137
+ }
138
+ const home = dataHome(env);
139
+ const known = readRegistry(home)[vaultKey(vault)];
140
+ if (known) return { store: import_node_path.default.join(home, known.id), id: known.id, database: known.database };
141
+ const database = vaultDatabaseFolder(vault);
142
+ const id = databaseIdIn(vault, database);
143
+ return id ? { store: import_node_path.default.join(home, id), id, database } : null;
144
+ }
145
+ function vaultSettingsPath(vault, opts = {}) {
146
+ const loc = storeLocation(vault, opts);
147
+ return loc ? import_node_path.default.join(loc.store, SETTINGS_FILE) : null;
148
+ }
52
149
  function localEngine(vault, opts = {}) {
53
- const settings = readJson(opts.settingsFile ?? vaultSettingsPath(vault)) ?? {};
54
- const store = typeof settings.storeFolder === "string" && settings.storeFolder ? settings.storeFolder : ".datanotes";
55
- const lock = readJson(import_node_path.default.join(vault, store.replace(/\/+$/, ""), "engine.lock"));
150
+ const loc = storeLocation(vault, opts);
151
+ if (!loc) return null;
152
+ const lock = readJson(import_node_path.default.join(loc.store, LOCK_FILE));
56
153
  if (!lock || (opts.now ?? Date.now()) - Date.parse(lock.heartbeat) >= LOCK_STALE_MS) return null;
57
154
  if (lock.host !== import_node_os.default.hostname()) throw new Error(`the vault is held by an engine on ${lock.host}; connect to it by its address`);
155
+ const settings = readJson(opts.settingsFile ?? import_node_path.default.join(loc.store, SETTINGS_FILE)) ?? {};
58
156
  const token = typeof settings.httpServerToken === "string" ? settings.httpServerToken : "";
59
157
  if (!token) return null;
60
158
  return { url: `http://${opts.host ?? "127.0.0.1"}:${lock.port}`, token, lock };
61
159
  }
62
160
  // Annotate the CommonJS export names for ESM import in node:
63
161
  0 && (module.exports = {
162
+ DEFAULT_DATABASE_FOLDER,
163
+ LOCK_FILE,
64
164
  LOCK_STALE_MS,
165
+ METASTORE,
166
+ REGISTRY_FILE,
167
+ SETTINGS_FILE,
168
+ STORE_RECORD_FILE,
169
+ dataHome,
170
+ databaseIdIn,
65
171
  localEngine,
172
+ readDatabaseId,
173
+ readRegistry,
174
+ storeLocation,
175
+ vaultDatabaseFolder,
176
+ vaultKey,
177
+ vaultMetastores,
66
178
  vaultSettingsPath
67
179
  });
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Find the engine running on a vault of this machine (Node only): its address and the owner's
3
+ * token, read from the database's store, where the engine keeps its settings and the lock it holds
4
+ * while it runs.
5
+ *
6
+ * The store is outside the vault, in the operating system's per-user data folder:
7
+ * `<data home>/<database id>/`, the id being `id` in the database's `metastore.yaml`. The data home
8
+ * is `%LOCALAPPDATA%\Datanotes` on Windows, `~/Library/Application Support/Datanotes` on macOS and
9
+ * `$XDG_DATA_HOME/datanotes` (`~/.local/share/datanotes`) elsewhere. `DATANOTES_DATA_HOME` moves the
10
+ * data home, `DATANOTES_STORE` (or `--store`) names the store folder itself. The engine finds a
11
+ * vault's database again through `vaults.json` in the data home (vault → id and database folder).
12
+ */
13
+ /** The lock an engine writes in its store (`<store>/engine.lock`). */
14
+ export interface EngineLock {
15
+ kind: "service";
16
+ pid: number;
17
+ host: string;
18
+ port: number;
19
+ startedAt: string;
20
+ heartbeat: string;
21
+ }
22
+ /** A lock whose heartbeat is older than this belongs to an engine that stopped. */
23
+ export declare const LOCK_STALE_MS = 30000;
24
+ /** The engine's settings file, in the store. */
25
+ export declare const SETTINGS_FILE = "settings.json";
26
+ /** The lock, in the store. */
27
+ export declare const LOCK_FILE = "engine.lock";
28
+ /** The store's record of its database: id, vault, database folder. */
29
+ export declare const STORE_RECORD_FILE = "store.json";
30
+ /** In the data home: the vaults this machine's engines opened, with their database. */
31
+ export declare const REGISTRY_FILE = "vaults.json";
32
+ /** The database folder of a new database: a top-level folder of the vault. */
33
+ export declare const DEFAULT_DATABASE_FOLDER = "datanotes";
34
+ export declare const METASTORE = "metastore.yaml";
35
+ type Env = Record<string, string | undefined>;
36
+ /** The data home: the folder that holds the stores of this user's databases. */
37
+ export declare function dataHome(env?: Env, platform?: string, home?: string): string;
38
+ /** The database id of a `metastore.yaml` text (its top-level `id:`), or null. */
39
+ export declare function readDatabaseId(text: string): string | null;
40
+ /** The database id of a vault's database folder, or null (no metastore, or no id in it). */
41
+ export declare function databaseIdIn(vault: string, database: string): string | null;
42
+ /**
43
+ * The folders of a vault on disk that hold a `metastore.yaml` (vault-relative with `/`, sorted).
44
+ * A vault has exactly one database folder, anywhere ("" for the root: --database). Dot-folders and
45
+ * `node_modules` are left out.
46
+ */
47
+ export declare function vaultMetastores(vault: string): string[];
48
+ /** A vault as the registry names it: its real path (lowercase on Windows, whose paths ignore case). */
49
+ export declare function vaultKey(vault: string, platform?: string): string;
50
+ export interface VaultRecord {
51
+ /** The database id. */
52
+ id: string;
53
+ /** The database folder, vault-relative. */
54
+ database: string;
55
+ }
56
+ /** The registry of a data home: vault key → database id and folder. */
57
+ export declare function readRegistry(home: string): Record<string, VaultRecord>;
58
+ export interface StoreLocation {
59
+ /** The store folder. */
60
+ store: string;
61
+ /** The database id (null: a store named by DATANOTES_STORE whose record does not say it). */
62
+ id: string | null;
63
+ /** The database folder, vault-relative. */
64
+ database: string;
65
+ }
66
+ export interface LocateOptions {
67
+ env?: Env;
68
+ /** The store folder itself (`--store`; else DATANOTES_STORE). */
69
+ store?: string;
70
+ }
71
+ /** The vault's one folder with a `metastore.yaml`, else `datanotes`. */
72
+ export declare function vaultDatabaseFolder(vault: string): string;
73
+ /**
74
+ * Where the store of a vault's database is, as the engine finds it: the store named by `--store`
75
+ * or DATANOTES_STORE; else the database this machine last opened on the vault (`vaults.json`);
76
+ * else the id in the vault's one `metastore.yaml`. Null when none of them says.
77
+ */
78
+ export declare function storeLocation(vault: string, opts?: LocateOptions): StoreLocation | null;
79
+ export interface LocalEngine {
80
+ url: string;
81
+ token: string;
82
+ lock: EngineLock;
83
+ }
84
+ /** The settings file of a vault's engine, in its store; null while the vault has no store yet. */
85
+ export declare function vaultSettingsPath(vault: string, opts?: LocateOptions): string | null;
86
+ /**
87
+ * The engine holding `vault`, or null when none runs. Throws when the lock is fresh but held on
88
+ * another machine (a store on a shared folder): that engine is not reachable from here.
89
+ */
90
+ export declare function localEngine(vault: string, opts?: LocateOptions & {
91
+ settingsFile?: string;
92
+ host?: string;
93
+ now?: number;
94
+ }): LocalEngine | null;
95
+ export {};
@@ -1,8 +1,16 @@
1
1
  /**
2
2
  * Find the engine running on a vault of this machine (Node only): its address and the owner's
3
- * token, read from the vault's settings and from the lock the engine holds while it runs.
3
+ * token, read from the database's store, where the engine keeps its settings and the lock it holds
4
+ * while it runs.
5
+ *
6
+ * The store is outside the vault, in the operating system's per-user data folder:
7
+ * `<data home>/<database id>/`, the id being `id` in the database's `metastore.yaml`. The data home
8
+ * is `%LOCALAPPDATA%\Datanotes` on Windows, `~/Library/Application Support/Datanotes` on macOS and
9
+ * `$XDG_DATA_HOME/datanotes` (`~/.local/share/datanotes`) elsewhere. `DATANOTES_DATA_HOME` moves the
10
+ * data home, `DATANOTES_STORE` (or `--store`) names the store folder itself. The engine finds a
11
+ * vault's database again through `vaults.json` in the data home (vault → id and database folder).
4
12
  */
5
- /** The lock an engine writes in its store folder (`<vault>/.datanotes/engine.lock`). */
13
+ /** The lock an engine writes in its store (`<store>/engine.lock`). */
6
14
  export interface EngineLock {
7
15
  kind: "service";
8
16
  pid: number;
@@ -13,19 +21,75 @@ export interface EngineLock {
13
21
  }
14
22
  /** A lock whose heartbeat is older than this belongs to an engine that stopped. */
15
23
  export declare const LOCK_STALE_MS = 30000;
24
+ /** The engine's settings file, in the store. */
25
+ export declare const SETTINGS_FILE = "settings.json";
26
+ /** The lock, in the store. */
27
+ export declare const LOCK_FILE = "engine.lock";
28
+ /** The store's record of its database: id, vault, database folder. */
29
+ export declare const STORE_RECORD_FILE = "store.json";
30
+ /** In the data home: the vaults this machine's engines opened, with their database. */
31
+ export declare const REGISTRY_FILE = "vaults.json";
32
+ /** The database folder of a new database: a top-level folder of the vault. */
33
+ export declare const DEFAULT_DATABASE_FOLDER = "datanotes";
34
+ export declare const METASTORE = "metastore.yaml";
35
+ type Env = Record<string, string | undefined>;
36
+ /** The data home: the folder that holds the stores of this user's databases. */
37
+ export declare function dataHome(env?: Env, platform?: string, home?: string): string;
38
+ /** The database id of a `metastore.yaml` text (its top-level `id:`), or null. */
39
+ export declare function readDatabaseId(text: string): string | null;
40
+ /** The database id of a vault's database folder, or null (no metastore, or no id in it). */
41
+ export declare function databaseIdIn(vault: string, database: string): string | null;
42
+ /**
43
+ * The folders of a vault on disk that hold a `metastore.yaml` (vault-relative with `/`, sorted).
44
+ * A vault has exactly one database folder, anywhere ("" for the root: --database). Dot-folders and
45
+ * `node_modules` are left out.
46
+ */
47
+ export declare function vaultMetastores(vault: string): string[];
48
+ /** A vault as the registry names it: its real path (lowercase on Windows, whose paths ignore case). */
49
+ export declare function vaultKey(vault: string, platform?: string): string;
50
+ export interface VaultRecord {
51
+ /** The database id. */
52
+ id: string;
53
+ /** The database folder, vault-relative. */
54
+ database: string;
55
+ }
56
+ /** The registry of a data home: vault key → database id and folder. */
57
+ export declare function readRegistry(home: string): Record<string, VaultRecord>;
58
+ export interface StoreLocation {
59
+ /** The store folder. */
60
+ store: string;
61
+ /** The database id (null: a store named by DATANOTES_STORE whose record does not say it). */
62
+ id: string | null;
63
+ /** The database folder, vault-relative. */
64
+ database: string;
65
+ }
66
+ export interface LocateOptions {
67
+ env?: Env;
68
+ /** The store folder itself (`--store`; else DATANOTES_STORE). */
69
+ store?: string;
70
+ }
71
+ /** The vault's one folder with a `metastore.yaml`, else `datanotes`. */
72
+ export declare function vaultDatabaseFolder(vault: string): string;
73
+ /**
74
+ * Where the store of a vault's database is, as the engine finds it: the store named by `--store`
75
+ * or DATANOTES_STORE; else the database this machine last opened on the vault (`vaults.json`);
76
+ * else the id in the vault's one `metastore.yaml`. Null when none of them says.
77
+ */
78
+ export declare function storeLocation(vault: string, opts?: LocateOptions): StoreLocation | null;
16
79
  export interface LocalEngine {
17
80
  url: string;
18
81
  token: string;
19
82
  lock: EngineLock;
20
83
  }
21
- /** The settings file of a vault's engine. */
22
- export declare function vaultSettingsPath(vault: string): string;
84
+ /** The settings file of a vault's engine, in its store; null while the vault has no store yet. */
85
+ export declare function vaultSettingsPath(vault: string, opts?: LocateOptions): string | null;
23
86
  /**
24
87
  * The engine holding `vault`, or null when none runs. Throws when the lock is fresh but held on
25
- * another machine (a synced or shared folder): that engine is not reachable from here.
88
+ * another machine (a store on a shared folder): that engine is not reachable from here.
26
89
  */
27
- export declare function localEngine(vault: string, opts?: {
90
+ export declare function localEngine(vault: string, opts?: LocateOptions & {
28
91
  settingsFile?: string;
29
92
  host?: string;
30
93
  now?: number;
31
94
  }): LocalEngine | null;
95
+ export {};
package/dist/api/local.js CHANGED
@@ -3,28 +3,126 @@ import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  var LOCK_STALE_MS = 3e4;
6
- function vaultSettingsPath(vault) {
7
- return path.join(vault, ".datanotes", "service.json");
6
+ var SETTINGS_FILE = "settings.json";
7
+ var LOCK_FILE = "engine.lock";
8
+ var STORE_RECORD_FILE = "store.json";
9
+ var REGISTRY_FILE = "vaults.json";
10
+ var DEFAULT_DATABASE_FOLDER = "datanotes";
11
+ var METASTORE = "metastore.yaml";
12
+ function dataHome(env = process.env, platform = process.platform, home = os.homedir()) {
13
+ if (env.DATANOTES_DATA_HOME) return path.resolve(env.DATANOTES_DATA_HOME);
14
+ if (platform === "win32") return path.join(env.LOCALAPPDATA || path.join(home, "AppData", "Local"), "Datanotes");
15
+ if (platform === "darwin") return path.join(home, "Library", "Application Support", "Datanotes");
16
+ return path.join(env.XDG_DATA_HOME || path.join(home, ".local", "share"), "datanotes");
17
+ }
18
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
19
+ function readDatabaseId(text) {
20
+ const m = /^id:[ \t]*(.*)$/m.exec(text);
21
+ const value = m ? m[1].replace(/#.*$/, "").trim().replace(/^["']|["']$/g, "") : "";
22
+ return UUID.test(value) ? value.toLowerCase() : null;
23
+ }
24
+ function databaseIdIn(vault, database) {
25
+ try {
26
+ return readDatabaseId(fs.readFileSync(path.join(vault, ...database.split("/"), METASTORE), "utf8"));
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ function vaultMetastores(vault) {
32
+ const out = [];
33
+ const walk = (dir, rel) => {
34
+ let ents;
35
+ try {
36
+ ents = fs.readdirSync(dir, { withFileTypes: true });
37
+ } catch {
38
+ return;
39
+ }
40
+ for (const e of ents) {
41
+ if (e.name.startsWith(".") || e.name === "node_modules") continue;
42
+ const r = rel ? `${rel}/${e.name}` : e.name;
43
+ if (e.isDirectory()) walk(path.join(dir, e.name), r);
44
+ else if (e.name === METASTORE) out.push(rel);
45
+ }
46
+ };
47
+ walk(vault, "");
48
+ return out.sort();
49
+ }
50
+ function vaultKey(vault, platform = process.platform) {
51
+ let real = path.resolve(vault);
52
+ try {
53
+ real = fs.realpathSync.native(real);
54
+ } catch {
55
+ }
56
+ return platform === "win32" ? real.toLowerCase() : real;
8
57
  }
9
58
  function readJson(file) {
10
59
  try {
11
- return JSON.parse(fs.readFileSync(file, "utf8"));
60
+ const v = JSON.parse(fs.readFileSync(file, "utf8"));
61
+ return v && typeof v === "object" && !Array.isArray(v) ? v : null;
12
62
  } catch {
13
63
  return null;
14
64
  }
15
65
  }
66
+ function readRegistry(home) {
67
+ const raw = readJson(path.join(home, REGISTRY_FILE)) ?? {};
68
+ const out = {};
69
+ for (const [k, v] of Object.entries(raw)) {
70
+ const r = v;
71
+ if (r && typeof r.id === "string" && UUID.test(r.id) && typeof r.database === "string") out[k] = { id: r.id, database: r.database };
72
+ }
73
+ return out;
74
+ }
75
+ function vaultDatabaseFolder(vault) {
76
+ const found = vaultMetastores(vault);
77
+ return found.length === 1 ? found[0] : DEFAULT_DATABASE_FOLDER;
78
+ }
79
+ function storeLocation(vault, opts = {}) {
80
+ const env = opts.env ?? process.env;
81
+ const explicit = opts.store ?? env.DATANOTES_STORE;
82
+ if (explicit) {
83
+ const store = path.resolve(explicit);
84
+ const rec = readJson(path.join(store, STORE_RECORD_FILE));
85
+ const database2 = typeof rec?.database === "string" ? rec.database : vaultDatabaseFolder(vault);
86
+ return { store, id: typeof rec?.id === "string" ? rec.id : databaseIdIn(vault, database2), database: database2 };
87
+ }
88
+ const home = dataHome(env);
89
+ const known = readRegistry(home)[vaultKey(vault)];
90
+ if (known) return { store: path.join(home, known.id), id: known.id, database: known.database };
91
+ const database = vaultDatabaseFolder(vault);
92
+ const id = databaseIdIn(vault, database);
93
+ return id ? { store: path.join(home, id), id, database } : null;
94
+ }
95
+ function vaultSettingsPath(vault, opts = {}) {
96
+ const loc = storeLocation(vault, opts);
97
+ return loc ? path.join(loc.store, SETTINGS_FILE) : null;
98
+ }
16
99
  function localEngine(vault, opts = {}) {
17
- const settings = readJson(opts.settingsFile ?? vaultSettingsPath(vault)) ?? {};
18
- const store = typeof settings.storeFolder === "string" && settings.storeFolder ? settings.storeFolder : ".datanotes";
19
- const lock = readJson(path.join(vault, store.replace(/\/+$/, ""), "engine.lock"));
100
+ const loc = storeLocation(vault, opts);
101
+ if (!loc) return null;
102
+ const lock = readJson(path.join(loc.store, LOCK_FILE));
20
103
  if (!lock || (opts.now ?? Date.now()) - Date.parse(lock.heartbeat) >= LOCK_STALE_MS) return null;
21
104
  if (lock.host !== os.hostname()) throw new Error(`the vault is held by an engine on ${lock.host}; connect to it by its address`);
105
+ const settings = readJson(opts.settingsFile ?? path.join(loc.store, SETTINGS_FILE)) ?? {};
22
106
  const token = typeof settings.httpServerToken === "string" ? settings.httpServerToken : "";
23
107
  if (!token) return null;
24
108
  return { url: `http://${opts.host ?? "127.0.0.1"}:${lock.port}`, token, lock };
25
109
  }
26
110
  export {
111
+ DEFAULT_DATABASE_FOLDER,
112
+ LOCK_FILE,
27
113
  LOCK_STALE_MS,
114
+ METASTORE,
115
+ REGISTRY_FILE,
116
+ SETTINGS_FILE,
117
+ STORE_RECORD_FILE,
118
+ dataHome,
119
+ databaseIdIn,
28
120
  localEngine,
121
+ readDatabaseId,
122
+ readRegistry,
123
+ storeLocation,
124
+ vaultDatabaseFolder,
125
+ vaultKey,
126
+ vaultMetastores,
29
127
  vaultSettingsPath
30
128
  };