datanotes 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/NOTICE +9 -0
  2. package/README.md +207 -28
  3. package/SECURITY.md +92 -45
  4. package/dist/THIRD-PARTY-NOTICES.txt +682 -0
  5. package/dist/api/catalog.d.cts +69 -0
  6. package/dist/api/catalog.d.ts +50 -2
  7. package/dist/api/cli-options.d.cts +39 -0
  8. package/dist/api/cli-options.d.ts +39 -0
  9. package/dist/api/client.d.cts +89 -0
  10. package/dist/api/client.d.ts +19 -30
  11. package/dist/api/docs.d.cts +211 -0
  12. package/dist/api/docs.d.ts +211 -0
  13. package/dist/api/guidance.d.cts +81 -0
  14. package/dist/api/guidance.d.ts +81 -0
  15. package/dist/api/index.cjs +6610 -2247
  16. package/dist/api/index.d.cts +13 -0
  17. package/dist/api/index.d.ts +10 -7
  18. package/dist/api/index.js +6610 -2247
  19. package/dist/api/local.cjs +118 -6
  20. package/dist/api/local.d.cts +95 -0
  21. package/dist/api/local.d.ts +70 -6
  22. package/dist/api/local.js +104 -6
  23. package/dist/api/mcp.cjs +17384 -0
  24. package/dist/api/mcp.d.cts +108 -0
  25. package/dist/api/mcp.d.ts +108 -0
  26. package/dist/api/mcp.js +17334 -0
  27. package/dist/api/otlp.d.cts +65 -0
  28. package/dist/api/redact.d.cts +17 -0
  29. package/dist/api/redact.d.ts +17 -0
  30. package/dist/api/requests.d.cts +26 -0
  31. package/dist/api/requests.d.ts +14 -16
  32. package/dist/api/results.d.cts +16 -0
  33. package/dist/api/results.d.ts +2 -1
  34. package/dist/api/tokens.d.cts +7 -0
  35. package/dist/datanotes.cjs +62911 -28409
  36. package/dist/docs/administration/configuration.md +59 -0
  37. package/dist/docs/administration/docker.md +80 -0
  38. package/dist/docs/administration/git-sync.md +71 -0
  39. package/dist/docs/administration/limits.md +69 -0
  40. package/dist/docs/administration/run-the-service.md +219 -0
  41. package/dist/docs/administration/status.md +61 -0
  42. package/dist/docs/administration/use-with-obsidian.md +75 -0
  43. package/dist/docs/api/operations/add_column.md +49 -0
  44. package/dist/docs/api/operations/add_constraint.md +55 -0
  45. package/dist/docs/api/operations/adopt_rows.md +50 -0
  46. package/dist/docs/api/operations/align_relations.md +41 -0
  47. package/dist/docs/api/operations/alter_column.md +46 -0
  48. package/dist/docs/api/operations/alter_table.md +67 -0
  49. package/dist/docs/api/operations/batch.md +48 -0
  50. package/dist/docs/api/operations/changes_stream.md +28 -0
  51. package/dist/docs/api/operations/check_table.md +29 -0
  52. package/dist/docs/api/operations/clone_table.md +48 -0
  53. package/dist/docs/api/operations/comment_on.md +42 -0
  54. package/dist/docs/api/operations/configure_database.md +52 -0
  55. package/dist/docs/api/operations/create_table.md +78 -0
  56. package/dist/docs/api/operations/create_view.md +54 -0
  57. package/dist/docs/api/operations/create_webhook.md +37 -0
  58. package/dist/docs/api/operations/define_base.md +52 -0
  59. package/dist/docs/api/operations/delete_rows.md +43 -0
  60. package/dist/docs/api/operations/delete_webhook.md +31 -0
  61. package/dist/docs/api/operations/describe_database.md +30 -0
  62. package/dist/docs/api/operations/describe_history.md +40 -0
  63. package/dist/docs/api/operations/describe_semantic_index.md +24 -0
  64. package/dist/docs/api/operations/describe_table.md +38 -0
  65. package/dist/docs/api/operations/docs.md +13 -0
  66. package/dist/docs/api/operations/drop_column.md +41 -0
  67. package/dist/docs/api/operations/drop_constraint.md +39 -0
  68. package/dist/docs/api/operations/drop_table.md +43 -0
  69. package/dist/docs/api/operations/drop_view.md +38 -0
  70. package/dist/docs/api/operations/engine_status.md +23 -0
  71. package/dist/docs/api/operations/graph_neighbors.md +32 -0
  72. package/dist/docs/api/operations/graph_path.md +32 -0
  73. package/dist/docs/api/operations/graph_traverse.md +43 -0
  74. package/dist/docs/api/operations/identify_session.md +24 -0
  75. package/dist/docs/api/operations/infer_table.md +43 -0
  76. package/dist/docs/api/operations/insert_row.md +48 -0
  77. package/dist/docs/api/operations/list_changes.md +35 -0
  78. package/dist/docs/api/operations/list_folder.md +28 -0
  79. package/dist/docs/api/operations/list_link_targets.md +44 -0
  80. package/dist/docs/api/operations/list_migrations.md +35 -0
  81. package/dist/docs/api/operations/list_tables.md +32 -0
  82. package/dist/docs/api/operations/list_webhooks.md +23 -0
  83. package/dist/docs/api/operations/manual_index.md +14 -0
  84. package/dist/docs/api/operations/manual_page.md +22 -0
  85. package/dist/docs/api/operations/merge_rows.md +44 -0
  86. package/dist/docs/api/operations/move_rows.md +52 -0
  87. package/dist/docs/api/operations/openapi.md +13 -0
  88. package/dist/docs/api/operations/openapi_post.md +20 -0
  89. package/dist/docs/api/operations/read_note.md +29 -0
  90. package/dist/docs/api/operations/recompute_columns.md +44 -0
  91. package/dist/docs/api/operations/record_tool_call.md +36 -0
  92. package/dist/docs/api/operations/rename_column.md +44 -0
  93. package/dist/docs/api/operations/rename_value.md +45 -0
  94. package/dist/docs/api/operations/restore_database.md +51 -0
  95. package/dist/docs/api/operations/restore_table.md +46 -0
  96. package/dist/docs/api/operations/search_notes.md +28 -0
  97. package/dist/docs/api/operations/select_rows.md +61 -0
  98. package/dist/docs/api/operations/semantic_search.md +34 -0
  99. package/dist/docs/api/operations/semantic_search_docs.md +26 -0
  100. package/dist/docs/api/operations/set_table_bases.md +44 -0
  101. package/dist/docs/api/operations/status.md +13 -0
  102. package/dist/docs/api/operations/stop_engine.md +23 -0
  103. package/dist/docs/api/operations/sync_semantic_index.md +33 -0
  104. package/dist/docs/api/operations/table_changes.md +31 -0
  105. package/dist/docs/api/operations/tags/auxiliary.md +14 -0
  106. package/dist/docs/api/operations/tags/change-feed.md +8 -0
  107. package/dist/docs/api/operations/tags/history.md +17 -0
  108. package/dist/docs/api/operations/tags/queries.md +15 -0
  109. package/dist/docs/api/operations/tags/rows-dml.md +16 -0
  110. package/dist/docs/api/operations/tags/schema-ddl.md +22 -0
  111. package/dist/docs/api/operations/tags/service.md +15 -0
  112. package/dist/docs/api/operations/tags/tokens.md +13 -0
  113. package/dist/docs/api/operations/tokens_create.md +34 -0
  114. package/dist/docs/api/operations/tokens_delete.md +29 -0
  115. package/dist/docs/api/operations/tokens_list.md +21 -0
  116. package/dist/docs/api/operations/tokens_me.md +20 -0
  117. package/dist/docs/api/operations/tokens_rotate.md +29 -0
  118. package/dist/docs/api/operations/tokens_update.md +34 -0
  119. package/dist/docs/api/operations/undo_migration.md +46 -0
  120. package/dist/docs/api/operations/update_row.md +60 -0
  121. package/dist/docs/api/operations/update_rows.md +46 -0
  122. package/dist/docs/api/operations/vacuum_table.md +37 -0
  123. package/dist/docs/api.md +135 -0
  124. package/dist/docs/develop/build-an-app.md +320 -0
  125. package/dist/docs/develop/build-on-datanotes.md +138 -0
  126. package/dist/docs/develop/change-feed-and-webhooks.md +70 -0
  127. package/dist/docs/develop/client.md +116 -0
  128. package/dist/docs/develop/concurrency.md +153 -0
  129. package/dist/docs/develop/http-api.md +79 -0
  130. package/dist/docs/develop/telemetry.md +86 -0
  131. package/dist/docs/guides/agents/docs-offline.md +99 -0
  132. package/dist/docs/guides/agents/how-agents-use-datanotes.md +124 -0
  133. package/dist/docs/guides/agents/prompts.md +495 -0
  134. package/dist/docs/guides/agents/reading-errors.md +112 -0
  135. package/dist/docs/guides/data-quality/check-constraints.md +379 -0
  136. package/dist/docs/guides/data-quality/check-table.md +63 -0
  137. package/dist/docs/guides/data-quality/hand-edits.md +507 -0
  138. package/dist/docs/guides/data-quality/sync-between-devices.md +51 -0
  139. package/dist/docs/guides/data-quality/validation.md +90 -0
  140. package/dist/docs/guides/database-objects/bases.md +480 -0
  141. package/dist/docs/guides/database-objects/catalogs-and-schemas.md +897 -0
  142. package/dist/docs/guides/database-objects/columns.md +1332 -0
  143. package/dist/docs/guides/database-objects/relations.md +1454 -0
  144. package/dist/docs/guides/database-objects/tables.md +1404 -0
  145. package/dist/docs/guides/database-objects/views.md +1003 -0
  146. package/dist/docs/guides/history/back-up.md +195 -0
  147. package/dist/docs/guides/history/change-data-feed.md +247 -0
  148. package/dist/docs/guides/history/journal-and-undo.md +262 -0
  149. package/dist/docs/guides/history/restore.md +341 -0
  150. package/dist/docs/guides/history/table-history.md +657 -0
  151. package/dist/docs/guides/history/vacuum.md +35 -0
  152. package/dist/docs/guides/read-and-write/automatic-links.md +768 -0
  153. package/dist/docs/guides/read-and-write/batch.md +139 -0
  154. package/dist/docs/guides/read-and-write/import-notes.md +896 -0
  155. package/dist/docs/guides/read-and-write/joins-and-aggregates.md +251 -0
  156. package/dist/docs/guides/read-and-write/read-and-filter.md +536 -0
  157. package/dist/docs/guides/read-and-write/write-rows.md +573 -0
  158. package/dist/docs/guides/search/link-graph.md +146 -0
  159. package/dist/docs/guides/search/semantic.md +133 -0
  160. package/dist/docs/guides/search/text.md +142 -0
  161. package/dist/docs/guides/security/security-model.md +110 -0
  162. package/dist/docs/guides/security/tokens.md +60 -0
  163. package/dist/docs/guides/security/who-did-what.md +42 -0
  164. package/dist/docs/index.md +113 -0
  165. package/dist/docs/llms.txt +33 -0
  166. package/dist/docs/reference/cli.md +136 -0
  167. package/dist/docs/reference/compatibility.md +63 -0
  168. package/dist/docs/reference/errors.md +72 -0
  169. package/dist/docs/reference/glossary.md +161 -0
  170. package/dist/docs/reference/operations/auxiliary.md +116 -0
  171. package/dist/docs/reference/operations/history.md +168 -0
  172. package/dist/docs/reference/operations/queries.md +169 -0
  173. package/dist/docs/reference/operations/rows.md +185 -0
  174. package/dist/docs/reference/operations/schema.md +316 -0
  175. package/dist/docs/reference/operations.md +96 -0
  176. package/dist/docs/reference/settings.md +31 -0
  177. package/dist/docs/release-notes.md +67 -0
  178. package/dist/docs/start/connect-agents.md +204 -0
  179. package/dist/docs/start/how-it-works.md +317 -0
  180. package/dist/docs/start/quick-start.md +126 -0
  181. package/package.json +59 -14
@@ -0,0 +1,195 @@
1
+ # Back up the history
2
+
3
+ > Keep the engine’s journal and transaction logs in a private git repository, and restore them on a new machine.
4
+
5
+ Undo, table history and time travel read the engine’s store, a folder outside the vault. Git sync of the vault doesn’t carry it, so a lost disk would lose them.
6
+
7
+ The store backup keeps the store in a git repository of its own and pushes it to a private remote, such as a GitHub repository. With the vault’s own git sync, a restore on a new machine brings back undo, history and time travel.
8
+
9
+ ## Find the logs that time travel reads
10
+
11
+ Time travel, undo and restores read three folders of the engine’s store, `<data home>/<database id>/`:
12
+
13
+ * **`journal/`**: one line per operation, with the data to undo it; `undo_migration` reads it
14
+ * **`transaction_log/`**: the versions of each table with history; `describe_history`, `select_rows` with `version` or `timestamp`, and the restores read them
15
+ * **`blobs/`**: the long texts that journal entries point to, stored once
16
+
17
+ `datanotes store path` shows where the store is. The store isn’t in the vault, so the vault’s [git sync](https://datanotes.dev/administration/git-sync/) never sees it. The store backup commits these folders to a repository of their own and pushes them.
18
+
19
+ ## Turn on the store backup
20
+
21
+ Git runs as the user who runs the engine, with that user’s credentials and SSH keys.
22
+
23
+ 1. Create an empty private repository, such as `vault-store` on GitHub.
24
+ 2. Check that git can push to it from this machine, as this user.
25
+ 3. Stop the engine.
26
+ 4. Turn the backup on, with the repository’s address.
27
+ 5. Start the engine.
28
+
29
+ ```bash
30
+ npx -y datanotes store backup --vault your_vault_folder \
31
+ --remote git@github.com:you/vault-store.git
32
+ ```
33
+
34
+ The command saves the setting, makes the store a git repository and pushes it once. A wrong address or a missing credential shows at once: the command then exits with code 1, and the engine retries later.
35
+
36
+ The first commit and push can take minutes on a large store, such as 3,400 files and 150 MB. The command prints each step and waits for it. The push may take up to 10 minutes, or `DATANOTES_GIT_NETWORK_TIMEOUT_S` seconds. A step that fails leaves no git lock behind, so you can run the command again.
37
+
38
+ Run `datanotes store backup --vault your_vault_folder` to see the setting. Add `--off` to turn the backup off: the store’s repository stays as it is.
39
+
40
+ Keep the repository private. It holds what your agents wrote, and the change feed holds the values they wrote too.
41
+
42
+ ## Push over SSH
43
+
44
+ Git pushes an `ssh://` or `git@` address with an SSH program. The store’s repository uses the one its `core.sshCommand` setting names. Without it, git uses its default SSH, in batch mode.
45
+
46
+ On Windows, the default is the SSH program bundled with Git for Windows. It doesn’t read keys from the Windows OpenSSH agent. If your key lives in that agent, the push fails with `Permission denied (publickey)`. Windows’ own OpenSSH reaches the agent.
47
+
48
+ The backup picks the SSH program in this order:
49
+
50
+ 1. The command you pass with `--ssh-command`. It’s saved as the store repository’s `core.sshCommand`.
51
+ 2. The `core.sshCommand` the store’s repository already has.
52
+ 3. The `core.sshCommand` of the vault’s own git repository, the one [git sync](https://datanotes.dev/administration/git-sync/) uses. The backup copies it to the store’s repository.
53
+
54
+ ```bash
55
+ npx -y datanotes store backup --vault your_vault_folder \
56
+ --remote git@github.com:you/vault-store.git \
57
+ --ssh-command C:/Windows/System32/OpenSSH/ssh.exe
58
+ ```
59
+
60
+ To check that the program reaches your key, run it as the user who runs the engine:
61
+
62
+ ```bash
63
+ C:/Windows/System32/OpenSSH/ssh.exe -T git@github.com
64
+ ```
65
+
66
+ ## Choose how often the backup commits
67
+
68
+ The backup looks at the store on a regular schedule, every 60 seconds by default. At each look:
69
+
70
+ 1. If nothing changed in the store, it does nothing.
71
+ 2. If something changed, it waits until the engine is idle: no operation running, and none for 3 seconds.
72
+ 3. It commits between two operations, then pushes.
73
+
74
+ An operation writes several files of the store: a journal line, new table versions, sometimes blobs. A commit in the middle of one would save half of it. So the commit holds the engine’s write lock, like a short read lock, for `git add` and `git commit` only. An operation that arrives meanwhile waits for the commit, usually a fraction of a second. The push runs after the lock is released.
75
+
76
+ When the engine stays busy, the backup tries again at the next look. After 5 busy looks in a row, the next one commits at the first gap between two operations anyway.
77
+
78
+ A lost disk costs at most the last interval of history: 60 seconds by default. A restore can then miss those seconds, while the vault may already hold the notes they wrote.
79
+
80
+ Set the interval with `--interval-s`, in seconds, at least 10:
81
+
82
+ ```bash
83
+ npx -y datanotes store backup --vault your_vault_folder --interval-s 30
84
+ ```
85
+
86
+ A push that fails is retried with a backoff, doubled at each failure up to 30 minutes. It never fails an operation. When the engine stops, it makes a last commit and push, for up to 15 seconds. After a forced stop, such as a killed process, the next start commits and pushes what was left at once. It first removes the git lock files, such as `.git/index.lock`, that a killed git leaves behind. Only one engine at a time uses a store, so no other git can hold them.
87
+
88
+ Git never prompts, and each git command has a time limit: 10 minutes for a push, 60 seconds for the others. A push that runs out of time counts as a failed push. [Git never waits for a person](https://datanotes.dev/administration/git-sync/#git-never-waits-for-a-person) explains it.
89
+
90
+ Git packs its files from time to time with `git gc`. On a large store, that takes minutes. So no `git gc` runs while the commit holds the write lock. The backup runs `git gc --auto` after the push instead, when operations no longer wait.
91
+
92
+ ## Set the backup for one run
93
+
94
+ `datanotes serve` takes the same choices for one run, over the settings:
95
+
96
+ | Option | Variable | Meaning |
97
+ | -------------------------------- | ----------------------------------- | -------------------------------------------------- |
98
+ | `--store-backup` | `DATANOTES_STORE_BACKUP=1` | Turns the backup on |
99
+ | `--no-store-backup` | `DATANOTES_STORE_BACKUP=0` | Turns the backup off |
100
+ | `--store-backup-remote your_url` | `DATANOTES_STORE_BACKUP_REMOTE` | The repository to push to; turns the backup on |
101
+ | `--store-backup-interval-s 60` | `DATANOTES_STORE_BACKUP_INTERVAL_S` | Seconds between two looks for changes, at least 10 |
102
+
103
+ The settings `storeBackupEnabled`, `storeBackupRemote` and `storeBackupIntervalSeconds` keep the choice from one run to the next. `datanotes store backup` writes them.
104
+
105
+ ## See what the backup keeps
106
+
107
+ The engine writes the store’s `.gitignore`. It names the files to keep, and ignores every other file:
108
+
109
+ | Kept | Left out |
110
+ | ------------------------------------------- | ------------------------------------------------------ |
111
+ | `journal/`, `blobs/` and `transaction_log/` | `settings.json`, with the owner token and the settings |
112
+ | `changes/`, the change feed | `webhooks.json`, with the webhooks’ secrets |
113
+ | `database_moves.jsonl` | `engine.lock`, `store.json` and `migration.json` |
114
+ | `tokens.json`, the named tokens’ hashes | `semantic/` and `logs/` |
115
+
116
+ `tokens.json` holds a hash of each named token, never the token. A restore keeps every named token working.
117
+
118
+ The change feed records the tool calls of your agents, with their results. A secret never gets there: a webhook’s `secret` from `create_webhook`, and any value under a key such as `token`, `password` or `api_key`, become `[redacted]`. The agent still gets the secret in its answer. The change feed goes on from its last number, so programs that read it with `since` keep their place.
119
+
120
+ ## Check the backup
121
+
122
+ `datanotes status` shows the backup on its `backup` line: the cadence, the remote, the last commit and push, and what waits. A failing push makes the status `needs attention`, and the command exits with code 1.
123
+
124
+ `POST /datanotes/status/` returns the same in `store_backup`:
125
+
126
+ ```json
127
+ {
128
+ "store_backup": {
129
+ "enabled": true,
130
+ "interval_s": 60,
131
+ "remote": "git@github.com:you/vault-store.git",
132
+ "last_commit": "3f9c2a1",
133
+ "last_commit_at": "2026-10-10T08:12:00.000Z",
134
+ "last_push_at": "2026-10-10T08:12:01.000Z",
135
+ "pending_operations": 0,
136
+ "pending_commits": 0
137
+ }
138
+ }
139
+ ```
140
+
141
+ The remote never shows a password or a token from its address.
142
+
143
+ ## Restore the store from its backup
144
+
145
+ On the new machine, restore the vault first, then its store:
146
+
147
+ 1. Clone the vault’s repository.
148
+ 2. Restore the store, naming the backup and the vault.
149
+ 3. Copy the owner token the command prints, once.
150
+ 4. Start the engine.
151
+
152
+ ```bash
153
+ git clone git@github.com:you/vault.git your_vault_folder
154
+ npx -y datanotes store restore --vault your_vault_folder \
155
+ --from git@github.com:you/vault-store.git
156
+ ```
157
+
158
+ The command reads the database’s id in the vault’s `metastore.yaml`. It clones the backup into `<data home>/<database id>/` and checks that the backup belongs to that database. It then writes new settings, with a new owner token and the backup turned on.
159
+
160
+ Undo, history and time travel then work as before. Two things don’t come back, and you set them again:
161
+
162
+ * the owner token, which is new: update the programs and agents that used the old one
163
+ * the webhooks, whose secrets the backup leaves out: create them again
164
+
165
+ These options change what the restore does:
166
+
167
+ * **`--force`**: restores over a store that already holds files, after renaming it `<store>.before-restore-<time>`
168
+ * **`--port`**: the port of the new settings, by default `27150`
169
+ * **`--no-backup`**: leaves the backup off in the new settings
170
+ * **`--store`**: restores into this folder, for an engine started with `--store`
171
+
172
+ Run one engine per backup repository. Two engines that push to the same repository refuse each other’s pushes.
173
+
174
+ ## Keep the backup repository small
175
+
176
+ Git keeps every version of every file, so the repository only grows. `vacuum_table` deletes old history from the store, and the next commit records the deletion, but earlier commits still hold the files.
177
+
178
+ To start again with a small repository, keep the old one as an archive:
179
+
180
+ 1. Stop the engine.
181
+ 2. Create a new, empty private repository.
182
+ 3. Rename the `.git` folder of the store, for example to `.git-old`.
183
+ 4. Turn the backup on with the new repository’s address.
184
+ 5. Start the engine.
185
+
186
+ The new repository starts from the store as it is now. Delete the old repository once you no longer need its history.
187
+
188
+ ## Keep each file under 95 MB
189
+
190
+ GitHub refuses a push with a file over 100 MB, every time. So before each push, the backup looks for a file over 95 MB in the commits to push. When it finds one, it doesn’t push, and doesn’t try again until the file is gone. `datanotes status` shows the backup as failing, with the file and its size, and the fix in `hint`.
191
+
192
+ To fix it:
193
+
194
+ 1. Make the file smaller. `vacuum_table` deletes a table’s old history.
195
+ 2. Start the backup repository again, as [Keep the backup repository small](#keep-the-backup-repository-small) shows. The large file stays in the old commits until you do.
@@ -0,0 +1,247 @@
1
+ # Change data feed
2
+
3
+ > Follow every change to rows, the schema and notes as it happens: poll the change feed, or have the engine push it to a webhook.
4
+
5
+ The change feed, `list_changes`, lists every change as an event: writes through the engine, hand edits with the rules they break, schema changes and, when asked for, tool calls. It works without history. Poll it from a position, or wait for the next event with `wait_ms`; a webhook has the engine post each event to a web address instead.
6
+
7
+ [Change feed and webhooks](https://datanotes.dev/develop/change-feed-and-webhooks/) shows how a program follows the feed.
8
+
9
+ ## Follow every change with the change feed
10
+
11
+ `list_changes` returns numbered row changes across all tables, oldest first. It includes engine writes and hand edits (`source: external`). Keep the returned `last` and pass it as `since` next time to get only what’s new. `wait_ms` waits for the next change, a technique called long polling.
12
+
13
+ **`list_changes`**
14
+
15
+ ```json
16
+ { "tables": ["film"], "limit": 3 }
17
+ ```
18
+
19
+ Answer (74 lines)
20
+
21
+ ```json
22
+ {
23
+ "events": [
24
+ {
25
+ "seq": 3,
26
+ "at": "2026-10-12T07:20:04.624Z",
27
+ "table": "main.public.film",
28
+ "kind": "insert",
29
+ "op": "insert_row",
30
+ "path": "datanotes/main/public/film/Nosferatu.md",
31
+ "row_id": "01a12efb-9f0c-7795-aacb-6e9601757026",
32
+ "changes": {
33
+ "id": { "after": "01a12efb-9f0c-7795-aacb-6e9601757026" },
34
+ "title": { "after": "Nosferatu" },
35
+ "status": { "after": "to watch" }
36
+ },
37
+ "source": "engine",
38
+ "row": {
39
+ "id": "01a12efb-9f0c-7795-aacb-6e9601757026",
40
+ "title": "Nosferatu",
41
+ "status": "to watch"
42
+ },
43
+ "actor": { "session": "history", "harness": "docs-example", "auth": "owner" },
44
+ "migration_id": "20261012T072004-insert_row-insert-into-film"
45
+ },
46
+ {
47
+ "seq": 4,
48
+ "at": "2026-10-12T07:20:04.630Z",
49
+ "table": "main.public.film",
50
+ "kind": "insert",
51
+ "op": "insert_row",
52
+ "path": "datanotes/main/public/film/Metropolis.md",
53
+ "row_id": "01a12efb-9f12-7afd-8545-4fec074ff1fb",
54
+ "changes": {
55
+ "id": { "after": "01a12efb-9f12-7afd-8545-4fec074ff1fb" },
56
+ "title": { "after": "Metropolis" },
57
+ "status": { "after": "to watch" }
58
+ },
59
+ "source": "engine",
60
+ "row": {
61
+ "id": "01a12efb-9f12-7afd-8545-4fec074ff1fb",
62
+ "title": "Metropolis",
63
+ "status": "to watch"
64
+ },
65
+ "actor": { "session": "history", "harness": "docs-example", "auth": "owner" },
66
+ "migration_id": "20261012T072004-insert_row-insert-into-film-2"
67
+ },
68
+ {
69
+ "seq": 5,
70
+ "at": "2026-10-12T07:20:04.637Z",
71
+ "table": "main.public.film",
72
+ "kind": "update",
73
+ "op": "update_row",
74
+ "path": "datanotes/main/public/film/Nosferatu.md",
75
+ "row_id": "01a12efb-9f0c-7795-aacb-6e9601757026",
76
+ "changes": {
77
+ "status": { "before": "to watch", "after": "watched" },
78
+ "rating": { "after": 4 },
79
+ "watched_on": { "after": "2026-10-03" }
80
+ },
81
+ "source": "engine",
82
+ "row": {
83
+ "id": "01a12efb-9f0c-7795-aacb-6e9601757026",
84
+ "title": "Nosferatu",
85
+ "status": "watched",
86
+ "rating": 4,
87
+ "watched_on": "2026-10-03"
88
+ },
89
+ "actor": { "session": "history", "harness": "docs-example", "auth": "owner" },
90
+ "migration_id": "20261012T072004-update_row-update-film"
91
+ }
92
+ ],
93
+ "last": 5,
94
+ "head": 13
95
+ }
96
+ ```
97
+
98
+ **`insert_row`**
99
+
100
+ ```json
101
+ { "table": "film", "values": { "title": "His Girl Friday" } }
102
+ ```
103
+
104
+ With `since`, the answer holds only the newer changes:
105
+
106
+ **`list_changes`**
107
+
108
+ ```json
109
+ { "since": 13 }
110
+ ```
111
+
112
+ Answer:
113
+
114
+ ```json
115
+ {
116
+ "events": [
117
+ {
118
+ "seq": 14,
119
+ "at": "2026-10-12T07:20:09.225Z",
120
+ "table": "main.public.film",
121
+ "kind": "insert",
122
+ "op": "insert_row",
123
+ "path": "datanotes/main/public/film/His Girl Friday.md",
124
+ "row_id": "01a12efb-b104-7743-a725-fa938af1a57b",
125
+ "changes": {
126
+ "id": { "after": "01a12efb-b104-7743-a725-fa938af1a57b" },
127
+ "title": { "after": "His Girl Friday" },
128
+ "status": { "after": "to watch" }
129
+ },
130
+ "source": "engine",
131
+ "row": {
132
+ "id": "01a12efb-b104-7743-a725-fa938af1a57b",
133
+ "title": "His Girl Friday",
134
+ "status": "to watch"
135
+ },
136
+ "actor": { "session": "history", "harness": "docs-example", "auth": "owner" },
137
+ "migration_id": "20261012T072009-insert_row-insert-into-film"
138
+ }
139
+ ],
140
+ "last": 14,
141
+ "head": 14
142
+ }
143
+ ```
144
+
145
+ ## Push changes to a web address with webhooks
146
+
147
+ `create_webhook` makes the engine send row changes in batches to a web address (URL), as `POST` requests. The engine signs each batch with a hash-based message authentication code (HMAC) of the body, in `X-Datanotes-Signature`. It retries until the receiver answers with a 2xx status. Only this answer returns the `secret`: give it to the receiver so it can check the signature.
148
+
149
+ **`create_webhook`**
150
+
151
+ ```json
152
+ { "url": "http://127.0.0.1:42525/datanotes-hook", "tables": ["film"] }
153
+ ```
154
+
155
+ Answer:
156
+
157
+ ```json
158
+ {
159
+ "id": "wh_c4b48fd54bb5",
160
+ "url": "http://127.0.0.1:42525/datanotes-hook",
161
+ "tables": ["film"],
162
+ "enabled": true,
163
+ "cursor": 14,
164
+ "created_at": "2026-10-12T07:20:09.229Z",
165
+ "failures": 0,
166
+ "status": "ok",
167
+ "secret": "<shown once>"
168
+ }
169
+ ```
170
+
171
+ **`update_row`**
172
+
173
+ ```json
174
+ {
175
+ "table": "film",
176
+ "path": "datanotes/main/public/film/His Girl Friday.md",
177
+ "set": { "status": "watched" }
178
+ }
179
+ ```
180
+
181
+ The receiver got this request:
182
+
183
+ ```json
184
+ {
185
+ "headers": {
186
+ "x-datanotes-signature": "sha256=399f785d39d4a9f7d6aead97b64aad81a936cfb1fdeaf69843407a2549d6829d"
187
+ },
188
+ "body": {
189
+ "webhook": "wh_c4b48fd54bb5",
190
+ "events": [
191
+ {
192
+ "seq": 15,
193
+ "at": "2026-10-12T07:20:09.237Z",
194
+ "table": "main.public.film",
195
+ "kind": "update",
196
+ "op": "update_row",
197
+ "path": "datanotes/main/public/film/His Girl Friday.md",
198
+ "row_id": "01a12efb-b104-7743-a725-fa938af1a57b",
199
+ "changes": { "status": { "before": "to watch", "after": "watched" } },
200
+ "source": "engine",
201
+ "row": {
202
+ "id": "01a12efb-b104-7743-a725-fa938af1a57b",
203
+ "title": "His Girl Friday",
204
+ "status": "watched"
205
+ },
206
+ "actor": { "session": "history", "harness": "docs-example", "auth": "owner" },
207
+ "migration_id": "20261012T072009-update_row-update-film"
208
+ }
209
+ ],
210
+ "last": 15
211
+ }
212
+ }
213
+ ```
214
+
215
+ **`list_webhooks`** (no arguments)
216
+
217
+ Answer:
218
+
219
+ ```json
220
+ {
221
+ "webhooks": [
222
+ {
223
+ "id": "wh_c4b48fd54bb5",
224
+ "url": "http://127.0.0.1:42525/datanotes-hook",
225
+ "tables": ["film"],
226
+ "enabled": true,
227
+ "cursor": 15,
228
+ "created_at": "2026-10-12T07:20:09.229Z",
229
+ "last_delivery": "2026-10-12T07:20:09.241Z",
230
+ "failures": 0,
231
+ "status": "ok"
232
+ }
233
+ ]
234
+ }
235
+ ```
236
+
237
+ **`delete_webhook`**
238
+
239
+ ```json
240
+ { "id": "wh_c4b48fd54bb5" }
241
+ ```
242
+
243
+ Answer:
244
+
245
+ ```json
246
+ { "deleted": true }
247
+ ```