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.
- package/NOTICE +9 -0
- package/README.md +207 -28
- package/SECURITY.md +92 -45
- package/dist/THIRD-PARTY-NOTICES.txt +682 -0
- package/dist/api/catalog.d.cts +69 -0
- package/dist/api/catalog.d.ts +50 -2
- package/dist/api/cli-options.d.cts +39 -0
- package/dist/api/cli-options.d.ts +39 -0
- package/dist/api/client.d.cts +89 -0
- package/dist/api/client.d.ts +19 -30
- package/dist/api/docs.d.cts +211 -0
- package/dist/api/docs.d.ts +211 -0
- package/dist/api/guidance.d.cts +81 -0
- package/dist/api/guidance.d.ts +81 -0
- package/dist/api/index.cjs +6610 -2247
- package/dist/api/index.d.cts +13 -0
- package/dist/api/index.d.ts +10 -7
- package/dist/api/index.js +6610 -2247
- package/dist/api/local.cjs +118 -6
- package/dist/api/local.d.cts +95 -0
- package/dist/api/local.d.ts +70 -6
- package/dist/api/local.js +104 -6
- package/dist/api/mcp.cjs +17384 -0
- package/dist/api/mcp.d.cts +108 -0
- package/dist/api/mcp.d.ts +108 -0
- package/dist/api/mcp.js +17334 -0
- package/dist/api/otlp.d.cts +65 -0
- package/dist/api/redact.d.cts +17 -0
- package/dist/api/redact.d.ts +17 -0
- package/dist/api/requests.d.cts +26 -0
- package/dist/api/requests.d.ts +14 -16
- package/dist/api/results.d.cts +16 -0
- package/dist/api/results.d.ts +2 -1
- package/dist/api/tokens.d.cts +7 -0
- package/dist/datanotes.cjs +62911 -28409
- package/dist/docs/administration/configuration.md +59 -0
- package/dist/docs/administration/docker.md +80 -0
- package/dist/docs/administration/git-sync.md +71 -0
- package/dist/docs/administration/limits.md +69 -0
- package/dist/docs/administration/run-the-service.md +219 -0
- package/dist/docs/administration/status.md +61 -0
- package/dist/docs/administration/use-with-obsidian.md +75 -0
- package/dist/docs/api/operations/add_column.md +49 -0
- package/dist/docs/api/operations/add_constraint.md +55 -0
- package/dist/docs/api/operations/adopt_rows.md +50 -0
- package/dist/docs/api/operations/align_relations.md +41 -0
- package/dist/docs/api/operations/alter_column.md +46 -0
- package/dist/docs/api/operations/alter_table.md +67 -0
- package/dist/docs/api/operations/batch.md +48 -0
- package/dist/docs/api/operations/changes_stream.md +28 -0
- package/dist/docs/api/operations/check_table.md +29 -0
- package/dist/docs/api/operations/clone_table.md +48 -0
- package/dist/docs/api/operations/comment_on.md +42 -0
- package/dist/docs/api/operations/configure_database.md +52 -0
- package/dist/docs/api/operations/create_table.md +78 -0
- package/dist/docs/api/operations/create_view.md +54 -0
- package/dist/docs/api/operations/create_webhook.md +37 -0
- package/dist/docs/api/operations/define_base.md +52 -0
- package/dist/docs/api/operations/delete_rows.md +43 -0
- package/dist/docs/api/operations/delete_webhook.md +31 -0
- package/dist/docs/api/operations/describe_database.md +30 -0
- package/dist/docs/api/operations/describe_history.md +40 -0
- package/dist/docs/api/operations/describe_semantic_index.md +24 -0
- package/dist/docs/api/operations/describe_table.md +38 -0
- package/dist/docs/api/operations/docs.md +13 -0
- package/dist/docs/api/operations/drop_column.md +41 -0
- package/dist/docs/api/operations/drop_constraint.md +39 -0
- package/dist/docs/api/operations/drop_table.md +43 -0
- package/dist/docs/api/operations/drop_view.md +38 -0
- package/dist/docs/api/operations/engine_status.md +23 -0
- package/dist/docs/api/operations/graph_neighbors.md +32 -0
- package/dist/docs/api/operations/graph_path.md +32 -0
- package/dist/docs/api/operations/graph_traverse.md +43 -0
- package/dist/docs/api/operations/identify_session.md +24 -0
- package/dist/docs/api/operations/infer_table.md +43 -0
- package/dist/docs/api/operations/insert_row.md +48 -0
- package/dist/docs/api/operations/list_changes.md +35 -0
- package/dist/docs/api/operations/list_folder.md +28 -0
- package/dist/docs/api/operations/list_link_targets.md +44 -0
- package/dist/docs/api/operations/list_migrations.md +35 -0
- package/dist/docs/api/operations/list_tables.md +32 -0
- package/dist/docs/api/operations/list_webhooks.md +23 -0
- package/dist/docs/api/operations/manual_index.md +14 -0
- package/dist/docs/api/operations/manual_page.md +22 -0
- package/dist/docs/api/operations/merge_rows.md +44 -0
- package/dist/docs/api/operations/move_rows.md +52 -0
- package/dist/docs/api/operations/openapi.md +13 -0
- package/dist/docs/api/operations/openapi_post.md +20 -0
- package/dist/docs/api/operations/read_note.md +29 -0
- package/dist/docs/api/operations/recompute_columns.md +44 -0
- package/dist/docs/api/operations/record_tool_call.md +36 -0
- package/dist/docs/api/operations/rename_column.md +44 -0
- package/dist/docs/api/operations/rename_value.md +45 -0
- package/dist/docs/api/operations/restore_database.md +51 -0
- package/dist/docs/api/operations/restore_table.md +46 -0
- package/dist/docs/api/operations/search_notes.md +28 -0
- package/dist/docs/api/operations/select_rows.md +61 -0
- package/dist/docs/api/operations/semantic_search.md +34 -0
- package/dist/docs/api/operations/semantic_search_docs.md +26 -0
- package/dist/docs/api/operations/set_table_bases.md +44 -0
- package/dist/docs/api/operations/status.md +13 -0
- package/dist/docs/api/operations/stop_engine.md +23 -0
- package/dist/docs/api/operations/sync_semantic_index.md +33 -0
- package/dist/docs/api/operations/table_changes.md +31 -0
- package/dist/docs/api/operations/tags/auxiliary.md +14 -0
- package/dist/docs/api/operations/tags/change-feed.md +8 -0
- package/dist/docs/api/operations/tags/history.md +17 -0
- package/dist/docs/api/operations/tags/queries.md +15 -0
- package/dist/docs/api/operations/tags/rows-dml.md +16 -0
- package/dist/docs/api/operations/tags/schema-ddl.md +22 -0
- package/dist/docs/api/operations/tags/service.md +15 -0
- package/dist/docs/api/operations/tags/tokens.md +13 -0
- package/dist/docs/api/operations/tokens_create.md +34 -0
- package/dist/docs/api/operations/tokens_delete.md +29 -0
- package/dist/docs/api/operations/tokens_list.md +21 -0
- package/dist/docs/api/operations/tokens_me.md +20 -0
- package/dist/docs/api/operations/tokens_rotate.md +29 -0
- package/dist/docs/api/operations/tokens_update.md +34 -0
- package/dist/docs/api/operations/undo_migration.md +46 -0
- package/dist/docs/api/operations/update_row.md +60 -0
- package/dist/docs/api/operations/update_rows.md +46 -0
- package/dist/docs/api/operations/vacuum_table.md +37 -0
- package/dist/docs/api.md +135 -0
- package/dist/docs/develop/build-an-app.md +320 -0
- package/dist/docs/develop/build-on-datanotes.md +138 -0
- package/dist/docs/develop/change-feed-and-webhooks.md +70 -0
- package/dist/docs/develop/client.md +116 -0
- package/dist/docs/develop/concurrency.md +153 -0
- package/dist/docs/develop/http-api.md +79 -0
- package/dist/docs/develop/telemetry.md +86 -0
- package/dist/docs/guides/agents/docs-offline.md +99 -0
- package/dist/docs/guides/agents/how-agents-use-datanotes.md +124 -0
- package/dist/docs/guides/agents/prompts.md +495 -0
- package/dist/docs/guides/agents/reading-errors.md +112 -0
- package/dist/docs/guides/data-quality/check-constraints.md +379 -0
- package/dist/docs/guides/data-quality/check-table.md +63 -0
- package/dist/docs/guides/data-quality/hand-edits.md +507 -0
- package/dist/docs/guides/data-quality/sync-between-devices.md +51 -0
- package/dist/docs/guides/data-quality/validation.md +90 -0
- package/dist/docs/guides/database-objects/bases.md +480 -0
- package/dist/docs/guides/database-objects/catalogs-and-schemas.md +897 -0
- package/dist/docs/guides/database-objects/columns.md +1332 -0
- package/dist/docs/guides/database-objects/relations.md +1454 -0
- package/dist/docs/guides/database-objects/tables.md +1404 -0
- package/dist/docs/guides/database-objects/views.md +1003 -0
- package/dist/docs/guides/history/back-up.md +195 -0
- package/dist/docs/guides/history/change-data-feed.md +247 -0
- package/dist/docs/guides/history/journal-and-undo.md +262 -0
- package/dist/docs/guides/history/restore.md +341 -0
- package/dist/docs/guides/history/table-history.md +657 -0
- package/dist/docs/guides/history/vacuum.md +35 -0
- package/dist/docs/guides/read-and-write/automatic-links.md +768 -0
- package/dist/docs/guides/read-and-write/batch.md +139 -0
- package/dist/docs/guides/read-and-write/import-notes.md +896 -0
- package/dist/docs/guides/read-and-write/joins-and-aggregates.md +251 -0
- package/dist/docs/guides/read-and-write/read-and-filter.md +536 -0
- package/dist/docs/guides/read-and-write/write-rows.md +573 -0
- package/dist/docs/guides/search/link-graph.md +146 -0
- package/dist/docs/guides/search/semantic.md +133 -0
- package/dist/docs/guides/search/text.md +142 -0
- package/dist/docs/guides/security/security-model.md +110 -0
- package/dist/docs/guides/security/tokens.md +60 -0
- package/dist/docs/guides/security/who-did-what.md +42 -0
- package/dist/docs/index.md +113 -0
- package/dist/docs/llms.txt +33 -0
- package/dist/docs/reference/cli.md +136 -0
- package/dist/docs/reference/compatibility.md +63 -0
- package/dist/docs/reference/errors.md +72 -0
- package/dist/docs/reference/glossary.md +161 -0
- package/dist/docs/reference/operations/auxiliary.md +116 -0
- package/dist/docs/reference/operations/history.md +168 -0
- package/dist/docs/reference/operations/queries.md +169 -0
- package/dist/docs/reference/operations/rows.md +185 -0
- package/dist/docs/reference/operations/schema.md +316 -0
- package/dist/docs/reference/operations.md +96 -0
- package/dist/docs/reference/settings.md +31 -0
- package/dist/docs/release-notes.md +67 -0
- package/dist/docs/start/connect-agents.md +204 -0
- package/dist/docs/start/how-it-works.md +317 -0
- package/dist/docs/start/quick-start.md +126 -0
- 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
|
+
```
|