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
@@ -0,0 +1,573 @@
1
+ # Write and move rows
2
+
3
+ > Insert a row, update one row or many, edit a note’s text, upsert, guard against concurrent edits, delete and move rows.
4
+
5
+ The engine checks every write before it changes anything, records it in the journal, and lets you undo it with `undo_migration`. A write answers with the notes it created, changed, moved or deleted.
6
+
7
+ The first example adds a row to the film diary of [Tables](https://datanotes.dev/guides/database-objects/tables/); the others work on a small diary of five classic films.
8
+
9
+ When several clients write at once, the engine checks each write on the latest data. [Concurrent writes](https://datanotes.dev/develop/concurrency/) explains the rules and when to retry.
10
+
11
+ ## Insert a row
12
+
13
+ `insert_row` writes a new note. The engine fills in the default (`status`) and an `id`, and names the note from `filename`. `body` sets the note’s text.
14
+
15
+ **`insert_row`**
16
+
17
+ ```json
18
+ {
19
+ "table": "film",
20
+ "values": { "title": "Metropolis", "year": 1927, "minutes": 153 },
21
+ "body": "Next on my list. Two and a half hours of silent film need a quiet evening.\n"
22
+ }
23
+ ```
24
+
25
+ Answer:
26
+
27
+ ```json
28
+ {
29
+ "ok": true,
30
+ "op": "insert_row",
31
+ "files_created": 1,
32
+ "planned_creates": ["datanotes/main/public/film/Metropolis.md"],
33
+ "commits": [{ "table": "main.public.film", "version": 1, "operation": "WRITE" }],
34
+ "migration_id": "20261012T071946-insert_row-insert-into-film"
35
+ }
36
+ ```
37
+
38
+ `datanotes/main/public/film/Metropolis.md` now reads:
39
+
40
+ ```markdown
41
+ ---
42
+ table: main.public.film
43
+ id: 01a12efb-5925-7551-9fc3-ca301818d73d
44
+ title: Metropolis
45
+ year: 1927
46
+ minutes: 153
47
+ status: to watch
48
+ ---
49
+ Next on my list. Two and a half hours of silent film need a quiet evening.
50
+ ```
51
+
52
+ ## Pick a free file name for a new row
53
+
54
+ Without `path`, the table’s filename pattern names the new note. When another note has that name, the engine adds `-2`, `-3` and so on.
55
+
56
+ A `path` you pass must be free. Windows and macOS see `Nosferatu.md` and `nosferatu.md` as one file. So on every system, the engine refuses a name that another note has in another case, and names that note:
57
+
58
+ **`insert_row`**
59
+
60
+ ```json
61
+ {
62
+ "table": "film",
63
+ "values": { "title": "The Kid", "year": 1921 },
64
+ "path": "datanotes/main/public/film/nosferatu.md"
65
+ }
66
+ ```
67
+
68
+ Refused:
69
+
70
+ ```text
71
+ insert_row refused — nothing was written:
72
+ conflict datanotes/main/public/film/nosferatu.md: note already exists in another case: "datanotes/main/public/film/Nosferatu.md" (one file on Windows and macOS)
73
+ Hint: Another note has this file name, maybe in another case (Windows and macOS see one file): pass another "path", give the row values its table's filename pattern turns into a free name, or update that note with update_row. Then call insert_row again.
74
+ Docs: https://datanotes.dev/guides/read-and-write/write-rows.md#pick-a-free-file-name-for-a-new-row (offline: read_docs("guides/read-and-write/write-rows#pick-a-free-file-name-for-a-new-row"))
75
+ ```
76
+
77
+ ## Update one row
78
+
79
+ `update_row` changes exactly one row, which you address by `path` or by its `id`. `set` sets columns and `unset` removes them. The engine checks the whole row before it writes anything.
80
+
81
+ **`update_row`**
82
+
83
+ ```json
84
+ {
85
+ "table": "film",
86
+ "path": "datanotes/main/public/film/His Girl Friday.md",
87
+ "set": { "status": "watched", "rating": 5, "tags": ["screwball"] }
88
+ }
89
+ ```
90
+
91
+ Answer:
92
+
93
+ ```json
94
+ {
95
+ "ok": true,
96
+ "op": "update_row",
97
+ "notes_changed": 1,
98
+ "planned_edits": [
99
+ {
100
+ "path": "datanotes/main/public/film/His Girl Friday.md",
101
+ "set": { "status": "watched", "rating": 5, "tags": ["screwball"] },
102
+ "remove": []
103
+ }
104
+ ],
105
+ "commits": [{ "table": "main.public.film", "version": 6, "operation": "UPDATE" }],
106
+ "migration_id": "20261012T071951-update_row-update-film"
107
+ }
108
+ ```
109
+
110
+ **`update_row`**
111
+
112
+ ```json
113
+ {
114
+ "table": "film",
115
+ "path": "datanotes/main/public/film/His Girl Friday.md",
116
+ "unset": ["tags"]
117
+ }
118
+ ```
119
+
120
+ The engine refuses a path that isn’t a row, and creates nothing:
121
+
122
+ **`update_row`**
123
+
124
+ ```json
125
+ {
126
+ "table": "film",
127
+ "path": "datanotes/main/public/film/Night of the Living Dead.md",
128
+ "set": { "rating": 4 }
129
+ }
130
+ ```
131
+
132
+ Refused:
133
+
134
+ ```text
135
+ film has no row at datanotes/main/public/film/Night of the Living Dead.md
136
+ Hint: Name the row by its "path" (the vault path of its note, e.g. "Films/Metropolis.md") or its "id": select_rows with columns ["path", "id"] and a "where" finds them.
137
+ 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"))
138
+ ```
139
+
140
+ ## Edit a note’s text
141
+
142
+ `update_row` edits the note’s text with these arguments:
143
+
144
+ * **`body_sections`**: replaces the text under one heading and leaves the rest of the note alone; the engine adds a missing heading at the end
145
+ * **`body_append`** and **`body_prepend`**: add text after or before the existing text
146
+ * **`body`**: replaces all of the text
147
+
148
+ **`update_row`**
149
+
150
+ ```json
151
+ {
152
+ "table": "film",
153
+ "path": "datanotes/main/public/film/Nosferatu.md",
154
+ "body_sections": {
155
+ "Rewatch": "Watched it again in October. The shadow on the stairs still works a century later."
156
+ },
157
+ "body_append": "Next: Metropolis, for more German Expressionism.\n"
158
+ }
159
+ ```
160
+
161
+ Answer:
162
+
163
+ ```json
164
+ {
165
+ "ok": true,
166
+ "op": "update_row",
167
+ "notes_changed": 1,
168
+ "planned_edits": [
169
+ {
170
+ "path": "datanotes/main/public/film/Nosferatu.md",
171
+ "set": {},
172
+ "remove": [],
173
+ "body": true
174
+ }
175
+ ],
176
+ "commits": [{ "table": "main.public.film", "version": 8, "operation": "UPDATE" }],
177
+ "migration_id": "20261012T071951-update_row-update-film-3",
178
+ "ambiguous_mentions": [
179
+ {
180
+ "path": "datanotes/main/public/film/Nosferatu.md",
181
+ "text": "Metropolis",
182
+ "reason": "sentence_start",
183
+ "candidates": ["datanotes/main/public/film/Metropolis.md"],
184
+ "context": "Next: Metropolis, for more German Expressionism.",
185
+ "hint": "\"Metropolis\" starts a sentence, a line or a label, so its capital does not make it a name: read the note and the text around it, then write the link with update_row and autolink: false, or leave the text as it is."
186
+ }
187
+ ]
188
+ }
189
+ ```
190
+
191
+ `datanotes/main/public/film/Nosferatu.md` now reads:
192
+
193
+ ```markdown
194
+ ---
195
+ table: main.public.film
196
+ id: 01a12efb-6c2e-7852-8b4b-ed121d84aec5
197
+ title: Nosferatu
198
+ year: 1922
199
+ status: watched
200
+ rating: 5
201
+ ---
202
+ ## Notes
203
+
204
+
205
+ Max Schreck doesn’t need a single word.
206
+
207
+
208
+ ## Rewatch
209
+
210
+
211
+ Watched it again in October. The shadow on the stairs still works a century later.
212
+
213
+
214
+ Next: Metropolis, for more German Expressionism.
215
+ ```
216
+
217
+ The engine links Metropolis, the note of another film, and lists the link in `links_added`. [Automatic links](https://datanotes.dev/guides/read-and-write/automatic-links/) shows which names it links.
218
+
219
+ ## Guard a write against concurrent edits with `expect`
220
+
221
+ A person may edit a note between an agent’s read and its write. `expect` makes the write a compare-and-set: it goes through only if the row still has those values.
222
+
223
+ **`update_row`**
224
+
225
+ ```json
226
+ {
227
+ "table": "film",
228
+ "path": "datanotes/main/public/film/Sherlock Jr..md",
229
+ "expect": { "status": "to watch" },
230
+ "set": { "status": "watched" }
231
+ }
232
+ ```
233
+
234
+ Answer:
235
+
236
+ ```json
237
+ {
238
+ "ok": true,
239
+ "op": "update_row",
240
+ "notes_changed": 1,
241
+ "planned_edits": [
242
+ {
243
+ "path": "datanotes/main/public/film/Sherlock Jr..md",
244
+ "set": { "status": "watched" },
245
+ "remove": []
246
+ }
247
+ ],
248
+ "commits": [{ "table": "main.public.film", "version": 9, "operation": "UPDATE" }],
249
+ "migration_id": "20261012T071951-update_row-update-film-4"
250
+ }
251
+ ```
252
+
253
+ Run the same call again. `status` is no longer `to watch`, so the engine writes nothing.
254
+
255
+ **`update_row`**
256
+
257
+ ```json
258
+ {
259
+ "table": "film",
260
+ "path": "datanotes/main/public/film/Sherlock Jr..md",
261
+ "expect": { "status": "to watch" },
262
+ "set": { "status": "watched" }
263
+ }
264
+ ```
265
+
266
+ Refused:
267
+
268
+ ```text
269
+ update_row refused — nothing was written:
270
+ conflict datanotes/main/public/film/Sherlock Jr..md: row changed: expected status = "to watch", found "watched" — read it again
271
+ Hint: The row changed since you read it: read it again (select_rows), then retry update_row with "expect" set to what you read.
272
+ Docs: https://datanotes.dev/guides/read-and-write/write-rows.md#guard-a-write-against-concurrent-edits-with-expect (offline: read_docs("guides/read-and-write/write-rows#guard-a-write-against-concurrent-edits-with-expect"))
273
+ ```
274
+
275
+ ## Update many rows
276
+
277
+ `update_rows` changes every row matching `where`. `expect_count` guards the write: the engine refuses it unless exactly that many rows match, so a filter that’s too broad changes nothing.
278
+
279
+ **`update_rows`**
280
+
281
+ ```json
282
+ {
283
+ "table": "film",
284
+ "where": { "status": "watched" },
285
+ "set": { "tags": ["seen-2026"] },
286
+ "expect_count": 2
287
+ }
288
+ ```
289
+
290
+ Refused:
291
+
292
+ ```text
293
+ update_rows refused — nothing was written:
294
+ conflict datanotes/main/public/film: "where" matched 3 rows, expected 2: nothing was written
295
+ Hint: Check the rows with select_rows and the same "where", then narrow it or change expect_count.
296
+ Docs: https://datanotes.dev/guides/read-and-write/write-rows.md#update-many-rows (offline: read_docs("guides/read-and-write/write-rows#update-many-rows"))
297
+ ```
298
+
299
+ **`update_rows`**
300
+
301
+ ```json
302
+ {
303
+ "table": "film",
304
+ "where": { "status": "watched" },
305
+ "set": { "tags": ["seen-2026"] },
306
+ "expect_count": 3
307
+ }
308
+ ```
309
+
310
+ Answer:
311
+
312
+ ```json
313
+ {
314
+ "ok": true,
315
+ "op": "update_rows",
316
+ "notes_changed": 3,
317
+ "planned_edits": [
318
+ {
319
+ "path": "datanotes/main/public/film/His Girl Friday.md",
320
+ "set": { "tags": ["seen-2026"] },
321
+ "remove": []
322
+ },
323
+ {
324
+ "path": "datanotes/main/public/film/Nosferatu.md",
325
+ "set": { "tags": ["seen-2026"] },
326
+ "remove": []
327
+ },
328
+ {
329
+ "path": "datanotes/main/public/film/Sherlock Jr..md",
330
+ "set": { "tags": ["seen-2026"] },
331
+ "remove": []
332
+ }
333
+ ],
334
+ "commits": [{ "table": "main.public.film", "version": 10, "operation": "UPDATE" }],
335
+ "migration_id": "20261012T071951-update_rows-update-film"
336
+ }
337
+ ```
338
+
339
+ ## Insert or update rows with `merge_rows`
340
+
341
+ `merge_rows` is an upsert: it inserts rows or updates existing ones. A row whose `on` columns match an existing row updates that row, and the engine inserts the others. The whole merge is one operation with one undo.
342
+
343
+ **`merge_rows`**
344
+
345
+ ```json
346
+ {
347
+ "table": "film",
348
+ "on": ["title"],
349
+ "rows": [
350
+ { "title": "Sherlock Jr.", "rating": 4 },
351
+ { "title": "Battleship Potemkin", "year": 1925 }
352
+ ]
353
+ }
354
+ ```
355
+
356
+ Answer:
357
+
358
+ ```json
359
+ {
360
+ "ok": true,
361
+ "op": "merge_rows",
362
+ "notes_changed": 1,
363
+ "files_created": 1,
364
+ "planned_edits": [
365
+ {
366
+ "path": "datanotes/main/public/film/Sherlock Jr..md",
367
+ "set": { "rating": 4 },
368
+ "remove": []
369
+ }
370
+ ],
371
+ "planned_creates": ["datanotes/main/public/film/Battleship Potemkin.md"],
372
+ "commits": [{ "table": "main.public.film", "version": 11, "operation": "MERGE" }],
373
+ "migration_id": "20261012T071951-merge_rows-merge-2-rows-into-film"
374
+ }
375
+ ```
376
+
377
+ **`select_rows`**
378
+
379
+ ```json
380
+ {
381
+ "table": "film",
382
+ "columns": ["title", "rating", "status"],
383
+ "where": { "title": { "in": ["Sherlock Jr.", "Battleship Potemkin"] } }
384
+ }
385
+ ```
386
+
387
+ Answer:
388
+
389
+ ```json
390
+ {
391
+ "table": "main.public.film",
392
+ "total": 2,
393
+ "rows": [
394
+ {
395
+ "path": "datanotes/main/public/film/Battleship Potemkin.md",
396
+ "title": "Battleship Potemkin",
397
+ "rating": null,
398
+ "status": "to watch"
399
+ },
400
+ {
401
+ "path": "datanotes/main/public/film/Sherlock Jr..md",
402
+ "title": "Sherlock Jr.",
403
+ "rating": 4,
404
+ "status": "watched"
405
+ }
406
+ ]
407
+ }
408
+ ```
409
+
410
+ ## Delete rows into the archive
411
+
412
+ By default, `delete_rows` moves the matching rows to the table’s archive folder, so you lose nothing. `mode: "delete"` moves them to the trash instead, and the journal keeps their content for undo. Always pass `where`: without it, every row matches.
413
+
414
+ **`delete_rows`**
415
+
416
+ ```json
417
+ { "table": "film", "where": { "title": "Battleship Potemkin" } }
418
+ ```
419
+
420
+ Answer:
421
+
422
+ ```json
423
+ {
424
+ "ok": true,
425
+ "op": "delete_rows",
426
+ "files_moved": 1,
427
+ "planned_moves": [
428
+ {
429
+ "from": "datanotes/main/public/film/Battleship Potemkin.md",
430
+ "to": "datanotes/main/public/film/_archive/Battleship Potemkin.md"
431
+ }
432
+ ],
433
+ "commits": [{ "table": "main.public.film", "version": 12, "operation": "DELETE" }],
434
+ "migration_id": "20261012T071951-delete_rows-archive-1-rows-of-film"
435
+ }
436
+ ```
437
+
438
+ **`list_folder`**
439
+
440
+ ```json
441
+ { "folder": "datanotes/main/public/film/_archive" }
442
+ ```
443
+
444
+ Answer:
445
+
446
+ ```json
447
+ {
448
+ "folder": "datanotes/main/public/film/_archive",
449
+ "folders": [],
450
+ "files": ["datanotes/main/public/film/_archive/Battleship Potemkin.md"]
451
+ }
452
+ ```
453
+
454
+ ## Move rows to another table
455
+
456
+ `move_rows` moves rows to another table. During the move, it can `rename` keys, `set` values and `drop` keys, and it checks every row against the destination first. The engine reports keys the destination doesn’t know and never drops them silently. `relocate: true` also moves the notes to the destination’s folder.
457
+
458
+ **`create_table`**
459
+
460
+ ```json
461
+ {
462
+ "name": "watchlist",
463
+ "description": "Films I plan to watch soon",
464
+ "columns": [
465
+ {
466
+ "name": "title",
467
+ "type": "string",
468
+ "required": true,
469
+ "description": "The film's title"
470
+ },
471
+ { "name": "released", "type": "integer", "description": "Year the film came out" }
472
+ ],
473
+ "filename": "{title}"
474
+ }
475
+ ```
476
+
477
+ **`move_rows`**
478
+
479
+ ```json
480
+ {
481
+ "table": "film",
482
+ "to_table": "watchlist",
483
+ "where": { "status": "to watch" },
484
+ "relocate": true
485
+ }
486
+ ```
487
+
488
+ Refused:
489
+
490
+ ```text
491
+ move_rows refused — nothing was written:
492
+ invalid datanotes/main/public/film/Battleship Potemkin.md: year is not a column of table "watchlist"
493
+ invalid datanotes/main/public/film/Battleship Potemkin.md: status is not a column of table "watchlist"
494
+ invalid datanotes/main/public/film/Charade.md: year is not a column of table "watchlist"
495
+ invalid datanotes/main/public/film/Charade.md: status is not a column of table "watchlist"
496
+ invalid datanotes/main/public/film/Metropolis.md: year is not a column of table "watchlist"
497
+ invalid datanotes/main/public/film/Metropolis.md: status is not a column of table "watchlist"
498
+ invalid datanotes/main/public/film/The General.md: year is not a column of table "watchlist"
499
+ invalid datanotes/main/public/film/The General.md: status is not a column of table "watchlist"
500
+ Hint: Write only the table's columns: describe_table lists them. To keep a new front matter key, add_column first. Then call move_rows again (dry_run: true checks without writing).
501
+ 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"))
502
+ ```
503
+
504
+ The watchlist has no `status`, `tags` or `rating` column, so the call drops them, and `year` becomes `released`:
505
+
506
+ **`move_rows`**
507
+
508
+ ```json
509
+ {
510
+ "table": "film",
511
+ "to_table": "watchlist",
512
+ "where": { "status": "to watch" },
513
+ "rename": { "year": "released" },
514
+ "drop": ["status", "tags", "rating"],
515
+ "relocate": true
516
+ }
517
+ ```
518
+
519
+ Answer (51 lines)
520
+
521
+ ```json
522
+ {
523
+ "ok": true,
524
+ "op": "move_rows",
525
+ "notes_changed": 4,
526
+ "files_moved": 4,
527
+ "planned_edits": [
528
+ {
529
+ "path": "datanotes/main/public/film/Battleship Potemkin.md",
530
+ "set": { "table": "main.public.watchlist", "released": 1925 },
531
+ "remove": ["year", "status"]
532
+ },
533
+ {
534
+ "path": "datanotes/main/public/film/Charade.md",
535
+ "set": { "table": "main.public.watchlist", "released": 1963 },
536
+ "remove": ["year", "status"]
537
+ },
538
+ {
539
+ "path": "datanotes/main/public/film/Metropolis.md",
540
+ "set": { "table": "main.public.watchlist", "released": 1927 },
541
+ "remove": ["year", "status"]
542
+ },
543
+ {
544
+ "path": "datanotes/main/public/film/The General.md",
545
+ "set": { "table": "main.public.watchlist", "released": 1926 },
546
+ "remove": ["year", "status"]
547
+ }
548
+ ],
549
+ "planned_moves": [
550
+ {
551
+ "from": "datanotes/main/public/film/Battleship Potemkin.md",
552
+ "to": "datanotes/main/public/watchlist/Battleship Potemkin.md"
553
+ },
554
+ {
555
+ "from": "datanotes/main/public/film/Charade.md",
556
+ "to": "datanotes/main/public/watchlist/Charade.md"
557
+ },
558
+ {
559
+ "from": "datanotes/main/public/film/Metropolis.md",
560
+ "to": "datanotes/main/public/watchlist/Metropolis.md"
561
+ },
562
+ {
563
+ "from": "datanotes/main/public/film/The General.md",
564
+ "to": "datanotes/main/public/watchlist/The General.md"
565
+ }
566
+ ],
567
+ "commits": [
568
+ { "table": "main.public.film", "version": 22, "operation": "MOVE ROWS" },
569
+ { "table": "main.public.watchlist", "version": 1, "operation": "MOVE ROWS" }
570
+ ],
571
+ "migration_id": "20261012T071951-move_rows-move-4-rows-from-film-to-watch"
572
+ }
573
+ ```