datanotes 2.0.0 → 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 +62911 -28409
  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 +59 -14
@@ -0,0 +1,507 @@
1
+ # Hand edits
2
+
3
+ > Edit notes in an editor or with git: the engine checks the edit, reports what it breaks, recomputes what it owns and repairs the links to notes you rename.
4
+
5
+ You can create, change, rename or remove a row outside the engine: in an editor, with git or through a sync tool. The engine checks the edit a moment later against the same rules as a write, but never refuses it: the note stays as you wrote it.
6
+
7
+ The change feed reports what the edit breaks, and `check_table` lists the rows that are invalid now. The engine fixes what it owns: generated columns, two-way links, and the links to a note you rename or move.
8
+
9
+ ## Catch hand edits that break a rule
10
+
11
+ The engine can’t refuse an edit made outside it, in an editor, through git or by a sync tool. Instead, it checks the edit as soon as it sees it, as `check_table` would:
12
+
13
+ * **The row’s own rules**: types, enums, required columns, links, CHECKs and filtered link targets
14
+ * **Rows that link to it**: when a rule follows links to the edited row
15
+ * **Dangling links**: when you delete or rename a row by hand, the engine reports the links it leaves dangling
16
+
17
+ The engine reports what the edit breaks in three places:
18
+
19
+ * **Change feed**: the event lists the problems in `violations`, where `constraint` names a CHECK; the event leaves out rules the row already broke
20
+ * **Log**: a WARN line
21
+ * **History**: when history is on, the `EXTERNAL_EDIT` version counts them in `operation_metrics.violations`
22
+
23
+ Here you turn Silent film into a top level by hand, with `kind: cinema`. It keeps a parent, and Slapstick comedy now hangs from a top level.
24
+
25
+ First, wait until the change feed includes the rename’s rewrite of Slapstick comedy’s link, and keep the feed’s position:
26
+
27
+ **`list_changes`**
28
+
29
+ ```json
30
+ { "tables": ["genre"], "wait_ms": 10000 }
31
+ ```
32
+
33
+ You edit `datanotes/main/public/genre/Silent film.md` by hand, outside the engine. The note now reads:
34
+
35
+ ```markdown
36
+ ---
37
+ table: main.public.genre
38
+ id: 01a12efb-65b7-7feb-9066-1ef0e98cd245
39
+ name: Silent film
40
+ kind: cinema
41
+ parent: "[[Cinema]]"
42
+ ---
43
+ ```
44
+
45
+ Read the hand edit’s event. `list_changes` long-polls with `wait_ms` until the event arrives:
46
+
47
+ **`list_changes`**
48
+
49
+ ```json
50
+ { "since": 39, "tables": ["genre"], "wait_ms": 10000 }
51
+ ```
52
+
53
+ Answer:
54
+
55
+ ```json
56
+ [
57
+ {
58
+ "seq": 40,
59
+ "kind": "update",
60
+ "path": "datanotes/main/public/genre/Silent film.md",
61
+ "source": "external",
62
+ "changes": { "kind": { "before": "era", "after": "cinema" } },
63
+ "violations": [
64
+ {
65
+ "path": "datanotes/main/public/genre/Silent film.md",
66
+ "violations": [
67
+ {
68
+ "code": "check",
69
+ "constraint": "parent_by_level",
70
+ "message": "violates CHECK parent_by_level"
71
+ }
72
+ ]
73
+ },
74
+ {
75
+ "path": "datanotes/main/public/genre/Slapstick comedy.md",
76
+ "violations": [
77
+ {
78
+ "code": "check",
79
+ "constraint": "parent_by_level",
80
+ "message": "violates CHECK parent_by_level (after the edit of [[Silent film]], a row it links to)"
81
+ }
82
+ ]
83
+ }
84
+ ]
85
+ }
86
+ ]
87
+ ```
88
+
89
+ The rows stay as they are until you fix them. `check_table` lists the rows that are invalid now, and without `table` it checks every table:
90
+
91
+ **`check_table`** (no arguments)
92
+
93
+ Answer:
94
+
95
+ ```json
96
+ {
97
+ "table": null,
98
+ "rows_checked": 16,
99
+ "tables_checked": 5,
100
+ "total": 2,
101
+ "violations": [
102
+ {
103
+ "path": "datanotes/main/public/genre/Silent film.md",
104
+ "table": "main.public.genre",
105
+ "violations": [
106
+ {
107
+ "constraint": "parent_by_level",
108
+ "code": "check",
109
+ "message": "violates CHECK parent_by_level: {\"or\":[{\"kind\":\"cinema\",\"parent\":{\"exists\":false}},{\"kind\":\"era\",\"parent.kind\":\"cinema\"},{\"kind\":\"genre\",\"parent.kind\":\"era\"}]}"
110
+ }
111
+ ]
112
+ },
113
+ {
114
+ "path": "datanotes/main/public/genre/Slapstick comedy.md",
115
+ "table": "main.public.genre",
116
+ "violations": [
117
+ {
118
+ "constraint": "parent_by_level",
119
+ "code": "check",
120
+ "message": "violates CHECK parent_by_level: {\"or\":[{\"kind\":\"cinema\",\"parent\":{\"exists\":false}},{\"kind\":\"era\",\"parent.kind\":\"cinema\"},{\"kind\":\"genre\",\"parent.kind\":\"era\"}]}"
121
+ }
122
+ ]
123
+ }
124
+ ]
125
+ }
126
+ ```
127
+
128
+ Fix the row through the engine:
129
+
130
+ **`update_row`**
131
+
132
+ ```json
133
+ {
134
+ "table": "genre",
135
+ "path": "datanotes/main/public/genre/Silent film.md",
136
+ "set": { "kind": "era" }
137
+ }
138
+ ```
139
+
140
+ Answer:
141
+
142
+ ```json
143
+ {
144
+ "ok": true,
145
+ "op": "update_row",
146
+ "notes_changed": 1,
147
+ "planned_edits": [
148
+ {
149
+ "path": "datanotes/main/public/genre/Silent film.md",
150
+ "set": { "kind": "era" },
151
+ "remove": []
152
+ }
153
+ ],
154
+ "commits": [{ "table": "main.public.genre", "version": 9, "operation": "UPDATE" }],
155
+ "migration_id": "20261012T071951-update_row-update-genre"
156
+ }
157
+ ```
158
+
159
+ ## Recompute generated columns after a hand edit
160
+
161
+ An edit made outside the engine, in an editor, through git or by a sync tool, can leave generated columns stale. `check_table` reports them with code `generated`, and `recompute_columns` brings them up to date.
162
+
163
+ The engine also recomputes them on its own about 3s after a hand edit, unless the `autoRecompute` setting is off. The examples on this page turn it off so you can see each step. Here you correct `where` by hand, because you saw His Girl Friday at the cinema, and `label` still says `home`.
164
+
165
+ You edit `datanotes/main/public/watch/2026-10-03T2130.md` by hand, outside the engine. The note now reads:
166
+
167
+ ```markdown
168
+ ---
169
+ table: main.public.watch
170
+ id: 01a12efb-5b61-7abf-9cb9-45a1f5a8f839
171
+ film: His Girl Friday
172
+ occurred_at: 2026-10-03T21:30
173
+ where: cinema
174
+ day: 2026-10-03
175
+ label: His Girl Friday (03/10 21:30, home)
176
+ ---
177
+ ```
178
+
179
+ **`check_table`**
180
+
181
+ ```json
182
+ { "table": "watch" }
183
+ ```
184
+
185
+ Answer:
186
+
187
+ ```json
188
+ {
189
+ "table": "watch",
190
+ "rows_checked": 1,
191
+ "total": 1,
192
+ "violations": [
193
+ {
194
+ "path": "datanotes/main/public/watch/2026-10-03T2130.md",
195
+ "violations": [
196
+ {
197
+ "column": "label",
198
+ "code": "generated",
199
+ "message": "should be \"His Girl Friday (03/10 21:30, cinema)\" (computed)",
200
+ "value": "His Girl Friday (03/10 21:30, home)"
201
+ }
202
+ ]
203
+ }
204
+ ]
205
+ }
206
+ ```
207
+
208
+ **`recompute_columns`**
209
+
210
+ ```json
211
+ { "table": "watch" }
212
+ ```
213
+
214
+ Answer:
215
+
216
+ ```json
217
+ {
218
+ "ok": true,
219
+ "op": "recompute_columns",
220
+ "notes_changed": 1,
221
+ "planned_edits": [
222
+ {
223
+ "path": "datanotes/main/public/watch/2026-10-03T2130.md",
224
+ "set": { "label": "His Girl Friday (03/10 21:30, cinema)" },
225
+ "remove": []
226
+ }
227
+ ],
228
+ "commits": [{ "table": "main.public.watch", "version": 3, "operation": "RECOMPUTE" }],
229
+ "migration_id": "20261012T071948-recompute_columns-recompute-id-day-label-of-watc"
230
+ }
231
+ ```
232
+
233
+ **`select_rows`**
234
+
235
+ ```json
236
+ { "table": "watch", "columns": ["where", "label"] }
237
+ ```
238
+
239
+ Answer:
240
+
241
+ ```json
242
+ {
243
+ "table": "main.public.watch",
244
+ "total": 1,
245
+ "rows": [
246
+ {
247
+ "path": "datanotes/main/public/watch/2026-10-03T2130.md",
248
+ "where": "cinema",
249
+ "label": "His Girl Friday (03/10 21:30, cinema)"
250
+ }
251
+ ]
252
+ }
253
+ ```
254
+
255
+ ## Rename a note by hand
256
+
257
+ You can rename or move a note outside the engine: in a file manager, with git or with a sync tool. The engine then rewrites every link to the note’s old path, across the vault. It rewrites rows and plain notes alike, as it does when it renames a note itself. Here you rename the plain note `Notes/Film night.md`. A viewing of Charade, a row of the `watch` table, links to it in its text.
258
+
259
+ You rename `Notes/Film night.md` to `Notes/Friday film night.md` by hand, outside the engine.
260
+
261
+ The engine waits until the vault has been quiet for 4 seconds, so an editor or a sync tool finishes first. The journal then lists the repair as one operation:
262
+
263
+ **`list_migrations`**
264
+
265
+ ```json
266
+ { "limit": 1 }
267
+ ```
268
+
269
+ Answer:
270
+
271
+ ```json
272
+ [
273
+ {
274
+ "op": "repair_links",
275
+ "summary": "repair the links after the hand move of Notes/Film night.md to Notes/Friday film night.md",
276
+ "notes_changed": 0,
277
+ "files": null
278
+ }
279
+ ]
280
+ ```
281
+
282
+ The viewing now links to the new name:
283
+
284
+ **`read_note`**
285
+
286
+ ```json
287
+ { "path": "datanotes/main/public/watch/2026-09-25T2100.md" }
288
+ ```
289
+
290
+ Answer:
291
+
292
+ ```markdown
293
+ ---
294
+ table: main.public.watch
295
+ id: 01a12efb-c19b-751f-b73a-92c00d579131
296
+ occurred_at: 2026-09-25T21:00
297
+ film: "[[Charade]]"
298
+ where: home
299
+ ---
300
+ Part of [[Friday film night]]. Four of us on the sofa, and nobody guessed the ending.
301
+ ```
302
+
303
+ `undo_migration` takes the repair back. The change feed lists its changes after the move, each with the move in `cause`. Links that an editor such as Obsidian already rewrote stay as they are: the engine doesn’t write them twice. To leave links as they are after a hand move, set `repairLinksOnHandMove` to `false` in the [engine’s settings](https://datanotes.dev/reference/settings/).
304
+
305
+ ## Find front matter keys the table doesn’t declare
306
+
307
+ Suppose someone later adds a front matter key by hand. `infer_table` with a `table` lists the keys its rows use that the table doesn’t declare. Add them with `add_column`, or remove them from the rows.
308
+
309
+ You edit `datanotes/main/public/film/Metropolis.md` by hand, outside the engine. The note now reads:
310
+
311
+ ```markdown
312
+ ---
313
+ table: main.public.film
314
+ id: 01a12efb-81ad-7b8f-aa3d-16ff4a7685be
315
+ year: 1927
316
+ minutes: 153
317
+ status: to watch
318
+ cast:
319
+ - "[[Brigitte Helm]]"
320
+ director: Fritz Lang
321
+ title: Metropolis
322
+ ---
323
+ ```
324
+
325
+ **`infer_table`**
326
+
327
+ ```json
328
+ { "table": "film" }
329
+ ```
330
+
331
+ Answer:
332
+
333
+ ```json
334
+ {
335
+ "files": 6,
336
+ "paths": [
337
+ "datanotes/main/public/film/Battleship Potemkin.md",
338
+ "datanotes/main/public/film/Charade.md",
339
+ "datanotes/main/public/film/His Girl Friday.md",
340
+ "datanotes/main/public/film/Metropolis.md",
341
+ "datanotes/main/public/film/Nosferatu.md",
342
+ "datanotes/main/public/film/The Gold Rush.md"
343
+ ],
344
+ "skipped": [],
345
+ "columns": [
346
+ {
347
+ "name": "cast",
348
+ "type": "list",
349
+ "description": null,
350
+ "stats": {
351
+ "present": 1,
352
+ "filled": 1,
353
+ "distinct": 1,
354
+ "types": { "list": 1 },
355
+ "examples": [["[[Brigitte Helm]]"]]
356
+ },
357
+ "items": "link"
358
+ }
359
+ ],
360
+ "hints": [
361
+ "add these with add_column, each with a description of what it holds (null here; dense: false unless every row should carry the front matter key), or drop those front matter keys from the rows"
362
+ ]
363
+ }
364
+ ```
365
+
366
+ ## Find rows whose front matter can’t be read
367
+
368
+ A hand edit can break a note’s YAML, such as a title with a colon in it. The engine can’t read the row any more, so `select_rows` and every other read leave it out. The engine reports it in three places instead:
369
+
370
+ * **Change feed**: an event of kind `unreadable`
371
+ * **`check_table`**: a violation with code `unreadable`
372
+ * **`describe_database`**: the table’s `unreadable_count`
373
+
374
+ Here you give Sherlock Jr. a title with a colon by hand.
375
+
376
+ **`list_changes`**
377
+
378
+ ```json
379
+ { "tables": ["film"] }
380
+ ```
381
+
382
+ You edit `datanotes/main/public/film/Sherlock Jr..md` by hand, outside the engine. The note now reads:
383
+
384
+ ```markdown
385
+ ---
386
+ table: main.public.film
387
+ id: 01a12efb-6c40-791d-b62a-7b324b13333f
388
+ title: Sherlock Jr.: the projectionist
389
+ year: 1924
390
+ status: watched
391
+ tags:
392
+ - seen-2026
393
+ rating: 4
394
+ ---
395
+ ```
396
+
397
+ The change feed reports the broken note:
398
+
399
+ **`list_changes`**
400
+
401
+ ```json
402
+ { "since": 37, "tables": ["film"], "kinds": ["unreadable"], "wait_ms": 10000 }
403
+ ```
404
+
405
+ Answer:
406
+
407
+ ```json
408
+ [
409
+ {
410
+ "kind": "unreadable",
411
+ "path": "datanotes/main/public/film/Sherlock Jr..md",
412
+ "violations": [
413
+ {
414
+ "path": "datanotes/main/public/film/Sherlock Jr..md",
415
+ "violations": [
416
+ {
417
+ "code": "unreadable",
418
+ "message": "the front matter cannot be read, so the row is left out of every read: Nested mappings are not allowed in compact mappings at line 3, column 8:"
419
+ }
420
+ ]
421
+ }
422
+ ]
423
+ }
424
+ ]
425
+ ```
426
+
427
+ `check_table` lists it with the rows that break the schema:
428
+
429
+ **`check_table`**
430
+
431
+ ```json
432
+ { "table": "film" }
433
+ ```
434
+
435
+ Answer:
436
+
437
+ ```json
438
+ [
439
+ {
440
+ "path": "datanotes/main/public/film/Sherlock Jr..md",
441
+ "violations": [
442
+ {
443
+ "code": "unreadable",
444
+ "message": "the front matter cannot be read, so the row is left out of every read: Nested mappings are not allowed in compact mappings at line 3, column 8:"
445
+ }
446
+ ]
447
+ }
448
+ ]
449
+ ```
450
+
451
+ `describe_database` counts it apart from `row_count`:
452
+
453
+ **`describe_database`** (no arguments)
454
+
455
+ Answer:
456
+
457
+ ```json
458
+ {
459
+ "name": "film",
460
+ "qualified": "main.public.film",
461
+ "row_count": 2,
462
+ "description": "Classic films I watched or want to watch",
463
+ "unreadable_count": 1
464
+ }
465
+ ```
466
+
467
+ Quote the title to fix the note. The row is back:
468
+
469
+ You edit `datanotes/main/public/film/Sherlock Jr..md` by hand, outside the engine. The note now reads:
470
+
471
+ ```markdown
472
+ ---
473
+ table: main.public.film
474
+ id: 01a12efb-6c40-791d-b62a-7b324b13333f
475
+ title: "Sherlock Jr.: the projectionist"
476
+ year: 1924
477
+ status: watched
478
+ tags:
479
+ - seen-2026
480
+ rating: 4
481
+ ---
482
+ ```
483
+
484
+ **`select_rows`**
485
+
486
+ ```json
487
+ {
488
+ "table": "film",
489
+ "columns": ["title"],
490
+ "where": { "title": { "contains": "Sherlock" } }
491
+ }
492
+ ```
493
+
494
+ Answer:
495
+
496
+ ```json
497
+ {
498
+ "table": "main.public.film",
499
+ "total": 1,
500
+ "rows": [
501
+ {
502
+ "path": "datanotes/main/public/film/Sherlock Jr..md",
503
+ "title": "Sherlock Jr.: the projectionist"
504
+ }
505
+ ]
506
+ }
507
+ ```
@@ -0,0 +1,51 @@
1
+ # Sync the vault between devices
2
+
3
+ > Keep a vault in Dropbox, iCloud Drive, Syncthing or Obsidian Sync: run one engine per synced folder, and find conflict copies and unreadable notes with check_table.
4
+
5
+ You can keep the vault in a sync tool, such as Dropbox, iCloud Drive, Syncthing or Obsidian Sync, so your notes reach every device. The engine works on a synced vault when you follow one rule: one engine per synced folder.
6
+
7
+ A sync tool can also leave conflict copies and notes it merged badly. `check_table` finds them.
8
+
9
+ ## Run one engine per synced folder
10
+
11
+ The engine keeps its lock, `engine.lock`, in its store, outside the vault. The sync tool carries the vault but not the store. So an engine on another device doesn’t see the lock, and it starts. Two engines on one folder then lose each other’s writes.
12
+
13
+ Follow these rules on a synced vault:
14
+
15
+ * **One engine**: run the engine on one device only, such as a desktop or a server that stays on.
16
+ * **No second engine on the same machine**: never run the engine on the host and the engine in Docker on the same folder. Each keeps a store of its own, so neither refuses the other.
17
+ * **Other devices edit by hand**: on a laptop or a phone, edit notes in Obsidian or another editor. Programs and agents on those devices call the one engine over HTTP, with a [named token](https://datanotes.dev/guides/security/tokens/) each.
18
+
19
+ To move the engine to another device, stop it with `datanotes stop` first. Then let the sync finish before you start the engine on the new device.
20
+
21
+ The sync tool writes the other devices’ edits into the folder. The engine reads them as [hand edits](https://datanotes.dev/guides/data-quality/hand-edits/): it checks each one, but can’t refuse it.
22
+
23
+ ## Pick a sync tool
24
+
25
+ Each tool handles a note changed on two devices at once in its own way:
26
+
27
+ | Tool | What it does with a conflict |
28
+ | ------------- | ------------------------------------------------------------------------ |
29
+ | Dropbox | keeps both: the second one gets `conflicted copy` and a date in its name |
30
+ | iCloud Drive | keeps both: the second one gets a number, such as `Metropolis 2.md` |
31
+ | Syncthing | keeps both: the second one gets `.sync-conflict-` and a date in its name |
32
+ | Obsidian Sync | merges the two versions into one note |
33
+
34
+ A conflict copy in a table’s folder has the same front matter, so it becomes a second row of the same table. A merge can leave front matter that no longer parses.
35
+
36
+ ## Find conflict copies and unreadable notes
37
+
38
+ Run `check_table` without `table` after a sync, or whenever a device was offline for a while. It checks every table and reports what a sync left behind:
39
+
40
+ * **Conflict copies**: a copy breaks each `unique` column, and an identity column that must match the file name. Its row lists the violations.
41
+ * **Unreadable notes**: a table whose notes can’t be read is listed in `errors`, with the error that names the problem.
42
+
43
+ To see the copies themselves, call `list_folder` on the table’s folder and look for the names in the table above.
44
+
45
+ For each copy, keep one version:
46
+
47
+ 1. Compare the copy with the original with `read_note`.
48
+ 2. Move what you want to keep into the original with `update_row`.
49
+ 3. Delete the copy with `delete_rows`, or delete the file by hand.
50
+
51
+ Run `check_table` again: the copies no longer show up.
@@ -0,0 +1,90 @@
1
+ # Validation
2
+
3
+ > What the engine checks on every write, how it refuses a bad value, and how to try a write first.
4
+
5
+ The engine checks every write against its table before it changes anything: required values, types, ranges, enums, links, unique values and CHECK constraints. When one check fails, it refuses the whole write and writes nothing. The refusal names the note, the column or rule, the values the engine accepts, and how to fix the call.
6
+
7
+ People, programs and agents go through the same checks. Hand edits are checked too, but never refused: [Hand edits](https://datanotes.dev/guides/data-quality/hand-edits/) shows what happens to them.
8
+
9
+ ## See how the engine refuses bad values
10
+
11
+ The engine checks every write against the columns. If one value is wrong, it writes nothing, and the answer names the column and the reason. For an enum, the answer also lists the allowed values.
12
+
13
+ **`insert_row`**
14
+
15
+ ```json
16
+ {
17
+ "table": "film",
18
+ "values": { "title": "Battleship Potemkin", "minutes": 0, "status": "finished" }
19
+ }
20
+ ```
21
+
22
+ Refused:
23
+
24
+ ```text
25
+ insert_row refused — nothing was written:
26
+ invalid datanotes/main/public/film/Battleship Potemkin.md: minutes must be >= 1
27
+ invalid datanotes/main/public/film/Battleship Potemkin.md: status must be one of: to watch; watched
28
+ Hint: Give a value inside the column's range (the message names the bound); describe_table shows its min and max. Use one of the allowed values listed in the message, written exactly; describe_table shows what each one means. To add a value, alter_column the enum. Then call insert_row again (dry_run: true checks without writing).
29
+ Docs: https://datanotes.dev/guides/database-objects/tables.md#describe-a-table-before-writing-to-it (offline: read_docs("guides/database-objects/tables#describe-a-table-before-writing-to-it"))
30
+ ```
31
+
32
+ A unique column refuses a second row with the same value:
33
+
34
+ **`insert_row`**
35
+
36
+ ```json
37
+ { "table": "film", "values": { "title": "Metropolis" } }
38
+ ```
39
+
40
+ Refused:
41
+
42
+ ```text
43
+ insert_row refused — nothing was written:
44
+ invalid datanotes/main/public/film/Metropolis-2.md: title must be unique (also used by datanotes/main/public/film/Metropolis.md)
45
+ Hint: Another row already has this value: choose another one, or update that row instead (select_rows finds it). Then call insert_row again (dry_run: true checks without writing).
46
+ Docs: https://datanotes.dev/guides/read-and-write/write-rows.md#update-one-row (offline: read_docs("guides/read-and-write/write-rows#update-one-row"))
47
+ ```
48
+
49
+ ## Try a write first with `dry_run`
50
+
51
+ Every write accepts `dry_run: true`. The engine checks the write and reports what it would do, but writes nothing.
52
+
53
+ **`insert_row`**
54
+
55
+ ```json
56
+ {
57
+ "table": "film",
58
+ "values": { "title": "Battleship Potemkin", "year": 1925, "minutes": 75 },
59
+ "dry_run": true
60
+ }
61
+ ```
62
+
63
+ Answer:
64
+
65
+ ```json
66
+ {
67
+ "ok": true,
68
+ "op": "insert_row",
69
+ "dry_run": true,
70
+ "planned_creates": ["datanotes/main/public/film/Battleship Potemkin.md"]
71
+ }
72
+ ```
73
+
74
+ The table still has one film:
75
+
76
+ **`select_rows`**
77
+
78
+ ```json
79
+ { "table": "film", "columns": ["title"] }
80
+ ```
81
+
82
+ Answer:
83
+
84
+ ```json
85
+ {
86
+ "table": "main.public.film",
87
+ "total": 1,
88
+ "rows": [{ "path": "datanotes/main/public/film/Metropolis.md", "title": "Metropolis" }]
89
+ }
90
+ ```