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,1404 @@
1
+ # Tables
2
+
3
+ > Create a table, find and describe it, set its folders, file names, template and tags, rename, copy and drop it.
4
+
5
+ A table lives in `datanotes/metastore.yaml`, and each of its rows is a Markdown note whose front matter key `table` names the table. Each column is a key of that front matter. You create and change tables only through operations, which validate each change, journal it and can undo it.
6
+
7
+ The examples start from an empty vault and build a small diary of classic films in the public domain. Later examples organize the tables of a second diary, kept in the catalog `diary`.
8
+
9
+ ## Create a table with typed columns
10
+
11
+ `create_table` takes the table’s columns and their types. The table and each column need a `description`: what a row records, and what each column holds. An agent matches your words against the descriptions and the `aliases` to find the table. `filename` names each row’s note after one of its columns.
12
+
13
+ **`create_table`**
14
+
15
+ ```json
16
+ {
17
+ "name": "film",
18
+ "description": "Classic films I watched or want to watch",
19
+ "columns": [
20
+ {
21
+ "name": "title",
22
+ "type": "string",
23
+ "required": true,
24
+ "unique": true,
25
+ "description": "The film's title, as on its poster"
26
+ },
27
+ { "name": "year", "type": "integer", "description": "Year the film first came out" },
28
+ {
29
+ "name": "minutes",
30
+ "type": "integer",
31
+ "min": 1,
32
+ "description": "Running time in minutes"
33
+ },
34
+ {
35
+ "name": "status",
36
+ "type": "enum",
37
+ "values": ["to watch", "watched"],
38
+ "ordered": true,
39
+ "default": "to watch",
40
+ "description": "Where the film is on my list"
41
+ },
42
+ {
43
+ "name": "rating",
44
+ "type": "integer",
45
+ "min": 1,
46
+ "max": 5,
47
+ "description": "My rating, from 1 (poor) to 5 (a masterpiece)"
48
+ },
49
+ { "name": "watched_on", "type": "date", "description": "The day I last watched it" }
50
+ ],
51
+ "filename": "{title}",
52
+ "aliases": ["films", "movies", "watchlist"]
53
+ }
54
+ ```
55
+
56
+ Answer:
57
+
58
+ ```json
59
+ {
60
+ "ok": true,
61
+ "migration_id": "20261012T071946-create_table-create-table-main-public-film",
62
+ "summary": null
63
+ }
64
+ ```
65
+
66
+ `datanotes/metastore.yaml` now reads:
67
+
68
+ ```yaml
69
+ # Metastore (Datanotes): catalogs, schemas, tables, columns, views.
70
+ id: 01a12efb-589d-722c-bc9b-81ec34e480a4
71
+ config:
72
+ history: true
73
+
74
+
75
+ catalogs:
76
+ main:
77
+ schemas:
78
+ public:
79
+ tables:
80
+ film:
81
+ description: Classic films I watched or want to watch
82
+ aliases:
83
+ - films
84
+ - movies
85
+ - watchlist
86
+ options:
87
+ filename: "{title}"
88
+ archive: _archive
89
+ columns:
90
+ - name: id
91
+ type: string
92
+ required: true
93
+ description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
94
+ unique: true
95
+ identity: uuid
96
+ dense: true
97
+ - name: title
98
+ type: string
99
+ required: true
100
+ description: The film's title, as on its poster
101
+ unique: true
102
+ - name: year
103
+ type: integer
104
+ description: Year the film first came out
105
+ - name: minutes
106
+ type: integer
107
+ min: 1
108
+ description: Running time in minutes
109
+ - name: status
110
+ type: enum
111
+ default: to watch
112
+ values:
113
+ - to watch
114
+ - watched
115
+ description: Where the film is on my list
116
+ ordered: true
117
+ - name: rating
118
+ type: integer
119
+ min: 1
120
+ max: 5
121
+ description: My rating, from 1 (poor) to 5 (a masterpiece)
122
+ - name: watched_on
123
+ type: date
124
+ description: The day I last watched it
125
+ ```
126
+
127
+ The engine writes the definition in YAML Ain’t Markup Language (YAML), and you can read the file like any other note. Change it only through the operations (`add_column`, `alter_table`, …): they validate the change against the rows, journal it and make it undoable. A hand edit does none of that.
128
+
129
+ The file uses the operations’ words: a column is `required`, a table or a column has a `description`, and `fields` keeps the fixed front matter keys. The table’s `partition`, `filename`, `archive` and `exclude` go under `options`.
130
+
131
+ The file also holds two things the engine adds on its own. One is the `id` column every table has, with its description. The other is `archive`, the folder for archived rows. `archive` is `_archive` unless `configure_database` sets another folder.
132
+
133
+ ## Describe every table and column
134
+
135
+ Agents and people understand the schema through its descriptions. The engine refuses a table or a column without one. The refusal lists every missing description at once, with the shape that fixes it. Write each one for a reader who never saw your notes.
136
+
137
+ **`create_table`**
138
+
139
+ ```json
140
+ {
141
+ "name": "screening",
142
+ "description": "Films shown in my town",
143
+ "columns": [
144
+ { "name": "film", "type": "string", "required": true },
145
+ { "name": "venue", "type": "string" },
146
+ {
147
+ "name": "starts_at",
148
+ "type": "datetime",
149
+ "description": "When the screening begins"
150
+ }
151
+ ],
152
+ "filename": "{starts_at:YYYY-MM-DD} {film}"
153
+ }
154
+ ```
155
+
156
+ Refused:
157
+
158
+ ```text
159
+ descriptions are required: main.public.screening has none for columns film, venue
160
+ Hint: Say what a row records in "description" and what each column holds in its own "description": {"name": "screening", "description": "<what a row of screening records>", "columns": [{"name": "film", "type": "string", "description": "<what film holds>"}, …]}. Agents and people understand the schema through them.
161
+ Docs: https://datanotes.dev/guides/database-objects/tables.md#describe-every-table-and-column (offline: read_docs("guides/database-objects/tables#describe-every-table-and-column"))
162
+ ```
163
+
164
+ A description can change, but it never goes. `alter_column` and `alter_table` refuse `null` or an empty text:
165
+
166
+ **`alter_column`**
167
+
168
+ ```json
169
+ { "table": "film", "column": "year", "changes": { "description": null } }
170
+ ```
171
+
172
+ Refused:
173
+
174
+ ```text
175
+ the description of column year of main.public.film cannot be removed (null or empty): every column needs one
176
+ Hint: Pass the new text, as in {"table": "main.public.film", "column": "year", "changes": {"description": "<what year holds>"}}, or leave "description" out to keep the one it has.
177
+ Docs: https://datanotes.dev/guides/database-objects/tables.md#describe-every-table-and-column (offline: read_docs("guides/database-objects/tables#describe-every-table-and-column"))
178
+ ```
179
+
180
+ You don’t write every description yourself. The engine writes those of the columns it sets itself: `id`, and `identity` columns of kind `uuid` or `created`.
181
+
182
+ A table created before descriptions were required may lack some, and it stays valid. `describe_table` and `describe_database` name what it lacks in `missing_descriptions`, such as `{"table": false, "columns": ["year"]}`. Fill each one with `alter_table` or `alter_column`. An `infer_table` proposal leaves its descriptions `null` for you to write, as [Infer a table from notes](https://datanotes.dev/guides/read-and-write/import-notes/#infer-a-table-from-notes) shows.
183
+
184
+ ## Find the table that matches a request
185
+
186
+ `list_tables` returns every table with its description and aliases. An agent matches your words against them, so “add this to my watchlist” leads to the `film` table.
187
+
188
+ **`list_tables`** (no arguments)
189
+
190
+ Answer:
191
+
192
+ ```json
193
+ {
194
+ "tables": [
195
+ {
196
+ "name": "film",
197
+ "catalog": "main",
198
+ "schema": "public",
199
+ "qualified": "main.public.film",
200
+ "description": "Classic films I watched or want to watch",
201
+ "aliases": ["films", "movies", "watchlist"]
202
+ }
203
+ ],
204
+ "views": []
205
+ }
206
+ ```
207
+
208
+ ## Describe a table before writing to it
209
+
210
+ `describe_table` lists the columns with their types and constraints, and the table’s row count. Read it before you write: it tells you which values the table accepts.
211
+
212
+ **`describe_table`**
213
+
214
+ ```json
215
+ { "table": "film" }
216
+ ```
217
+
218
+ Answer (83 lines)
219
+
220
+ ```json
221
+ {
222
+ "qualified": "main.public.film",
223
+ "name": "film",
224
+ "catalog": "main",
225
+ "schema": "public",
226
+ "description": "Classic films I watched or want to watch",
227
+ "aliases": ["films", "movies", "watchlist"],
228
+ "layout": {
229
+ "folder": "datanotes/main/public/film",
230
+ "row_folder": "datanotes/main/public/film",
231
+ "archive": "datanotes/main/public/film/_archive"
232
+ },
233
+ "options": { "filename": "{title}", "archive": "_archive" },
234
+ "columns": [
235
+ {
236
+ "name": "id",
237
+ "type": "string",
238
+ "required": true,
239
+ "aliases": [],
240
+ "unique": true,
241
+ "description": "Unique id of the row (UUIDv7): set when the row is created, never changes",
242
+ "identity": "uuid",
243
+ "dense": true
244
+ },
245
+ {
246
+ "name": "title",
247
+ "type": "string",
248
+ "required": true,
249
+ "aliases": [],
250
+ "unique": true,
251
+ "description": "The film's title, as on its poster",
252
+ "dense": false
253
+ },
254
+ {
255
+ "name": "year",
256
+ "type": "integer",
257
+ "required": false,
258
+ "aliases": [],
259
+ "description": "Year the film first came out",
260
+ "dense": false
261
+ },
262
+ {
263
+ "name": "minutes",
264
+ "type": "integer",
265
+ "required": false,
266
+ "min": 1,
267
+ "aliases": [],
268
+ "description": "Running time in minutes",
269
+ "dense": false
270
+ },
271
+ {
272
+ "name": "status",
273
+ "type": "enum",
274
+ "required": false,
275
+ "values": ["to watch", "watched"],
276
+ "aliases": [],
277
+ "ordered": true,
278
+ "description": "Where the film is on my list",
279
+ "default": "to watch",
280
+ "dense": false
281
+ },
282
+ {
283
+ "name": "rating",
284
+ "type": "integer",
285
+ "required": false,
286
+ "min": 1,
287
+ "max": 5,
288
+ "aliases": [],
289
+ "description": "My rating, from 1 (poor) to 5 (a masterpiece)",
290
+ "dense": false
291
+ },
292
+ {
293
+ "name": "watched_on",
294
+ "type": "date",
295
+ "required": false,
296
+ "aliases": [],
297
+ "description": "The day I last watched it",
298
+ "dense": false
299
+ }
300
+ ],
301
+ "untyped_fields": ["table"],
302
+ "row_count": 0
303
+ }
304
+ ```
305
+
306
+ ## Add fixed front matter keys, a note template and tags
307
+
308
+ `create_table` takes three more arguments:
309
+
310
+ * **`fields`**: front matter keys every new row gets with a fixed value and no checks, such as `cssclasses`, which styles the notes in Obsidian
311
+ * **`row_template`**: the text of every new row’s note, with `{column}` placeholders
312
+ * **`tags`**: key/value labels for the table, which `list_tables` and `describe_table` return
313
+
314
+ **`create_table`**
315
+
316
+ ```json
317
+ {
318
+ "name": "watch",
319
+ "description": "Each time I watch a film",
320
+ "columns": [
321
+ {
322
+ "name": "film",
323
+ "type": "string",
324
+ "required": true,
325
+ "description": "The film I watched"
326
+ },
327
+ {
328
+ "name": "where",
329
+ "type": "enum",
330
+ "values": ["cinema", "home", "outdoor screening"],
331
+ "required": true,
332
+ "description": "The kind of place I watched it in"
333
+ },
334
+ {
335
+ "name": "place",
336
+ "type": "string",
337
+ "description": "The cinema, park or room, in my words"
338
+ },
339
+ {
340
+ "name": "occurred_at",
341
+ "type": "datetime",
342
+ "default": "{now}",
343
+ "description": "When the film started"
344
+ },
345
+ {
346
+ "name": "price",
347
+ "type": "number",
348
+ "min": 0,
349
+ "step": 0.01,
350
+ "description": "What the ticket cost, if anything"
351
+ }
352
+ ],
353
+ "filename": "{occurred_at:YYYY-MM-DD} {film}",
354
+ "fields": { "cssclasses": ["watch"] },
355
+ "row_template": "Watched {film} ({where}).\n\n## Thoughts\n",
356
+ "tags": { "area": "films" }
357
+ }
358
+ ```
359
+
360
+ **`insert_row`**
361
+
362
+ ```json
363
+ {
364
+ "table": "watch",
365
+ "values": {
366
+ "film": "Nosferatu",
367
+ "where": "outdoor screening",
368
+ "place": "the park by the river"
369
+ }
370
+ }
371
+ ```
372
+
373
+ Answer:
374
+
375
+ ```json
376
+ {
377
+ "ok": true,
378
+ "op": "insert_row",
379
+ "files_created": 1,
380
+ "planned_creates": ["datanotes/main/public/watch/2026-10-12 Nosferatu.md"],
381
+ "commits": [{ "table": "main.public.watch", "version": 1, "operation": "WRITE" }],
382
+ "migration_id": "20261012T071946-insert_row-insert-into-watch"
383
+ }
384
+ ```
385
+
386
+ `datanotes/main/public/watch/2026-10-12 Nosferatu.md` now reads:
387
+
388
+ ```markdown
389
+ ---
390
+ table: main.public.watch
391
+ cssclasses:
392
+ - watch
393
+ id: 01a12efb-596f-7f8d-a613-98700dbd6bde
394
+ film: Nosferatu
395
+ where: outdoor screening
396
+ place: the park by the river
397
+ occurred_at: 2026-10-12T07:19:46Z
398
+ ---
399
+ Watched Nosferatu (outdoor screening).
400
+
401
+
402
+ ## Thoughts
403
+ ```
404
+
405
+ ## Set a table’s options when you create it
406
+
407
+ `create_table` also takes the options that `alter_table` changes later. Two of them:
408
+
409
+ * **`exclude`**: folders whose notes never become rows, such as an editor’s templates
410
+ * **`history`**: `true` keeps every version of the table, whatever the database’s setting
411
+
412
+ **`create_table`**
413
+
414
+ ```json
415
+ {
416
+ "name": "review",
417
+ "description": "What I wrote about a film after watching it",
418
+ "columns": [
419
+ {
420
+ "name": "film",
421
+ "type": "string",
422
+ "required": true,
423
+ "unique": true,
424
+ "description": "The film the review is about"
425
+ },
426
+ { "name": "verdict", "type": "string", "description": "The review in one sentence" }
427
+ ],
428
+ "filename": "{film}",
429
+ "exclude": ["datanotes/main/public/review/Templates"],
430
+ "history": true
431
+ }
432
+ ```
433
+
434
+ Answer:
435
+
436
+ ```json
437
+ { "ok": true, "summary": null }
438
+ ```
439
+
440
+ [Keep a folder’s notes out of the table](https://datanotes.dev/guides/read-and-write/import-notes/#keep-a-folders-notes-out-of-the-table-with-exclude) and [Table history and time travel](https://datanotes.dev/guides/history/table-history/) show both at work.
441
+
442
+ ## Rename or move a table
443
+
444
+ `alter_table` renames a table with `name`, or moves it to another `catalog` or `schema`. Here `actors` becomes `actor`, singular like the other tables. The rows’ folder and their front matter key `table` follow, and the engine rewrites the links to them. `dry_run` lists the notes that would move.
445
+
446
+ **`alter_table`**
447
+
448
+ ```json
449
+ { "table": "classics.actors", "name": "actor", "dry_run": true }
450
+ ```
451
+
452
+ Answer:
453
+
454
+ ```json
455
+ {
456
+ "ok": true,
457
+ "op": "alter_table",
458
+ "dry_run": true,
459
+ "planned_edits": [
460
+ {
461
+ "path": "datanotes/diary/classics/actors/Max Schreck.md",
462
+ "set": { "table": "diary.classics.actor" },
463
+ "remove": []
464
+ }
465
+ ],
466
+ "planned_moves": [
467
+ {
468
+ "from": "store:transaction_log/diary.classics.actors/00000000000000000000.json",
469
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000000.json"
470
+ },
471
+ {
472
+ "from": "store:transaction_log/diary.classics.actors/00000000000000000001.json",
473
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000001.json"
474
+ },
475
+ {
476
+ "from": "store:transaction_log/diary.classics.actors/00000000000000000002.json",
477
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000002.json"
478
+ },
479
+ {
480
+ "from": "store:transaction_log/diary.classics.actors/00000000000000000003.json",
481
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000003.json"
482
+ },
483
+ {
484
+ "from": "store:transaction_log/diary.classics.actors/00000000000000000004.json",
485
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000004.json"
486
+ },
487
+ {
488
+ "from": "store:transaction_log/diary.classics.actors/00000000000000000005.json",
489
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000005.json"
490
+ },
491
+ {
492
+ "from": "datanotes/diary/classics/actors/Max Schreck.md",
493
+ "to": "datanotes/diary/classics/actor/Max Schreck.md"
494
+ }
495
+ ],
496
+ "planned_texts": ["datanotes/metastore.yaml"]
497
+ }
498
+ ```
499
+
500
+ **`alter_table`**
501
+
502
+ ```json
503
+ { "table": "classics.actors", "name": "actor" }
504
+ ```
505
+
506
+ Answer (48 lines)
507
+
508
+ ```json
509
+ {
510
+ "ok": true,
511
+ "op": "alter_table",
512
+ "notes_changed": 1,
513
+ "files_moved": 7,
514
+ "planned_edits": [
515
+ {
516
+ "path": "datanotes/diary/classics/actors/Max Schreck.md",
517
+ "set": { "table": "diary.classics.actor" },
518
+ "remove": []
519
+ }
520
+ ],
521
+ "planned_moves": [
522
+ {
523
+ "from": "store:transaction_log/diary.classics.actors/00000000000000000000.json",
524
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000000.json"
525
+ },
526
+ {
527
+ "from": "store:transaction_log/diary.classics.actors/00000000000000000001.json",
528
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000001.json"
529
+ },
530
+ {
531
+ "from": "store:transaction_log/diary.classics.actors/00000000000000000002.json",
532
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000002.json"
533
+ },
534
+ {
535
+ "from": "store:transaction_log/diary.classics.actors/00000000000000000003.json",
536
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000003.json"
537
+ },
538
+ {
539
+ "from": "store:transaction_log/diary.classics.actors/00000000000000000004.json",
540
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000004.json"
541
+ },
542
+ {
543
+ "from": "store:transaction_log/diary.classics.actors/00000000000000000005.json",
544
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000005.json"
545
+ },
546
+ {
547
+ "from": "datanotes/diary/classics/actors/Max Schreck.md",
548
+ "to": "datanotes/diary/classics/actor/Max Schreck.md"
549
+ }
550
+ ],
551
+ "planned_texts": ["datanotes/metastore.yaml"],
552
+ "commits": [
553
+ { "table": "diary.classics.actor", "version": 6, "operation": "ALTER TABLE" }
554
+ ],
555
+ "migration_id": "20261012T072004-alter_table-move-table-diary-classics-acto"
556
+ }
557
+ ```
558
+
559
+ Then move it to a schema of its own:
560
+
561
+ **`alter_table`**
562
+
563
+ ```json
564
+ { "table": "classics.actor", "catalog": "diary", "schema": "credits" }
565
+ ```
566
+
567
+ Answer (52 lines)
568
+
569
+ ```json
570
+ {
571
+ "ok": true,
572
+ "op": "alter_table",
573
+ "notes_changed": 1,
574
+ "files_moved": 8,
575
+ "planned_edits": [
576
+ {
577
+ "path": "datanotes/diary/classics/actor/Max Schreck.md",
578
+ "set": { "table": "diary.credits.actor" },
579
+ "remove": []
580
+ }
581
+ ],
582
+ "planned_moves": [
583
+ {
584
+ "from": "store:transaction_log/diary.classics.actor/00000000000000000000.json",
585
+ "to": "store:transaction_log/diary.credits.actor/00000000000000000000.json"
586
+ },
587
+ {
588
+ "from": "store:transaction_log/diary.classics.actor/00000000000000000001.json",
589
+ "to": "store:transaction_log/diary.credits.actor/00000000000000000001.json"
590
+ },
591
+ {
592
+ "from": "store:transaction_log/diary.classics.actor/00000000000000000002.json",
593
+ "to": "store:transaction_log/diary.credits.actor/00000000000000000002.json"
594
+ },
595
+ {
596
+ "from": "store:transaction_log/diary.classics.actor/00000000000000000003.json",
597
+ "to": "store:transaction_log/diary.credits.actor/00000000000000000003.json"
598
+ },
599
+ {
600
+ "from": "store:transaction_log/diary.classics.actor/00000000000000000004.json",
601
+ "to": "store:transaction_log/diary.credits.actor/00000000000000000004.json"
602
+ },
603
+ {
604
+ "from": "store:transaction_log/diary.classics.actor/00000000000000000005.json",
605
+ "to": "store:transaction_log/diary.credits.actor/00000000000000000005.json"
606
+ },
607
+ {
608
+ "from": "store:transaction_log/diary.classics.actor/00000000000000000006.json",
609
+ "to": "store:transaction_log/diary.credits.actor/00000000000000000006.json"
610
+ },
611
+ {
612
+ "from": "datanotes/diary/classics/actor/Max Schreck.md",
613
+ "to": "datanotes/diary/credits/actor/Max Schreck.md"
614
+ }
615
+ ],
616
+ "planned_texts": ["datanotes/metastore.yaml"],
617
+ "commits": [
618
+ { "table": "diary.credits.actor", "version": 7, "operation": "ALTER TABLE" }
619
+ ],
620
+ "migration_id": "20261012T072004-alter_table-move-table-diary-classics-acto-2"
621
+ }
622
+ ```
623
+
624
+ **`list_folder`**
625
+
626
+ ```json
627
+ { "folder": "datanotes/diary/credits/actor" }
628
+ ```
629
+
630
+ Answer:
631
+
632
+ ```json
633
+ {
634
+ "folder": "datanotes/diary/credits/actor",
635
+ "folders": [],
636
+ "files": ["datanotes/diary/credits/actor/Max Schreck.md"]
637
+ }
638
+ ```
639
+
640
+ ## Change sub-folders and file names
641
+
642
+ `alter_table` also changes `partition`, the sub-folders, and `filename`, the name pattern. The engine renames every row to fit, and refuses the change when a row can’t fill the pattern.
643
+
644
+ **`insert_row`**
645
+
646
+ ```json
647
+ {
648
+ "table": "film",
649
+ "values": { "title": "Metropolis", "year": 1927, "minutes": 153, "status": "to watch" }
650
+ }
651
+ ```
652
+
653
+ **`alter_table`**
654
+
655
+ ```json
656
+ { "table": "film", "partition": "{year}" }
657
+ ```
658
+
659
+ Answer:
660
+
661
+ ```json
662
+ {
663
+ "ok": true,
664
+ "op": "alter_table",
665
+ "files_moved": 3,
666
+ "planned_moves": [
667
+ {
668
+ "from": "datanotes/diary/classics/film/His Girl Friday.md",
669
+ "to": "datanotes/diary/classics/film/1940/His Girl Friday.md"
670
+ },
671
+ {
672
+ "from": "datanotes/diary/classics/film/Metropolis.md",
673
+ "to": "datanotes/diary/classics/film/1927/Metropolis.md"
674
+ },
675
+ {
676
+ "from": "datanotes/diary/classics/film/Nosferatu.md",
677
+ "to": "datanotes/diary/classics/film/1922/Nosferatu.md"
678
+ }
679
+ ],
680
+ "planned_texts": ["datanotes/metastore.yaml"],
681
+ "commits": [
682
+ { "table": "diary.classics.film", "version": 13, "operation": "ALTER TABLE" }
683
+ ],
684
+ "migration_id": "20261012T072004-alter_table-alter-table-diary-classics-fil"
685
+ }
686
+ ```
687
+
688
+ Metropolis has no `rating` yet, so the engine refuses a name with `{rating}`:
689
+
690
+ **`alter_table`**
691
+
692
+ ```json
693
+ { "table": "film", "filename": "{title} ({rating} stars)" }
694
+ ```
695
+
696
+ Refused:
697
+
698
+ ```text
699
+ alter_table refused — nothing was written:
700
+ conflict datanotes/diary/classics/film/1927/Metropolis.md: cannot compute the new path: path placeholder {rating} has no value
701
+ Hint: Resolve what the message names, then call alter_table again; dry_run: true checks without writing.
702
+ Docs: https://datanotes.dev/guides/agents/reading-errors.md (offline: read_docs("guides/agents/reading-errors"))
703
+ ```
704
+
705
+ **`alter_table`**
706
+
707
+ ```json
708
+ { "table": "film", "filename": "{title} ({year})" }
709
+ ```
710
+
711
+ Answer:
712
+
713
+ ```json
714
+ {
715
+ "ok": true,
716
+ "op": "alter_table",
717
+ "files_moved": 3,
718
+ "planned_moves": [
719
+ {
720
+ "from": "datanotes/diary/classics/film/1922/Nosferatu.md",
721
+ "to": "datanotes/diary/classics/film/1922/Nosferatu (1922).md"
722
+ },
723
+ {
724
+ "from": "datanotes/diary/classics/film/1927/Metropolis.md",
725
+ "to": "datanotes/diary/classics/film/1927/Metropolis (1927).md"
726
+ },
727
+ {
728
+ "from": "datanotes/diary/classics/film/1940/His Girl Friday.md",
729
+ "to": "datanotes/diary/classics/film/1940/His Girl Friday (1940).md"
730
+ }
731
+ ],
732
+ "planned_texts": ["datanotes/metastore.yaml"],
733
+ "commits": [
734
+ { "table": "diary.classics.film", "version": 14, "operation": "ALTER TABLE" }
735
+ ],
736
+ "migration_id": "20261012T072004-alter_table-alter-table-diary-classics-fil-2"
737
+ }
738
+ ```
739
+
740
+ **`list_folder`**
741
+
742
+ ```json
743
+ { "folder": "datanotes/diary/classics/film/1940" }
744
+ ```
745
+
746
+ Answer:
747
+
748
+ ```json
749
+ {
750
+ "folder": "datanotes/diary/classics/film/1940",
751
+ "folders": [],
752
+ "files": ["datanotes/diary/classics/film/1940/His Girl Friday (1940).md"]
753
+ }
754
+ ```
755
+
756
+ ## Set the archive folder and untyped fields
757
+
758
+ `archive` names the folder, inside the table’s folder, that archived rows go to, and rows already archived move with it. `archive: ""` goes back to the database’s `archive_folder`. `fields` sets untyped fields that every new row gets, as names and values like in `create_table`, and a `null` value removes one.
759
+
760
+ A change that touches only metadata, meaning fields, tags, description, history or the template, rewrites the definition alone and moves no row.
761
+
762
+ **`alter_table`**
763
+
764
+ ```json
765
+ { "table": "film", "archive": "_done" }
766
+ ```
767
+
768
+ Answer:
769
+
770
+ ```json
771
+ {
772
+ "ok": true,
773
+ "op": "alter_table",
774
+ "planned_texts": ["datanotes/metastore.yaml"],
775
+ "commits": [
776
+ { "table": "diary.classics.film", "version": 15, "operation": "ALTER TABLE" }
777
+ ],
778
+ "migration_id": "20261012T072004-alter_table-alter-table-diary-classics-fil-3"
779
+ }
780
+ ```
781
+
782
+ **`alter_table`**
783
+
784
+ ```json
785
+ { "table": "film", "fields": { "cut": "restored", "subtitles": "off" } }
786
+ ```
787
+
788
+ Answer:
789
+
790
+ ```json
791
+ {
792
+ "ok": true,
793
+ "op": "alter_table",
794
+ "planned_texts": ["datanotes/metastore.yaml"],
795
+ "commits": [
796
+ { "table": "diary.classics.film", "version": 16, "operation": "ALTER TABLE" }
797
+ ],
798
+ "migration_id": "20261012T072004-alter_table-alter-table-diary-classics-fil-4"
799
+ }
800
+ ```
801
+
802
+ **`alter_table`**
803
+
804
+ ```json
805
+ { "table": "film", "fields": { "subtitles": null } }
806
+ ```
807
+
808
+ Answer:
809
+
810
+ ```json
811
+ {
812
+ "ok": true,
813
+ "op": "alter_table",
814
+ "planned_texts": ["datanotes/metastore.yaml"],
815
+ "commits": [
816
+ { "table": "diary.classics.film", "version": 17, "operation": "ALTER TABLE" }
817
+ ],
818
+ "migration_id": "20261012T072004-alter_table-alter-table-diary-classics-fil-5"
819
+ }
820
+ ```
821
+
822
+ **`insert_row`**
823
+
824
+ ```json
825
+ {
826
+ "table": "film",
827
+ "values": {
828
+ "title": "Battleship Potemkin",
829
+ "year": 1925,
830
+ "minutes": 75,
831
+ "status": "to watch"
832
+ }
833
+ }
834
+ ```
835
+
836
+ Answer:
837
+
838
+ ```json
839
+ {
840
+ "ok": true,
841
+ "op": "insert_row",
842
+ "files_created": 1,
843
+ "planned_creates": ["datanotes/diary/classics/film/1925/Battleship Potemkin (1925).md"],
844
+ "commits": [{ "table": "diary.classics.film", "version": 18, "operation": "WRITE" }],
845
+ "migration_id": "20261012T072004-insert_row-insert-into-film-3"
846
+ }
847
+ ```
848
+
849
+ `datanotes/diary/classics/film/1925/Battleship Potemkin (1925).md` now reads:
850
+
851
+ ```markdown
852
+ ---
853
+ table: diary.classics.film
854
+ cut: restored
855
+ id: 01a12efb-9da0-70b8-9f73-bb00b210e05f
856
+ title: Battleship Potemkin
857
+ year: 1925
858
+ minutes: 75
859
+ status: to watch
860
+ ---
861
+ ```
862
+
863
+ ## Give new notes a template
864
+
865
+ `row_template`, in `create_table` and `alter_table`, is the text of every new row’s note, with `{column}` placeholders. Notes you create by hand in the table’s folder get it too.
866
+
867
+ **`alter_table`**
868
+
869
+ ```json
870
+ {
871
+ "table": "film",
872
+ "row_template": "# {title} ({year})\n\n## What I liked\n\n## Who I watched it with\n"
873
+ }
874
+ ```
875
+
876
+ Answer:
877
+
878
+ ```json
879
+ {
880
+ "ok": true,
881
+ "op": "alter_table",
882
+ "planned_texts": ["datanotes/metastore.yaml"],
883
+ "commits": [
884
+ { "table": "diary.classics.film", "version": 19, "operation": "ALTER TABLE" }
885
+ ],
886
+ "migration_id": "20261012T072004-alter_table-alter-table-diary-classics-fil-6"
887
+ }
888
+ ```
889
+
890
+ **`insert_row`**
891
+
892
+ ```json
893
+ {
894
+ "table": "film",
895
+ "values": { "title": "Sherlock Jr.", "year": 1924, "minutes": 45, "status": "to watch" }
896
+ }
897
+ ```
898
+
899
+ Answer:
900
+
901
+ ```json
902
+ {
903
+ "ok": true,
904
+ "op": "insert_row",
905
+ "files_created": 1,
906
+ "planned_creates": ["datanotes/diary/classics/film/1924/Sherlock Jr. (1924).md"],
907
+ "commits": [{ "table": "diary.classics.film", "version": 20, "operation": "WRITE" }],
908
+ "migration_id": "20261012T072004-insert_row-insert-into-film-4"
909
+ }
910
+ ```
911
+
912
+ `datanotes/diary/classics/film/1924/Sherlock Jr. (1924).md` now reads:
913
+
914
+ ```markdown
915
+ ---
916
+ table: diary.classics.film
917
+ cut: restored
918
+ id: 01a12efb-9dba-741f-a279-de88358083ce
919
+ title: Sherlock Jr.
920
+ year: 1924
921
+ minutes: 45
922
+ status: to watch
923
+ ---
924
+ # Sherlock Jr. (1924)
925
+
926
+
927
+ ## What I liked
928
+
929
+
930
+ ## Who I watched it with
931
+ ```
932
+
933
+ `row_template: ""` removes the template, and notes already written keep their text:
934
+
935
+ **`alter_table`**
936
+
937
+ ```json
938
+ { "table": "film", "row_template": "" }
939
+ ```
940
+
941
+ Answer:
942
+
943
+ ```json
944
+ {
945
+ "ok": true,
946
+ "op": "alter_table",
947
+ "planned_texts": ["datanotes/metastore.yaml"],
948
+ "commits": [
949
+ { "table": "diary.classics.film", "version": 21, "operation": "ALTER TABLE" }
950
+ ],
951
+ "migration_id": "20261012T072004-alter_table-alter-table-diary-classics-fil-7"
952
+ }
953
+ ```
954
+
955
+ ## Set defaults for new tables
956
+
957
+ The other settings shape the tables you create next:
958
+
959
+ * **`default_schema`**, with `default_catalog`: places a table given only a name
960
+ * **`default_partition`**: the sub-folders of a table with a `time_column`, as a date format
961
+ * **`default_filename`**: the file names of a table with a `time_column`, as a date format
962
+ * **`archive_folder`**: the name of a new table’s archive folder
963
+
964
+ Existing tables keep their settings.
965
+
966
+ **`configure_database`**
967
+
968
+ ```json
969
+ {
970
+ "default_schema": "log",
971
+ "default_partition": "YYYY/MM",
972
+ "default_filename": "YYYY-MM-DD HHmm",
973
+ "archive_folder": "_old"
974
+ }
975
+ ```
976
+
977
+ Answer:
978
+
979
+ ```json
980
+ {
981
+ "default_catalog": "diary",
982
+ "default_schema": "log",
983
+ "default_partition": "YYYY/MM",
984
+ "default_filename": "YYYY-MM-DD HHmm",
985
+ "archive_folder": "_old",
986
+ "timezone": "Europe/Rome",
987
+ "history": true,
988
+ "autolink": true
989
+ }
990
+ ```
991
+
992
+ **`create_table`**
993
+
994
+ ```json
995
+ {
996
+ "name": "watch",
997
+ "description": "Each time I watch a film",
998
+ "columns": [
999
+ {
1000
+ "name": "film",
1001
+ "type": "link",
1002
+ "ref": "diary.classics.film",
1003
+ "required": true,
1004
+ "description": "The film I watched"
1005
+ },
1006
+ {
1007
+ "name": "occurred_at",
1008
+ "type": "datetime",
1009
+ "required": true,
1010
+ "description": "When the film started"
1011
+ },
1012
+ {
1013
+ "name": "where",
1014
+ "type": "enum",
1015
+ "values": ["cinema", "home", "outdoor screening"],
1016
+ "description": "The kind of place I watched it in"
1017
+ },
1018
+ {
1019
+ "name": "place",
1020
+ "type": "string",
1021
+ "description": "The cinema, park or room, in my words"
1022
+ }
1023
+ ],
1024
+ "time_column": "occurred_at"
1025
+ }
1026
+ ```
1027
+
1028
+ Answer:
1029
+
1030
+ ```json
1031
+ {
1032
+ "ok": true,
1033
+ "migration_id": "20261012T072004-create_table-create-table-diary-log-watch"
1034
+ }
1035
+ ```
1036
+
1037
+ The new table is `diary.log.watch`, and its rows go in sub-folders by year and month. A date-time without an offset gets Rome’s offset:
1038
+
1039
+ **`insert_row`**
1040
+
1041
+ ```json
1042
+ {
1043
+ "table": "watch",
1044
+ "values": {
1045
+ "film": "[[Nosferatu (1922)]]",
1046
+ "occurred_at": "2026-10-06T21:30",
1047
+ "where": "outdoor screening",
1048
+ "place": "the park by the river"
1049
+ }
1050
+ }
1051
+ ```
1052
+
1053
+ Answer:
1054
+
1055
+ ```json
1056
+ {
1057
+ "ok": true,
1058
+ "op": "insert_row",
1059
+ "files_created": 1,
1060
+ "planned_creates": ["datanotes/diary/log/watch/2026/10/2026-10-06 2130.md"],
1061
+ "commits": [{ "table": "diary.log.watch", "version": 1, "operation": "WRITE" }],
1062
+ "migration_id": "20261012T072004-insert_row-insert-into-watch"
1063
+ }
1064
+ ```
1065
+
1066
+ `datanotes/diary/log/watch/2026/10/2026-10-06 2130.md` now reads:
1067
+
1068
+ ```markdown
1069
+ ---
1070
+ table: diary.log.watch
1071
+ id: 01a12efb-9e05-7ed2-95c8-870d481fea29
1072
+ film: "[[Nosferatu (1922)]]"
1073
+ occurred_at: 2026-10-06T21:30:00+02:00
1074
+ where: outdoor screening
1075
+ place: the park by the river
1076
+ ---
1077
+ ```
1078
+
1079
+ ## Copy a table to try changes
1080
+
1081
+ `clone_table` copies a table’s definition and rows under a new name, as independent copies. It can also copy the table as it was at an earlier version, as [Copy a table as it was](https://datanotes.dev/guides/history/table-history/#copy-a-table-as-it-was) shows. With `copy_rows: false`, it copies only the definition. Use a copy to try something without touching the original.
1082
+
1083
+ **`clone_table`**
1084
+
1085
+ ```json
1086
+ { "source": "diary.classics.film", "target": "sandbox.classics.film_copy" }
1087
+ ```
1088
+
1089
+ Answer:
1090
+
1091
+ ```json
1092
+ {
1093
+ "ok": true,
1094
+ "op": "clone_table",
1095
+ "files_created": 5,
1096
+ "planned_creates": [
1097
+ "datanotes/sandbox/classics/film_copy/1922/Nosferatu (1922).md",
1098
+ "datanotes/sandbox/classics/film_copy/1924/Sherlock Jr. (1924).md",
1099
+ "datanotes/sandbox/classics/film_copy/1925/Battleship Potemkin (1925).md",
1100
+ "datanotes/sandbox/classics/film_copy/1927/Metropolis (1927).md",
1101
+ "datanotes/sandbox/classics/film_copy/1940/His Girl Friday (1940).md"
1102
+ ],
1103
+ "planned_texts": ["datanotes/metastore.yaml"],
1104
+ "commits": [
1105
+ { "table": "sandbox.classics.film_copy", "version": 0, "operation": "CLONE" }
1106
+ ],
1107
+ "migration_id": "20261012T072004-clone_table-clone-diary-classics-film-to-s"
1108
+ }
1109
+ ```
1110
+
1111
+ **`select_rows`**
1112
+
1113
+ ```json
1114
+ { "table": "sandbox.classics.film_copy", "columns": ["title", "year"] }
1115
+ ```
1116
+
1117
+ Answer:
1118
+
1119
+ ```json
1120
+ {
1121
+ "table": "sandbox.classics.film_copy",
1122
+ "total": 5,
1123
+ "rows": [
1124
+ {
1125
+ "path": "datanotes/sandbox/classics/film_copy/1922/Nosferatu (1922).md",
1126
+ "title": "Nosferatu",
1127
+ "year": 1922
1128
+ },
1129
+ {
1130
+ "path": "datanotes/sandbox/classics/film_copy/1924/Sherlock Jr. (1924).md",
1131
+ "title": "Sherlock Jr.",
1132
+ "year": 1924
1133
+ },
1134
+ {
1135
+ "path": "datanotes/sandbox/classics/film_copy/1925/Battleship Potemkin (1925).md",
1136
+ "title": "Battleship Potemkin",
1137
+ "year": 1925
1138
+ },
1139
+ {
1140
+ "path": "datanotes/sandbox/classics/film_copy/1927/Metropolis (1927).md",
1141
+ "title": "Metropolis",
1142
+ "year": 1927
1143
+ },
1144
+ {
1145
+ "path": "datanotes/sandbox/classics/film_copy/1940/His Girl Friday (1940).md",
1146
+ "title": "His Girl Friday",
1147
+ "year": 1940
1148
+ }
1149
+ ]
1150
+ }
1151
+ ```
1152
+
1153
+ **`clone_table`**
1154
+
1155
+ ```json
1156
+ {
1157
+ "source": "diary.classics.film",
1158
+ "target": "sandbox.classics.film_empty",
1159
+ "copy_rows": false
1160
+ }
1161
+ ```
1162
+
1163
+ Answer:
1164
+
1165
+ ```json
1166
+ {
1167
+ "ok": true,
1168
+ "op": "clone_table",
1169
+ "planned_texts": ["datanotes/metastore.yaml"],
1170
+ "commits": [
1171
+ { "table": "sandbox.classics.film_empty", "version": 0, "operation": "CLONE" }
1172
+ ],
1173
+ "migration_id": "20261012T072004-clone_table-clone-diary-classics-film-to-s-2"
1174
+ }
1175
+ ```
1176
+
1177
+ **`describe_table`**
1178
+
1179
+ ```json
1180
+ { "table": "film_empty" }
1181
+ ```
1182
+
1183
+ Answer:
1184
+
1185
+ ```json
1186
+ {
1187
+ "name": "film_empty",
1188
+ "catalog": "sandbox",
1189
+ "schema": "classics",
1190
+ "rows": 0,
1191
+ "columns": ["id", "added_on", "title", "year", "minutes", "status", "rating"]
1192
+ }
1193
+ ```
1194
+
1195
+ ## Drop a table and archive its rows
1196
+
1197
+ `drop_table` refuses a table that still has rows. With `mode: "archive"`, it removes the definition and moves the rows to the table’s archive folder. The engine deletes nothing, and you can undo the operation.
1198
+
1199
+ You choose that folder with `archive` when you create the table. It’s a vault path, as here, or a folder name inside the table’s folder (`_archive` by default).
1200
+
1201
+ **`create_table`**
1202
+
1203
+ ```json
1204
+ {
1205
+ "name": "scratch",
1206
+ "description": "Throwaway notes for trying things out",
1207
+ "columns": [{ "name": "note", "type": "string", "description": "The note's text" }],
1208
+ "filename": "{note}",
1209
+ "archive": "Archive/scratch"
1210
+ }
1211
+ ```
1212
+
1213
+ **`insert_row`**
1214
+
1215
+ ```json
1216
+ { "table": "scratch", "values": { "note": "temporary" } }
1217
+ ```
1218
+
1219
+ **`drop_table`**
1220
+
1221
+ ```json
1222
+ { "table": "scratch" }
1223
+ ```
1224
+
1225
+ Refused:
1226
+
1227
+ ```text
1228
+ drop_table refused — nothing was written:
1229
+ conflict datanotes/metastore.yaml: table has 1 rows; pass mode: "archive" to archive them with the table
1230
+ Hint: Pass mode: "archive" to archive the rows with the table, or delete them first (delete_rows).
1231
+ Docs: https://datanotes.dev/guides/database-objects/tables.md#drop-a-table-and-archive-its-rows (offline: read_docs("guides/database-objects/tables#drop-a-table-and-archive-its-rows"))
1232
+ ```
1233
+
1234
+ **`drop_table`**
1235
+
1236
+ ```json
1237
+ { "table": "scratch", "mode": "archive" }
1238
+ ```
1239
+
1240
+ Answer:
1241
+
1242
+ ```json
1243
+ {
1244
+ "ok": true,
1245
+ "op": "drop_table",
1246
+ "files_moved": 1,
1247
+ "planned_moves": [
1248
+ {
1249
+ "from": "datanotes/main/public/scratch/temporary.md",
1250
+ "to": "Archive/scratch/temporary.md"
1251
+ }
1252
+ ],
1253
+ "planned_texts": ["datanotes/metastore.yaml"],
1254
+ "commits": [{ "table": "main.public.scratch", "version": 2, "operation": "DROP TABLE" }],
1255
+ "migration_id": "20261012T071946-drop_table-drop-table-scratch"
1256
+ }
1257
+ ```
1258
+
1259
+ **`list_folder`**
1260
+
1261
+ ```json
1262
+ { "folder": "Archive/scratch" }
1263
+ ```
1264
+
1265
+ Answer:
1266
+
1267
+ ```json
1268
+ { "folder": "Archive/scratch", "folders": [], "files": ["Archive/scratch/temporary.md"] }
1269
+ ```
1270
+
1271
+ ## See the tables in `metastore.yaml`
1272
+
1273
+ The engine wrote this file in its own words. It holds `film`, `watch` and `review`, and `scratch` is gone because you dropped it.
1274
+
1275
+ `datanotes/metastore.yaml` now reads:
1276
+
1277
+ ```yaml
1278
+ # Metastore (Datanotes): catalogs, schemas, tables, columns, views.
1279
+ id: 01a12efb-589d-722c-bc9b-81ec34e480a4
1280
+ config:
1281
+ history: true
1282
+
1283
+
1284
+ catalogs:
1285
+ main:
1286
+ schemas:
1287
+ public:
1288
+ tables:
1289
+ film:
1290
+ description: Classic films I watched or want to watch
1291
+ aliases:
1292
+ - films
1293
+ - movies
1294
+ - watchlist
1295
+ options:
1296
+ filename: "{title}"
1297
+ archive: _archive
1298
+ columns:
1299
+ - name: id
1300
+ type: string
1301
+ required: true
1302
+ description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
1303
+ unique: true
1304
+ identity: uuid
1305
+ dense: true
1306
+ - name: title
1307
+ type: string
1308
+ required: true
1309
+ description: The film's title, as on its poster
1310
+ unique: true
1311
+ - name: year
1312
+ type: integer
1313
+ description: Year the film first came out
1314
+ - name: minutes
1315
+ type: integer
1316
+ min: 1
1317
+ description: Running time in minutes
1318
+ - name: status
1319
+ type: enum
1320
+ default: to watch
1321
+ values:
1322
+ - to watch
1323
+ - watched
1324
+ description: Where the film is on my list
1325
+ ordered: true
1326
+ - name: rating
1327
+ type: integer
1328
+ min: 1
1329
+ max: 5
1330
+ description: My rating, from 1 (poor) to 5 (a masterpiece)
1331
+ - name: watched_on
1332
+ type: date
1333
+ description: The day I last watched it
1334
+ watch:
1335
+ description: Each time I watch a film
1336
+ tags:
1337
+ area: films
1338
+ options:
1339
+ filename: "{occurred_at:YYYY-MM-DD} {film}"
1340
+ archive: _archive
1341
+ fields:
1342
+ cssclasses:
1343
+ - watch
1344
+ columns:
1345
+ - name: id
1346
+ type: string
1347
+ required: true
1348
+ description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
1349
+ unique: true
1350
+ identity: uuid
1351
+ dense: true
1352
+ - name: film
1353
+ type: string
1354
+ required: true
1355
+ description: The film I watched
1356
+ - name: where
1357
+ type: enum
1358
+ required: true
1359
+ values:
1360
+ - cinema
1361
+ - home
1362
+ - outdoor screening
1363
+ description: The kind of place I watched it in
1364
+ - name: place
1365
+ type: string
1366
+ description: The cinema, park or room, in my words
1367
+ - name: occurred_at
1368
+ type: datetime
1369
+ default: "{now}"
1370
+ description: When the film started
1371
+ - name: price
1372
+ type: number
1373
+ min: 0
1374
+ step: 0.01
1375
+ description: What the ticket cost, if anything
1376
+ row_template: |
1377
+ Watched {film} ({where}).
1378
+
1379
+
1380
+ ## Thoughts
1381
+ review:
1382
+ description: What I wrote about a film after watching it
1383
+ options:
1384
+ filename: "{film}"
1385
+ archive: _archive
1386
+ exclude: datanotes/main/public/review/Templates
1387
+ history: true
1388
+ columns:
1389
+ - name: id
1390
+ type: string
1391
+ required: true
1392
+ description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
1393
+ unique: true
1394
+ identity: uuid
1395
+ dense: true
1396
+ - name: film
1397
+ type: string
1398
+ required: true
1399
+ description: The film the review is about
1400
+ unique: true
1401
+ - name: verdict
1402
+ type: string
1403
+ description: The review in one sentence
1404
+ ```