datanotes 2.0.1 → 3.1.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 (185) hide show
  1. package/NOTICE +9 -0
  2. package/README.md +203 -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 +122 -0
  10. package/dist/api/client.d.ts +56 -34
  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 +7470 -2771
  16. package/dist/api/index.d.cts +14 -0
  17. package/dist/api/index.d.ts +11 -7
  18. package/dist/api/index.js +7470 -2771
  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 +17387 -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 +17337 -0
  27. package/dist/api/otlp.d.cts +113 -0
  28. package/dist/api/otlp.d.ts +63 -15
  29. package/dist/api/redact.d.cts +42 -0
  30. package/dist/api/redact.d.ts +42 -0
  31. package/dist/api/requests.d.cts +26 -0
  32. package/dist/api/requests.d.ts +14 -16
  33. package/dist/api/results.d.cts +16 -0
  34. package/dist/api/results.d.ts +2 -1
  35. package/dist/api/tokens.d.cts +12 -0
  36. package/dist/api/tokens.d.ts +5 -0
  37. package/dist/api/tracing.d.cts +107 -0
  38. package/dist/api/tracing.d.ts +107 -0
  39. package/dist/datanotes.cjs +69597 -28329
  40. package/dist/docs/administration/configuration.md +59 -0
  41. package/dist/docs/administration/docker.md +80 -0
  42. package/dist/docs/administration/git-sync.md +71 -0
  43. package/dist/docs/administration/limits.md +153 -0
  44. package/dist/docs/administration/run-the-service.md +237 -0
  45. package/dist/docs/administration/status.md +61 -0
  46. package/dist/docs/administration/use-with-obsidian.md +75 -0
  47. package/dist/docs/api/operations/add_column.md +49 -0
  48. package/dist/docs/api/operations/add_constraint.md +55 -0
  49. package/dist/docs/api/operations/adopt_rows.md +50 -0
  50. package/dist/docs/api/operations/align_relations.md +41 -0
  51. package/dist/docs/api/operations/alter_column.md +46 -0
  52. package/dist/docs/api/operations/alter_table.md +67 -0
  53. package/dist/docs/api/operations/batch.md +48 -0
  54. package/dist/docs/api/operations/changes_stream.md +28 -0
  55. package/dist/docs/api/operations/check_table.md +29 -0
  56. package/dist/docs/api/operations/clone_table.md +48 -0
  57. package/dist/docs/api/operations/comment_on.md +42 -0
  58. package/dist/docs/api/operations/configure_database.md +52 -0
  59. package/dist/docs/api/operations/create_table.md +78 -0
  60. package/dist/docs/api/operations/create_view.md +54 -0
  61. package/dist/docs/api/operations/create_webhook.md +37 -0
  62. package/dist/docs/api/operations/define_base.md +52 -0
  63. package/dist/docs/api/operations/delete_rows.md +43 -0
  64. package/dist/docs/api/operations/delete_webhook.md +31 -0
  65. package/dist/docs/api/operations/describe_database.md +30 -0
  66. package/dist/docs/api/operations/describe_history.md +40 -0
  67. package/dist/docs/api/operations/describe_semantic_index.md +24 -0
  68. package/dist/docs/api/operations/describe_table.md +38 -0
  69. package/dist/docs/api/operations/docs.md +13 -0
  70. package/dist/docs/api/operations/drop_column.md +41 -0
  71. package/dist/docs/api/operations/drop_constraint.md +39 -0
  72. package/dist/docs/api/operations/drop_table.md +43 -0
  73. package/dist/docs/api/operations/drop_view.md +38 -0
  74. package/dist/docs/api/operations/engine_status.md +23 -0
  75. package/dist/docs/api/operations/graph_neighbors.md +34 -0
  76. package/dist/docs/api/operations/graph_path.md +32 -0
  77. package/dist/docs/api/operations/graph_traverse.md +43 -0
  78. package/dist/docs/api/operations/identify_session.md +24 -0
  79. package/dist/docs/api/operations/infer_table.md +43 -0
  80. package/dist/docs/api/operations/insert_row.md +48 -0
  81. package/dist/docs/api/operations/list_changes.md +35 -0
  82. package/dist/docs/api/operations/list_folder.md +28 -0
  83. package/dist/docs/api/operations/list_link_targets.md +44 -0
  84. package/dist/docs/api/operations/list_migrations.md +35 -0
  85. package/dist/docs/api/operations/list_tables.md +32 -0
  86. package/dist/docs/api/operations/list_webhooks.md +23 -0
  87. package/dist/docs/api/operations/manual_index.md +14 -0
  88. package/dist/docs/api/operations/manual_page.md +22 -0
  89. package/dist/docs/api/operations/merge_rows.md +44 -0
  90. package/dist/docs/api/operations/move_rows.md +52 -0
  91. package/dist/docs/api/operations/openapi.md +13 -0
  92. package/dist/docs/api/operations/openapi_post.md +20 -0
  93. package/dist/docs/api/operations/read_note.md +29 -0
  94. package/dist/docs/api/operations/recompute_columns.md +44 -0
  95. package/dist/docs/api/operations/record_tool_call.md +37 -0
  96. package/dist/docs/api/operations/rename_column.md +44 -0
  97. package/dist/docs/api/operations/rename_value.md +45 -0
  98. package/dist/docs/api/operations/restore_database.md +51 -0
  99. package/dist/docs/api/operations/restore_table.md +46 -0
  100. package/dist/docs/api/operations/search_notes.md +28 -0
  101. package/dist/docs/api/operations/select_rows.md +61 -0
  102. package/dist/docs/api/operations/semantic_search.md +34 -0
  103. package/dist/docs/api/operations/semantic_search_docs.md +26 -0
  104. package/dist/docs/api/operations/set_table_bases.md +44 -0
  105. package/dist/docs/api/operations/status.md +13 -0
  106. package/dist/docs/api/operations/stop_engine.md +23 -0
  107. package/dist/docs/api/operations/sync_semantic_index.md +33 -0
  108. package/dist/docs/api/operations/table_changes.md +31 -0
  109. package/dist/docs/api/operations/tags/auxiliary.md +14 -0
  110. package/dist/docs/api/operations/tags/change-feed.md +8 -0
  111. package/dist/docs/api/operations/tags/history.md +17 -0
  112. package/dist/docs/api/operations/tags/queries.md +15 -0
  113. package/dist/docs/api/operations/tags/rows-dml.md +16 -0
  114. package/dist/docs/api/operations/tags/schema-ddl.md +22 -0
  115. package/dist/docs/api/operations/tags/service.md +15 -0
  116. package/dist/docs/api/operations/tags/tokens.md +13 -0
  117. package/dist/docs/api/operations/tokens_create.md +34 -0
  118. package/dist/docs/api/operations/tokens_delete.md +29 -0
  119. package/dist/docs/api/operations/tokens_list.md +21 -0
  120. package/dist/docs/api/operations/tokens_me.md +20 -0
  121. package/dist/docs/api/operations/tokens_rotate.md +29 -0
  122. package/dist/docs/api/operations/tokens_update.md +34 -0
  123. package/dist/docs/api/operations/undo_migration.md +46 -0
  124. package/dist/docs/api/operations/update_row.md +60 -0
  125. package/dist/docs/api/operations/update_rows.md +46 -0
  126. package/dist/docs/api/operations/vacuum_table.md +37 -0
  127. package/dist/docs/api.md +135 -0
  128. package/dist/docs/develop/build-an-app.md +320 -0
  129. package/dist/docs/develop/build-on-datanotes.md +138 -0
  130. package/dist/docs/develop/change-feed-and-webhooks.md +70 -0
  131. package/dist/docs/develop/client.md +116 -0
  132. package/dist/docs/develop/concurrency.md +188 -0
  133. package/dist/docs/develop/http-api.md +89 -0
  134. package/dist/docs/develop/telemetry.md +285 -0
  135. package/dist/docs/guides/agents/docs-offline.md +99 -0
  136. package/dist/docs/guides/agents/how-agents-use-datanotes.md +124 -0
  137. package/dist/docs/guides/agents/prompts.md +495 -0
  138. package/dist/docs/guides/agents/reading-errors.md +112 -0
  139. package/dist/docs/guides/data-quality/check-constraints.md +379 -0
  140. package/dist/docs/guides/data-quality/check-table.md +63 -0
  141. package/dist/docs/guides/data-quality/hand-edits.md +507 -0
  142. package/dist/docs/guides/data-quality/sync-between-devices.md +51 -0
  143. package/dist/docs/guides/data-quality/validation.md +90 -0
  144. package/dist/docs/guides/database-objects/bases.md +480 -0
  145. package/dist/docs/guides/database-objects/catalogs-and-schemas.md +897 -0
  146. package/dist/docs/guides/database-objects/columns.md +1332 -0
  147. package/dist/docs/guides/database-objects/relations.md +1454 -0
  148. package/dist/docs/guides/database-objects/tables.md +1340 -0
  149. package/dist/docs/guides/database-objects/views.md +1003 -0
  150. package/dist/docs/guides/history/back-up.md +203 -0
  151. package/dist/docs/guides/history/change-data-feed.md +247 -0
  152. package/dist/docs/guides/history/journal-and-undo.md +262 -0
  153. package/dist/docs/guides/history/restore.md +341 -0
  154. package/dist/docs/guides/history/table-history.md +657 -0
  155. package/dist/docs/guides/history/vacuum.md +26 -0
  156. package/dist/docs/guides/read-and-write/automatic-links.md +768 -0
  157. package/dist/docs/guides/read-and-write/batch.md +139 -0
  158. package/dist/docs/guides/read-and-write/import-notes.md +896 -0
  159. package/dist/docs/guides/read-and-write/joins-and-aggregates.md +251 -0
  160. package/dist/docs/guides/read-and-write/read-and-filter.md +536 -0
  161. package/dist/docs/guides/read-and-write/write-rows.md +573 -0
  162. package/dist/docs/guides/search/link-graph.md +241 -0
  163. package/dist/docs/guides/search/semantic.md +133 -0
  164. package/dist/docs/guides/search/text.md +142 -0
  165. package/dist/docs/guides/security/security-model.md +110 -0
  166. package/dist/docs/guides/security/tokens.md +60 -0
  167. package/dist/docs/guides/security/who-did-what.md +44 -0
  168. package/dist/docs/index.md +113 -0
  169. package/dist/docs/llms.txt +33 -0
  170. package/dist/docs/reference/cli.md +143 -0
  171. package/dist/docs/reference/compatibility.md +83 -0
  172. package/dist/docs/reference/errors.md +72 -0
  173. package/dist/docs/reference/glossary.md +161 -0
  174. package/dist/docs/reference/operations/auxiliary.md +116 -0
  175. package/dist/docs/reference/operations/history.md +168 -0
  176. package/dist/docs/reference/operations/queries.md +169 -0
  177. package/dist/docs/reference/operations/rows.md +185 -0
  178. package/dist/docs/reference/operations/schema.md +316 -0
  179. package/dist/docs/reference/operations.md +96 -0
  180. package/dist/docs/reference/settings.md +43 -0
  181. package/dist/docs/release-notes.md +188 -0
  182. package/dist/docs/start/connect-agents.md +204 -0
  183. package/dist/docs/start/how-it-works.md +349 -0
  184. package/dist/docs/start/quick-start.md +128 -0
  185. package/package.json +60 -15
@@ -0,0 +1,1340 @@
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": "20261014T191515-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: 01a13bd7-1943-7bc6-ab38-ab176313afeb
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-14 Nosferatu.md"],
381
+ "commits": [{ "table": "main.public.watch", "version": 1, "operation": "WRITE" }],
382
+ "migration_id": "20261014T191515-insert_row-insert-into-watch"
383
+ }
384
+ ```
385
+
386
+ `datanotes/main/public/watch/2026-10-14 Nosferatu.md` now reads:
387
+
388
+ ```markdown
389
+ ---
390
+ table: main.public.watch
391
+ cssclasses:
392
+ - watch
393
+ id: 01a13bd7-1c3b-7687-845d-b10ea8ff95a2
394
+ film: Nosferatu
395
+ where: outdoor screening
396
+ place: the park by the river
397
+ occurred_at: 2026-10-14T19:15:15Z
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.segment.jsonl",
469
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000000.segment.jsonl"
470
+ },
471
+ {
472
+ "from": "datanotes/diary/classics/actors/Max Schreck.md",
473
+ "to": "datanotes/diary/classics/actor/Max Schreck.md"
474
+ }
475
+ ],
476
+ "planned_texts": ["datanotes/metastore.yaml"]
477
+ }
478
+ ```
479
+
480
+ **`alter_table`**
481
+
482
+ ```json
483
+ { "table": "classics.actors", "name": "actor" }
484
+ ```
485
+
486
+ Answer:
487
+
488
+ ```json
489
+ {
490
+ "ok": true,
491
+ "op": "alter_table",
492
+ "notes_changed": 1,
493
+ "files_moved": 2,
494
+ "planned_edits": [
495
+ {
496
+ "path": "datanotes/diary/classics/actors/Max Schreck.md",
497
+ "set": { "table": "diary.classics.actor" },
498
+ "remove": []
499
+ }
500
+ ],
501
+ "planned_moves": [
502
+ {
503
+ "from": "store:transaction_log/diary.classics.actors/00000000000000000000.segment.jsonl",
504
+ "to": "store:transaction_log/diary.classics.actor/00000000000000000000.segment.jsonl"
505
+ },
506
+ {
507
+ "from": "datanotes/diary/classics/actors/Max Schreck.md",
508
+ "to": "datanotes/diary/classics/actor/Max Schreck.md"
509
+ }
510
+ ],
511
+ "planned_texts": ["datanotes/metastore.yaml"],
512
+ "commits": [
513
+ { "table": "diary.classics.actor", "version": 6, "operation": "ALTER TABLE" }
514
+ ],
515
+ "migration_id": "20261014T191534-alter_table-move-table-diary-classics-acto"
516
+ }
517
+ ```
518
+
519
+ Then move it to a schema of its own:
520
+
521
+ **`alter_table`**
522
+
523
+ ```json
524
+ { "table": "classics.actor", "catalog": "diary", "schema": "credits" }
525
+ ```
526
+
527
+ Answer:
528
+
529
+ ```json
530
+ {
531
+ "ok": true,
532
+ "op": "alter_table",
533
+ "notes_changed": 1,
534
+ "files_moved": 2,
535
+ "planned_edits": [
536
+ {
537
+ "path": "datanotes/diary/classics/actor/Max Schreck.md",
538
+ "set": { "table": "diary.credits.actor" },
539
+ "remove": []
540
+ }
541
+ ],
542
+ "planned_moves": [
543
+ {
544
+ "from": "store:transaction_log/diary.classics.actor/00000000000000000000.segment.jsonl",
545
+ "to": "store:transaction_log/diary.credits.actor/00000000000000000000.segment.jsonl"
546
+ },
547
+ {
548
+ "from": "datanotes/diary/classics/actor/Max Schreck.md",
549
+ "to": "datanotes/diary/credits/actor/Max Schreck.md"
550
+ }
551
+ ],
552
+ "planned_texts": ["datanotes/metastore.yaml"],
553
+ "commits": [
554
+ { "table": "diary.credits.actor", "version": 7, "operation": "ALTER TABLE" }
555
+ ],
556
+ "migration_id": "20261014T191534-alter_table-move-table-diary-classics-acto-2"
557
+ }
558
+ ```
559
+
560
+ **`list_folder`**
561
+
562
+ ```json
563
+ { "folder": "datanotes/diary/credits/actor" }
564
+ ```
565
+
566
+ Answer:
567
+
568
+ ```json
569
+ {
570
+ "folder": "datanotes/diary/credits/actor",
571
+ "folders": [],
572
+ "files": ["datanotes/diary/credits/actor/Max Schreck.md"]
573
+ }
574
+ ```
575
+
576
+ ## Change sub-folders and file names
577
+
578
+ `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.
579
+
580
+ **`insert_row`**
581
+
582
+ ```json
583
+ {
584
+ "table": "film",
585
+ "values": { "title": "Metropolis", "year": 1927, "minutes": 153, "status": "to watch" }
586
+ }
587
+ ```
588
+
589
+ **`alter_table`**
590
+
591
+ ```json
592
+ { "table": "film", "partition": "{year}" }
593
+ ```
594
+
595
+ Answer:
596
+
597
+ ```json
598
+ {
599
+ "ok": true,
600
+ "op": "alter_table",
601
+ "files_moved": 3,
602
+ "planned_moves": [
603
+ {
604
+ "from": "datanotes/diary/classics/film/His Girl Friday.md",
605
+ "to": "datanotes/diary/classics/film/1940/His Girl Friday.md"
606
+ },
607
+ {
608
+ "from": "datanotes/diary/classics/film/Metropolis.md",
609
+ "to": "datanotes/diary/classics/film/1927/Metropolis.md"
610
+ },
611
+ {
612
+ "from": "datanotes/diary/classics/film/Nosferatu.md",
613
+ "to": "datanotes/diary/classics/film/1922/Nosferatu.md"
614
+ }
615
+ ],
616
+ "planned_texts": ["datanotes/metastore.yaml"],
617
+ "commits": [
618
+ { "table": "diary.classics.film", "version": 13, "operation": "ALTER TABLE" }
619
+ ],
620
+ "migration_id": "20261014T191534-alter_table-alter-table-diary-classics-fil-5"
621
+ }
622
+ ```
623
+
624
+ Metropolis has no `rating` yet, so the engine refuses a name with `{rating}`:
625
+
626
+ **`alter_table`**
627
+
628
+ ```json
629
+ { "table": "film", "filename": "{title} ({rating} stars)" }
630
+ ```
631
+
632
+ Refused:
633
+
634
+ ```text
635
+ alter_table refused — nothing was written:
636
+ conflict datanotes/diary/classics/film/1927/Metropolis.md: cannot compute the new path: path placeholder {rating} has no value
637
+ Hint: Resolve what the message names, then call alter_table again; dry_run: true checks without writing.
638
+ Docs: https://datanotes.dev/guides/agents/reading-errors.md (offline: read_docs("guides/agents/reading-errors"))
639
+ ```
640
+
641
+ **`alter_table`**
642
+
643
+ ```json
644
+ { "table": "film", "filename": "{title} ({year})" }
645
+ ```
646
+
647
+ Answer:
648
+
649
+ ```json
650
+ {
651
+ "ok": true,
652
+ "op": "alter_table",
653
+ "files_moved": 3,
654
+ "planned_moves": [
655
+ {
656
+ "from": "datanotes/diary/classics/film/1922/Nosferatu.md",
657
+ "to": "datanotes/diary/classics/film/1922/Nosferatu (1922).md"
658
+ },
659
+ {
660
+ "from": "datanotes/diary/classics/film/1927/Metropolis.md",
661
+ "to": "datanotes/diary/classics/film/1927/Metropolis (1927).md"
662
+ },
663
+ {
664
+ "from": "datanotes/diary/classics/film/1940/His Girl Friday.md",
665
+ "to": "datanotes/diary/classics/film/1940/His Girl Friday (1940).md"
666
+ }
667
+ ],
668
+ "planned_texts": ["datanotes/metastore.yaml"],
669
+ "commits": [
670
+ { "table": "diary.classics.film", "version": 14, "operation": "ALTER TABLE" }
671
+ ],
672
+ "migration_id": "20261014T191534-alter_table-alter-table-diary-classics-fil-6"
673
+ }
674
+ ```
675
+
676
+ **`list_folder`**
677
+
678
+ ```json
679
+ { "folder": "datanotes/diary/classics/film/1940" }
680
+ ```
681
+
682
+ Answer:
683
+
684
+ ```json
685
+ {
686
+ "folder": "datanotes/diary/classics/film/1940",
687
+ "folders": [],
688
+ "files": ["datanotes/diary/classics/film/1940/His Girl Friday (1940).md"]
689
+ }
690
+ ```
691
+
692
+ ## Set the archive folder and untyped fields
693
+
694
+ `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.
695
+
696
+ A change that touches only metadata, meaning fields, tags, description, history or the template, rewrites the definition alone and moves no row.
697
+
698
+ **`alter_table`**
699
+
700
+ ```json
701
+ { "table": "film", "archive": "_done" }
702
+ ```
703
+
704
+ Answer:
705
+
706
+ ```json
707
+ {
708
+ "ok": true,
709
+ "op": "alter_table",
710
+ "planned_texts": ["datanotes/metastore.yaml"],
711
+ "commits": [
712
+ { "table": "diary.classics.film", "version": 15, "operation": "ALTER TABLE" }
713
+ ],
714
+ "migration_id": "20261014T191534-alter_table-alter-table-diary-classics-fil-7"
715
+ }
716
+ ```
717
+
718
+ **`alter_table`**
719
+
720
+ ```json
721
+ { "table": "film", "fields": { "cut": "restored", "subtitles": "off" } }
722
+ ```
723
+
724
+ Answer:
725
+
726
+ ```json
727
+ {
728
+ "ok": true,
729
+ "op": "alter_table",
730
+ "planned_texts": ["datanotes/metastore.yaml"],
731
+ "commits": [
732
+ { "table": "diary.classics.film", "version": 16, "operation": "ALTER TABLE" }
733
+ ],
734
+ "migration_id": "20261014T191534-alter_table-alter-table-diary-classics-fil-8"
735
+ }
736
+ ```
737
+
738
+ **`alter_table`**
739
+
740
+ ```json
741
+ { "table": "film", "fields": { "subtitles": null } }
742
+ ```
743
+
744
+ Answer:
745
+
746
+ ```json
747
+ {
748
+ "ok": true,
749
+ "op": "alter_table",
750
+ "planned_texts": ["datanotes/metastore.yaml"],
751
+ "commits": [
752
+ { "table": "diary.classics.film", "version": 17, "operation": "ALTER TABLE" }
753
+ ],
754
+ "migration_id": "20261014T191534-alter_table-alter-table-diary-classics-fil-9"
755
+ }
756
+ ```
757
+
758
+ **`insert_row`**
759
+
760
+ ```json
761
+ {
762
+ "table": "film",
763
+ "values": {
764
+ "title": "Battleship Potemkin",
765
+ "year": 1925,
766
+ "minutes": 75,
767
+ "status": "to watch"
768
+ }
769
+ }
770
+ ```
771
+
772
+ Answer:
773
+
774
+ ```json
775
+ {
776
+ "ok": true,
777
+ "op": "insert_row",
778
+ "files_created": 1,
779
+ "planned_creates": ["datanotes/diary/classics/film/1925/Battleship Potemkin (1925).md"],
780
+ "commits": [{ "table": "diary.classics.film", "version": 18, "operation": "WRITE" }],
781
+ "migration_id": "20261014T191534-insert_row-insert-into-film-4"
782
+ }
783
+ ```
784
+
785
+ `datanotes/diary/classics/film/1925/Battleship Potemkin (1925).md` now reads:
786
+
787
+ ```markdown
788
+ ---
789
+ table: diary.classics.film
790
+ cut: restored
791
+ id: 01a13bd7-6708-7c51-9bc8-ec11609a2c93
792
+ title: Battleship Potemkin
793
+ year: 1925
794
+ minutes: 75
795
+ status: to watch
796
+ ---
797
+ ```
798
+
799
+ ## Give new notes a template
800
+
801
+ `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.
802
+
803
+ **`alter_table`**
804
+
805
+ ```json
806
+ {
807
+ "table": "film",
808
+ "row_template": "# {title} ({year})\n\n## What I liked\n\n## Who I watched it with\n"
809
+ }
810
+ ```
811
+
812
+ Answer:
813
+
814
+ ```json
815
+ {
816
+ "ok": true,
817
+ "op": "alter_table",
818
+ "planned_texts": ["datanotes/metastore.yaml"],
819
+ "commits": [
820
+ { "table": "diary.classics.film", "version": 19, "operation": "ALTER TABLE" }
821
+ ],
822
+ "migration_id": "20261014T191534-alter_table-alter-table-diary-classics-fil-10"
823
+ }
824
+ ```
825
+
826
+ **`insert_row`**
827
+
828
+ ```json
829
+ {
830
+ "table": "film",
831
+ "values": { "title": "Sherlock Jr.", "year": 1924, "minutes": 45, "status": "to watch" }
832
+ }
833
+ ```
834
+
835
+ Answer:
836
+
837
+ ```json
838
+ {
839
+ "ok": true,
840
+ "op": "insert_row",
841
+ "files_created": 1,
842
+ "planned_creates": ["datanotes/diary/classics/film/1924/Sherlock Jr. (1924).md"],
843
+ "commits": [{ "table": "diary.classics.film", "version": 20, "operation": "WRITE" }],
844
+ "migration_id": "20261014T191534-insert_row-insert-into-film-5"
845
+ }
846
+ ```
847
+
848
+ `datanotes/diary/classics/film/1924/Sherlock Jr. (1924).md` now reads:
849
+
850
+ ```markdown
851
+ ---
852
+ table: diary.classics.film
853
+ cut: restored
854
+ id: 01a13bd7-6717-7265-ac31-ce2823c07dc6
855
+ title: Sherlock Jr.
856
+ year: 1924
857
+ minutes: 45
858
+ status: to watch
859
+ ---
860
+ # Sherlock Jr. (1924)
861
+
862
+
863
+ ## What I liked
864
+
865
+
866
+ ## Who I watched it with
867
+ ```
868
+
869
+ `row_template: ""` removes the template, and notes already written keep their text:
870
+
871
+ **`alter_table`**
872
+
873
+ ```json
874
+ { "table": "film", "row_template": "" }
875
+ ```
876
+
877
+ Answer:
878
+
879
+ ```json
880
+ {
881
+ "ok": true,
882
+ "op": "alter_table",
883
+ "planned_texts": ["datanotes/metastore.yaml"],
884
+ "commits": [
885
+ { "table": "diary.classics.film", "version": 21, "operation": "ALTER TABLE" }
886
+ ],
887
+ "migration_id": "20261014T191534-alter_table-alter-table-diary-classics-fil-11"
888
+ }
889
+ ```
890
+
891
+ ## Set defaults for new tables
892
+
893
+ The other settings shape the tables you create next:
894
+
895
+ * **`default_schema`**, with `default_catalog`: places a table given only a name
896
+ * **`default_partition`**: the sub-folders of a table with a `time_column`, as a date format
897
+ * **`default_filename`**: the file names of a table with a `time_column`, as a date format
898
+ * **`archive_folder`**: the name of a new table’s archive folder
899
+
900
+ Existing tables keep their settings.
901
+
902
+ **`configure_database`**
903
+
904
+ ```json
905
+ {
906
+ "default_schema": "log",
907
+ "default_partition": "YYYY/MM",
908
+ "default_filename": "YYYY-MM-DD HHmm",
909
+ "archive_folder": "_old"
910
+ }
911
+ ```
912
+
913
+ Answer:
914
+
915
+ ```json
916
+ {
917
+ "default_catalog": "diary",
918
+ "default_schema": "log",
919
+ "default_partition": "YYYY/MM",
920
+ "default_filename": "YYYY-MM-DD HHmm",
921
+ "archive_folder": "_old",
922
+ "timezone": "Europe/Rome",
923
+ "history": true,
924
+ "autolink": true
925
+ }
926
+ ```
927
+
928
+ **`create_table`**
929
+
930
+ ```json
931
+ {
932
+ "name": "watch",
933
+ "description": "Each time I watch a film",
934
+ "columns": [
935
+ {
936
+ "name": "film",
937
+ "type": "link",
938
+ "ref": "diary.classics.film",
939
+ "required": true,
940
+ "description": "The film I watched"
941
+ },
942
+ {
943
+ "name": "occurred_at",
944
+ "type": "datetime",
945
+ "required": true,
946
+ "description": "When the film started"
947
+ },
948
+ {
949
+ "name": "where",
950
+ "type": "enum",
951
+ "values": ["cinema", "home", "outdoor screening"],
952
+ "description": "The kind of place I watched it in"
953
+ },
954
+ {
955
+ "name": "place",
956
+ "type": "string",
957
+ "description": "The cinema, park or room, in my words"
958
+ }
959
+ ],
960
+ "time_column": "occurred_at"
961
+ }
962
+ ```
963
+
964
+ Answer:
965
+
966
+ ```json
967
+ {
968
+ "ok": true,
969
+ "migration_id": "20261014T191534-create_table-create-table-diary-log-watch"
970
+ }
971
+ ```
972
+
973
+ 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:
974
+
975
+ **`insert_row`**
976
+
977
+ ```json
978
+ {
979
+ "table": "watch",
980
+ "values": {
981
+ "film": "[[Nosferatu (1922)]]",
982
+ "occurred_at": "2026-10-06T21:30",
983
+ "where": "outdoor screening",
984
+ "place": "the park by the river"
985
+ }
986
+ }
987
+ ```
988
+
989
+ Answer:
990
+
991
+ ```json
992
+ {
993
+ "ok": true,
994
+ "op": "insert_row",
995
+ "files_created": 1,
996
+ "planned_creates": ["datanotes/diary/log/watch/2026/10/2026-10-06 2130.md"],
997
+ "commits": [{ "table": "diary.log.watch", "version": 1, "operation": "WRITE" }],
998
+ "migration_id": "20261014T191534-insert_row-insert-into-watch"
999
+ }
1000
+ ```
1001
+
1002
+ `datanotes/diary/log/watch/2026/10/2026-10-06 2130.md` now reads:
1003
+
1004
+ ```markdown
1005
+ ---
1006
+ table: diary.log.watch
1007
+ id: 01a13bd7-6747-7f6c-aff2-ff81497a9a02
1008
+ film: "[[Nosferatu (1922)]]"
1009
+ occurred_at: 2026-10-06T21:30:00+02:00
1010
+ where: outdoor screening
1011
+ place: the park by the river
1012
+ ---
1013
+ ```
1014
+
1015
+ ## Copy a table to try changes
1016
+
1017
+ `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.
1018
+
1019
+ **`clone_table`**
1020
+
1021
+ ```json
1022
+ { "source": "diary.classics.film", "target": "sandbox.classics.film_copy" }
1023
+ ```
1024
+
1025
+ Answer:
1026
+
1027
+ ```json
1028
+ {
1029
+ "ok": true,
1030
+ "op": "clone_table",
1031
+ "files_created": 5,
1032
+ "planned_creates": [
1033
+ "datanotes/sandbox/classics/film_copy/1922/Nosferatu (1922).md",
1034
+ "datanotes/sandbox/classics/film_copy/1924/Sherlock Jr. (1924).md",
1035
+ "datanotes/sandbox/classics/film_copy/1925/Battleship Potemkin (1925).md",
1036
+ "datanotes/sandbox/classics/film_copy/1927/Metropolis (1927).md",
1037
+ "datanotes/sandbox/classics/film_copy/1940/His Girl Friday (1940).md"
1038
+ ],
1039
+ "planned_texts": ["datanotes/metastore.yaml"],
1040
+ "commits": [
1041
+ { "table": "sandbox.classics.film_copy", "version": 0, "operation": "CLONE" }
1042
+ ],
1043
+ "migration_id": "20261014T191534-clone_table-clone-diary-classics-film-to-s"
1044
+ }
1045
+ ```
1046
+
1047
+ **`select_rows`**
1048
+
1049
+ ```json
1050
+ { "table": "sandbox.classics.film_copy", "columns": ["title", "year"] }
1051
+ ```
1052
+
1053
+ Answer:
1054
+
1055
+ ```json
1056
+ {
1057
+ "table": "sandbox.classics.film_copy",
1058
+ "total": 5,
1059
+ "rows": [
1060
+ {
1061
+ "path": "datanotes/sandbox/classics/film_copy/1922/Nosferatu (1922).md",
1062
+ "title": "Nosferatu",
1063
+ "year": 1922
1064
+ },
1065
+ {
1066
+ "path": "datanotes/sandbox/classics/film_copy/1924/Sherlock Jr. (1924).md",
1067
+ "title": "Sherlock Jr.",
1068
+ "year": 1924
1069
+ },
1070
+ {
1071
+ "path": "datanotes/sandbox/classics/film_copy/1925/Battleship Potemkin (1925).md",
1072
+ "title": "Battleship Potemkin",
1073
+ "year": 1925
1074
+ },
1075
+ {
1076
+ "path": "datanotes/sandbox/classics/film_copy/1927/Metropolis (1927).md",
1077
+ "title": "Metropolis",
1078
+ "year": 1927
1079
+ },
1080
+ {
1081
+ "path": "datanotes/sandbox/classics/film_copy/1940/His Girl Friday (1940).md",
1082
+ "title": "His Girl Friday",
1083
+ "year": 1940
1084
+ }
1085
+ ]
1086
+ }
1087
+ ```
1088
+
1089
+ **`clone_table`**
1090
+
1091
+ ```json
1092
+ {
1093
+ "source": "diary.classics.film",
1094
+ "target": "sandbox.classics.film_empty",
1095
+ "copy_rows": false
1096
+ }
1097
+ ```
1098
+
1099
+ Answer:
1100
+
1101
+ ```json
1102
+ {
1103
+ "ok": true,
1104
+ "op": "clone_table",
1105
+ "planned_texts": ["datanotes/metastore.yaml"],
1106
+ "commits": [
1107
+ { "table": "sandbox.classics.film_empty", "version": 0, "operation": "CLONE" }
1108
+ ],
1109
+ "migration_id": "20261014T191534-clone_table-clone-diary-classics-film-to-s-2"
1110
+ }
1111
+ ```
1112
+
1113
+ **`describe_table`**
1114
+
1115
+ ```json
1116
+ { "table": "film_empty" }
1117
+ ```
1118
+
1119
+ Answer:
1120
+
1121
+ ```json
1122
+ {
1123
+ "name": "film_empty",
1124
+ "catalog": "sandbox",
1125
+ "schema": "classics",
1126
+ "rows": 0,
1127
+ "columns": ["id", "added_on", "title", "year", "minutes", "status", "rating"]
1128
+ }
1129
+ ```
1130
+
1131
+ ## Drop a table and archive its rows
1132
+
1133
+ `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.
1134
+
1135
+ 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).
1136
+
1137
+ **`create_table`**
1138
+
1139
+ ```json
1140
+ {
1141
+ "name": "scratch",
1142
+ "description": "Throwaway notes for trying things out",
1143
+ "columns": [{ "name": "note", "type": "string", "description": "The note's text" }],
1144
+ "filename": "{note}",
1145
+ "archive": "Archive/scratch"
1146
+ }
1147
+ ```
1148
+
1149
+ **`insert_row`**
1150
+
1151
+ ```json
1152
+ { "table": "scratch", "values": { "note": "temporary" } }
1153
+ ```
1154
+
1155
+ **`drop_table`**
1156
+
1157
+ ```json
1158
+ { "table": "scratch" }
1159
+ ```
1160
+
1161
+ Refused:
1162
+
1163
+ ```text
1164
+ drop_table refused — nothing was written:
1165
+ conflict datanotes/metastore.yaml: table has 1 rows; pass mode: "archive" to archive them with the table
1166
+ Hint: Pass mode: "archive" to archive the rows with the table, or delete them first (delete_rows).
1167
+ 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"))
1168
+ ```
1169
+
1170
+ **`drop_table`**
1171
+
1172
+ ```json
1173
+ { "table": "scratch", "mode": "archive" }
1174
+ ```
1175
+
1176
+ Answer:
1177
+
1178
+ ```json
1179
+ {
1180
+ "ok": true,
1181
+ "op": "drop_table",
1182
+ "files_moved": 1,
1183
+ "planned_moves": [
1184
+ {
1185
+ "from": "datanotes/main/public/scratch/temporary.md",
1186
+ "to": "Archive/scratch/temporary.md"
1187
+ }
1188
+ ],
1189
+ "planned_texts": ["datanotes/metastore.yaml"],
1190
+ "commits": [{ "table": "main.public.scratch", "version": 2, "operation": "DROP TABLE" }],
1191
+ "migration_id": "20261014T191516-drop_table-drop-table-scratch"
1192
+ }
1193
+ ```
1194
+
1195
+ **`list_folder`**
1196
+
1197
+ ```json
1198
+ { "folder": "Archive/scratch" }
1199
+ ```
1200
+
1201
+ Answer:
1202
+
1203
+ ```json
1204
+ { "folder": "Archive/scratch", "folders": [], "files": ["Archive/scratch/temporary.md"] }
1205
+ ```
1206
+
1207
+ ## See the tables in `metastore.yaml`
1208
+
1209
+ The engine wrote this file in its own words. It holds `film`, `watch` and `review`, and `scratch` is gone because you dropped it.
1210
+
1211
+ `datanotes/metastore.yaml` now reads:
1212
+
1213
+ ```yaml
1214
+ # Metastore (Datanotes): catalogs, schemas, tables, columns, views.
1215
+ id: 01a13bd7-1943-7bc6-ab38-ab176313afeb
1216
+ config:
1217
+ history: true
1218
+
1219
+
1220
+ catalogs:
1221
+ main:
1222
+ schemas:
1223
+ public:
1224
+ tables:
1225
+ film:
1226
+ description: Classic films I watched or want to watch
1227
+ aliases:
1228
+ - films
1229
+ - movies
1230
+ - watchlist
1231
+ options:
1232
+ filename: "{title}"
1233
+ archive: _archive
1234
+ columns:
1235
+ - name: id
1236
+ type: string
1237
+ required: true
1238
+ description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
1239
+ unique: true
1240
+ identity: uuid
1241
+ dense: true
1242
+ - name: title
1243
+ type: string
1244
+ required: true
1245
+ description: The film's title, as on its poster
1246
+ unique: true
1247
+ - name: year
1248
+ type: integer
1249
+ description: Year the film first came out
1250
+ - name: minutes
1251
+ type: integer
1252
+ min: 1
1253
+ description: Running time in minutes
1254
+ - name: status
1255
+ type: enum
1256
+ default: to watch
1257
+ values:
1258
+ - to watch
1259
+ - watched
1260
+ description: Where the film is on my list
1261
+ ordered: true
1262
+ - name: rating
1263
+ type: integer
1264
+ min: 1
1265
+ max: 5
1266
+ description: My rating, from 1 (poor) to 5 (a masterpiece)
1267
+ - name: watched_on
1268
+ type: date
1269
+ description: The day I last watched it
1270
+ watch:
1271
+ description: Each time I watch a film
1272
+ tags:
1273
+ area: films
1274
+ options:
1275
+ filename: "{occurred_at:YYYY-MM-DD} {film}"
1276
+ archive: _archive
1277
+ fields:
1278
+ cssclasses:
1279
+ - watch
1280
+ columns:
1281
+ - name: id
1282
+ type: string
1283
+ required: true
1284
+ description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
1285
+ unique: true
1286
+ identity: uuid
1287
+ dense: true
1288
+ - name: film
1289
+ type: string
1290
+ required: true
1291
+ description: The film I watched
1292
+ - name: where
1293
+ type: enum
1294
+ required: true
1295
+ values:
1296
+ - cinema
1297
+ - home
1298
+ - outdoor screening
1299
+ description: The kind of place I watched it in
1300
+ - name: place
1301
+ type: string
1302
+ description: The cinema, park or room, in my words
1303
+ - name: occurred_at
1304
+ type: datetime
1305
+ default: "{now}"
1306
+ description: When the film started
1307
+ - name: price
1308
+ type: number
1309
+ min: 0
1310
+ step: 0.01
1311
+ description: What the ticket cost, if anything
1312
+ row_template: |
1313
+ Watched {film} ({where}).
1314
+
1315
+
1316
+ ## Thoughts
1317
+ review:
1318
+ description: What I wrote about a film after watching it
1319
+ options:
1320
+ filename: "{film}"
1321
+ archive: _archive
1322
+ exclude: datanotes/main/public/review/Templates
1323
+ history: true
1324
+ columns:
1325
+ - name: id
1326
+ type: string
1327
+ required: true
1328
+ description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
1329
+ unique: true
1330
+ identity: uuid
1331
+ dense: true
1332
+ - name: film
1333
+ type: string
1334
+ required: true
1335
+ description: The film the review is about
1336
+ unique: true
1337
+ - name: verdict
1338
+ type: string
1339
+ description: The review in one sentence
1340
+ ```