datanotes 3.0.0 → 3.2.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 (73) hide show
  1. package/README.md +1 -5
  2. package/dist/api/client.d.cts +37 -4
  3. package/dist/api/client.d.ts +37 -4
  4. package/dist/api/index.cjs +563 -60
  5. package/dist/api/index.d.cts +3 -2
  6. package/dist/api/index.d.ts +3 -2
  7. package/dist/api/index.js +563 -60
  8. package/dist/api/mcp.cjs +181 -11
  9. package/dist/api/mcp.js +181 -11
  10. package/dist/api/otlp.d.cts +63 -15
  11. package/dist/api/otlp.d.ts +63 -15
  12. package/dist/api/redact.d.cts +25 -0
  13. package/dist/api/redact.d.ts +25 -0
  14. package/dist/api/tokens.d.cts +5 -0
  15. package/dist/api/tokens.d.ts +5 -0
  16. package/dist/api/tracing.d.cts +107 -0
  17. package/dist/api/tracing.d.ts +107 -0
  18. package/dist/datanotes.cjs +9934 -2656
  19. package/dist/docs/administration/limits.md +96 -12
  20. package/dist/docs/administration/run-the-service.md +18 -0
  21. package/dist/docs/api/operations/add_column.md +1 -0
  22. package/dist/docs/api/operations/add_constraint.md +2 -0
  23. package/dist/docs/api/operations/alter_column.md +1 -1
  24. package/dist/docs/api/operations/alter_table.md +3 -2
  25. package/dist/docs/api/operations/check_table.md +1 -1
  26. package/dist/docs/api/operations/create_table.md +3 -1
  27. package/dist/docs/api/operations/describe_table.md +1 -1
  28. package/dist/docs/api/operations/graph_neighbors.md +4 -2
  29. package/dist/docs/api/operations/graph_path.md +3 -3
  30. package/dist/docs/api/operations/graph_traverse.md +2 -2
  31. package/dist/docs/api/operations/insert_row.md +2 -2
  32. package/dist/docs/api/operations/record_tool_call.md +1 -0
  33. package/dist/docs/api/operations/tags/queries.md +2 -2
  34. package/dist/docs/api/operations/tags/rows-dml.md +1 -1
  35. package/dist/docs/api.md +4 -4
  36. package/dist/docs/develop/concurrency.md +36 -1
  37. package/dist/docs/develop/http-api.md +11 -1
  38. package/dist/docs/develop/telemetry.md +224 -25
  39. package/dist/docs/guides/data-quality/check-constraints.md +14 -13
  40. package/dist/docs/guides/data-quality/hand-edits.md +10 -10
  41. package/dist/docs/guides/database-objects/bases.md +11 -11
  42. package/dist/docs/guides/database-objects/catalogs-and-schemas.md +16 -10
  43. package/dist/docs/guides/database-objects/columns.md +38 -38
  44. package/dist/docs/guides/database-objects/relations.md +173 -36
  45. package/dist/docs/guides/database-objects/tables.md +90 -109
  46. package/dist/docs/guides/database-objects/views.md +44 -44
  47. package/dist/docs/guides/history/back-up.md +12 -4
  48. package/dist/docs/guides/history/change-data-feed.md +34 -34
  49. package/dist/docs/guides/history/journal-and-undo.md +12 -12
  50. package/dist/docs/guides/history/restore.md +20 -20
  51. package/dist/docs/guides/history/table-history.md +31 -31
  52. package/dist/docs/guides/history/vacuum.md +1 -10
  53. package/dist/docs/guides/read-and-write/automatic-links.md +15 -15
  54. package/dist/docs/guides/read-and-write/import-notes.md +21 -21
  55. package/dist/docs/guides/read-and-write/joins-and-aggregates.md +7 -7
  56. package/dist/docs/guides/read-and-write/read-and-filter.md +10 -10
  57. package/dist/docs/guides/read-and-write/write-rows.md +10 -10
  58. package/dist/docs/guides/search/link-graph.md +95 -0
  59. package/dist/docs/guides/search/text.md +1 -1
  60. package/dist/docs/guides/security/who-did-what.md +2 -0
  61. package/dist/docs/reference/cli.md +10 -3
  62. package/dist/docs/reference/compatibility.md +33 -13
  63. package/dist/docs/reference/errors.md +1 -1
  64. package/dist/docs/reference/operations/auxiliary.md +2 -2
  65. package/dist/docs/reference/operations/queries.md +3 -3
  66. package/dist/docs/reference/operations/rows.md +1 -1
  67. package/dist/docs/reference/operations/schema.md +15 -11
  68. package/dist/docs/reference/operations.md +39 -39
  69. package/dist/docs/reference/settings.md +37 -25
  70. package/dist/docs/release-notes.md +145 -0
  71. package/dist/docs/start/how-it-works.md +35 -3
  72. package/dist/docs/start/quick-start.md +2 -0
  73. package/package.json +3 -2
@@ -6,21 +6,86 @@ The engine keeps every row in a Markdown note, so its speed depends on the numbe
6
6
 
7
7
  ## Measured times
8
8
 
9
- We measured the engine on databases of 10,000 and 50,000 notes. On Windows 11, Microsoft Defender scanned the files. On Linux, the engine ran in a container with the notes on a Linux volume.
9
+ We measured Datanotes 3.1.0 on synthetic databases of 10,000 and 50,000 notes, made with `bench/scale/`. One machine ran every test:
10
10
 
11
- | Measure | 10,000 notes | 50,000 notes |
12
- | --------------------------------------- | ------------ | ------------ |
13
- | Start, Windows 11 with Defender | 15 to 18 s | 63 to 115 s |
14
- | Start, Linux container | 4 to 5 s | 22 s |
15
- | `select_rows` with `where` | 30 to 35 ms | 178 ms |
16
- | `insert_row` | 91 to 128 ms | 414 ms |
17
- | Memory (resident set size) after writes | Not measured | Up to 2 GB |
11
+ * Windows 11 Pro on an Intel Core i7-13700HX, with 32 GB of memory and an NVMe SSD
12
+ * Node.js 24.21
13
+ * Microsoft Defender scanning every file, with no exclusion
14
+ * Other programs running at the same time
18
15
 
19
- These numbers set the size the engine handles well:
16
+ The times are medians. The first start reads every note and builds the [vault index](#start-faster-with-the-vault-index). A restart reads only the notes that changed.
20
17
 
21
- * Up to about 10,000 notes, it starts within 20 seconds and answers within a few hundred milliseconds
22
- * From 10,000 to 20,000 notes, start it before your agents and keep it running
23
- * Above about 20,000 notes, run it as a service on Linux, and give it 2 GB of memory
18
+ | Measure | 10,000 notes | 50,000 notes |
19
+ | ----------------------------------------------------- | ------------ | ------------ |
20
+ | First start | 2.7 s | 15 s |
21
+ | Restart | 0.7 s | 2.0 to 2.3 s |
22
+ | First `select_rows` after a restart | 0.15 s | 1.1 to 2 s |
23
+ | Background work after a restart | 0.6 s | 4 to 6 s |
24
+ | Memory (resident set size) after a restart | 130 MB | 330 MB |
25
+ | Memory (resident set size) after a series of searches | 150 MB | 460 MB |
26
+ | Vault index on disk | 21 MB | 104 MB |
27
+
28
+ We measured the memory once the engine was idle, after a garbage collection, with the read cache off.
29
+
30
+ At 50,000 notes, the operations took these times:
31
+
32
+ | Operation | Median time |
33
+ | ---------------------------------------------- | ----------- |
34
+ | `insert_row` | 37 ms |
35
+ | `update_row` | 47 ms |
36
+ | `select_rows` on a table of 117 rows | 2.8 ms |
37
+ | `select_rows` of 5 notes by path | 0.3 ms |
38
+ | `describe_database` | 24 ms |
39
+ | `graph_neighbors` right after a write | 2.5 ms |
40
+ | `search_notes`, two words found in 1,213 notes | 14 ms |
41
+ | `search_notes`, a person’s name | 7 ms |
42
+ | `search_notes`, a word found in 15,905 notes | 140 ms |
43
+
44
+ The first `select_rows` waits until the engine has compared the vault with its index. These numbers set the size the engine handles well:
45
+
46
+ * Up to about 10,000 notes, it starts within 3 seconds and answers within a few hundred milliseconds
47
+ * From 10,000 to 50,000 notes, start it before your agents and keep it running
48
+ * Above about 20,000 notes, run it as a service, and give it 2 GB of memory
49
+
50
+ ## Start faster with the vault index
51
+
52
+ The engine keeps a vault index in its store, in `index/vault.sqlite`. It records what the engine read from each note: its properties, tags, links and text. It also indexes the words of each note, so `search_notes` reads only the notes that can match. It keeps each text compressed. At the next start, the engine reads only the notes that changed since.
53
+
54
+ * A note counts as unchanged while its size, its modification time and its change time stay the same.
55
+ * The change time moves with every write, also one that restores the old modification time, as `cp -p` and `rsync -t` do.
56
+ * A note written within 2 seconds of being recorded is read again. Some file systems keep times to the second only.
57
+ * A note you moved while the engine was stopped is found by its file id, or by its content.
58
+
59
+ The index is a cache, never the source of truth. Keep these points in mind:
60
+
61
+ * You can delete it at any time. The next start builds it again.
62
+ * The store backup leaves it out, and so does Time Machine on macOS.
63
+ * It needs Node.js 22.13 or later. On an older Node.js, every start reads every note.
64
+ * After each start, the engine checks the index in the background, one table at a time.
65
+ * When SQLite or that check finds damage, the engine stops using the index until the next start rebuilds it.
66
+ * A query with only one- or two-letter words reads every note.
67
+
68
+ To turn it off, set `vaultIndexEnabled` to `false` in the [engine’s settings](https://datanotes.dev/administration/configuration/). The engine then deletes the index, and every start reads every note.
69
+
70
+ ## Run a large database on Windows
71
+
72
+ Microsoft Defender scans every file the engine opens. On a large vault, these scans take most of the start. The engine marks its store as not content indexed, so Windows Search skips it. It never changes your antivirus settings.
73
+
74
+ To speed up the engine, choose one of these options:
75
+
76
+ * Exclude the store folder from real-time scanning. Run this in a PowerShell window opened as administrator:
77
+
78
+ ```powershell
79
+ Add-MpPreference -ExclusionPath "$env:LOCALAPPDATA\Datanotes"
80
+ ```
81
+
82
+ * Exclude the vault folder too, but only when its notes come from sources you trust.
83
+
84
+ * Put the vault and the data home on a Dev Drive. Its performance mode defers the scans.
85
+
86
+ Never exclude the `node.exe` process: that exempts every Node.js program on the machine.
87
+
88
+ Keep the store out of OneDrive, Dropbox and iCloud folders. Sync clients and scanners lock files, and the engine then retries its renames. On macOS, the engine writes a `.metadata_never_index` file in its store, so Spotlight skips it. It also keeps the vault index out of Time Machine, since the engine can rebuild it. Time Machine still backs up the rest of the store.
24
89
 
25
90
  ## Start the engine before the agent
26
91
 
@@ -67,3 +132,22 @@ volumes:
67
132
  ```
68
133
 
69
134
  On a Linux host, a bind mount delivers file events: set `DATANOTES_WATCH=events` there too. [Run in Docker](https://datanotes.dev/administration/docker/) explains the container.
135
+
136
+ ## Check the engine’s indexes
137
+
138
+ The engine keeps indexes of the vault in memory, so a read does not go through every note. It learns each change from the vault’s file events: its own writes, your edits, renames and deletes. It never trusts them over the notes, which stay the source of truth.
139
+
140
+ When an answer looks wrong after many changes outside the engine, check the indexes. Start the engine with `DATANOTES_INDEX_CHECK=1`:
141
+
142
+ ```bash
143
+ DATANOTES_INDEX_CHECK=1 \
144
+ npx -y datanotes serve --database your_database_folder
145
+ ```
146
+
147
+ After every operation, the engine then compares its indexes with a full read of the vault:
148
+
149
+ * A difference shows as an error in the engine’s log, with the notes it concerns.
150
+ * The engine then reads the vault again, so the next operations answer right.
151
+ * Each comparison reads every note, so keep the check for debugging, never in daily use.
152
+
153
+ Every 10 minutes, once no write ran for a minute, the engine also compares the transaction log’s view of the rows with the vault. A difference shows as a warning, and the engine logs the changes it missed.
@@ -22,6 +22,24 @@ The first start creates the engine’s store and its settings file, `settings.js
22
22
  npx -y datanotes store path --database your_database_folder
23
23
  ```
24
24
 
25
+ Add `--json` to get the answer as an object:
26
+
27
+ | Field | Meaning |
28
+ | ----------------- | ----------------------------------------------------------------------------- |
29
+ | `vault` | The folder you gave, as an absolute path |
30
+ | `store` | The store folder |
31
+ | `database_id` | The database’s id |
32
+ | `database_folder` | The database folder, relative to the folder you gave; empty with `--database` |
33
+ | `database_path` | The database folder on disk, with `metastore.yaml` at its root |
34
+
35
+ Every field but `vault` is `null` when the engine cannot tell where the store would be. That happens only when all three hold:
36
+
37
+ * You pass no `--store`, and `DATANOTES_STORE` is not set.
38
+ * This machine never ran the engine on the folder.
39
+ * `metastore.yaml` has no id yet.
40
+
41
+ With `--store`, only `database_id` can be `null`: neither the store nor `metastore.yaml` records the id.
42
+
25
43
  To run the engine in a container, follow [Run in Docker](https://datanotes.dev/administration/docker/).
26
44
 
27
45
  ## Where the engine keeps its store
@@ -13,6 +13,7 @@ Column names are permanent identifiers; only `rename_column` changes them.
13
13
  Enum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", …}`): describe_table shows them and a refused value lists them.
14
14
  Generated link columns: `generated: "link({occurred_at:YYYY-MM-DD})"` with `ref` = the days table links each row to the row of `ref` named by the template (its folder read from the schema; the target may not exist yet).
15
15
  Filtered link targets (link columns): `ref_where` (a `where` every target must match) and `ref_by` (a column of the row picks the target level: `{"column": "kind", "levels": ["area", "project", "workstream"]}` or `{"column": "kind", "map": {"area": null, "project": ["area"]}}`; null = top level, no link; `target_column` when the target names it differently: an enum target column must have every value the map allows, a string one takes any value). A link to no row of the table, or to the row itself, is refused too. Existing rows that break them refuse the change; `list_link_targets` lists the valid targets of a row.
16
+ Acyclic links: `acyclic: true` on a link (or list of links) to the table itself refuses every write that closes a cycle, on either side of an `inverse`; refused while existing rows form one (they are listed).
16
17
 
17
18
  The schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).
18
19
 
@@ -18,6 +18,7 @@ A write that changes a linked row re-checks the rows that link to it and is refu
18
18
  it breaks them, naming the changed row (in a batch, re-link them first: each step is checked on the vault
19
19
  the earlier ones left); the inverse links a write adds, undo_migration and restore_table are checked too.
20
20
  rename_column / rename_value follow into CHECKs; drop_column of a column a CHECK reads is refused.
21
+ `hint`: what to do when a row breaks the rule, in your words; a refused write puts it first in its hint, and each violation of the CHECK carries it.
21
22
 
22
23
  The schema changes only through operations like this one: never edit metastore.yaml by hand (no validation, no undo).
23
24
 
@@ -36,6 +37,7 @@ The schema changes only through operations like this one: never edit metastore.y
36
37
  | `table` | `string` | yes | | |
37
38
  | `name` | `string` | yes | | |
38
39
  | `check` | `object` | yes | | |
40
+ | `hint` | `string \| null` | no | `null` | What a refused write tells the agent when a row breaks this CHECK: how to fix the row. It comes first in the refusal's hint; describe_table lists it in constraint_hints. |
39
41
  | `dry_run` | `boolean` | no | `false` | |
40
42
 
41
43
  ## Responses
@@ -3,7 +3,7 @@
3
3
  `POST /datanotes/operations/alter_column/` (operation `alter_column`)
4
4
 
5
5
  Change a column's attributes (type, min/max/step, values, required, default, label,
6
- aliases, description, note, `ref_where`, `ref_by` (see list_link_targets); `null` removes an attribute, except
6
+ aliases, description, note, `ref_where`, `ref_by` (see list_link_targets), `acyclic` (refused while the rows already form a cycle: they are listed); `null` removes an attribute, except
7
7
  `description`, which changes but never goes (null or "" is refused); `dense` true/false). Refused with
8
8
  the list of violating rows when existing values do not satisfy the new definition. A
9
9
  different scale means a different column: add a new one instead of rescaling. To rename one
@@ -5,7 +5,7 @@
5
5
  Alter a table: move it (`catalog`, `schema`), rename it (`name`), change
6
6
  `partition` / `filename` patterns, `description` (changes, never goes: "" is refused), `aliases`,
7
7
  `exclude` folders, `row_template`
8
- ("" removes it), `history` (true | false | "default"), `autolink_target`
8
+ ("" removes it), `instructions` (how to fill a row, for agents; "" removes them), `history` (true | false | "default"), `autolink_target`
9
9
  (true | false | "default": may automatic links link its rows? default: yes, unless its rows are named by
10
10
  a date or a time) or `tags` (key/value
11
11
  labels merged into the table's, e.g. `{"domain": "health"}`; a `null` value removes that tag;
@@ -13,7 +13,7 @@ labels merged into the table's, e.g. `{"domain": "health"}`; a `null` value remo
13
13
  `archive` (folder name inside the table folder) or `fields` (untyped fields, name → default; a
14
14
  `null` value removes one). Rows, archive and transaction log follow; links are updated; renamed
15
15
  rows keep their old name in `aliases`. A change of metadata only (tags, description, aliases,
16
- history, autolink_target, row_template, fields) rewrites the table entry alone: no row is moved or rewritten. Refused
16
+ history, autolink_target, row_template, instructions, fields) rewrites the table entry alone: no row is moved or rewritten. Refused
17
17
  when a row cannot fill a pattern. Renaming or moving the table is refused while a CHECK that follows
18
18
  links, or a `ref_where` / `ref_by`, of another table reads it through a link (link targets are not
19
19
  rewritten): the conflicts name them. Use `dry_run` to see `planned_moves`; undo with undo_migration.
@@ -44,6 +44,7 @@ The schema changes only through operations like this one: never edit metastore.y
44
44
  | `autolink_target` | `boolean \| "default" \| null` | no | `null` | May automatic links link the table's rows? true, false, or "default" (yes, unless its rows are named by a date or a time). |
45
45
  | `exclude` | `string[] \| null` | no | `null` | |
46
46
  | `row_template` | `string \| null` | no | `null` | Markdown body of new rows; "" removes it. |
47
+ | `instructions` | `string \| null` | no | `null` | How to fill a row, for agents (Markdown); "" removes them. |
47
48
  | `tags` | `object \| null` | no | `null` | Tags to add or change (strings); a null value removes that tag. The other tags are kept. |
48
49
  | `clear_tags` | `boolean` | no | `false` | Remove every tag first (with `tags`: the table keeps exactly those). |
49
50
  | `archive` | `string \| null` | no | `null` | Archive folder: a folder name inside the table folder (e.g. "_archive"); "" = the database default (`archive_folder`). Archived rows follow. |
@@ -3,7 +3,7 @@
3
3
  `POST /datanotes/operations/check_table/` (operation `check_table`)
4
4
 
5
5
  Validate every row against the table (required, types, ranges, enums, links, unknown
6
- columns, CHECK constraints, those that follow links included, filtered link targets (ref_where / ref_by), stale generated/identity values, missing inverse links, front matter that cannot be read: code `unreadable`, with no column, for a note whose `table` line names the table but whose YAML is broken, which every other read leaves out). A CHECK violation has code `check` and the CHECK's name as `constraint` (every other code names its `column`), as in the `violations` of list_changes. Use it to find rows edited by hand that break the schema. Without `table`, every table is checked: each row then names its `table`, `tables_checked` counts the tables checked, and a table that cannot be checked is listed in `errors` ({table, error}) while the others are. The rows with violations are paged: `limit` (default 500, at most 5000) from `offset`; `total` counts them all. (Hand edits are also checked as they happen: see `violations` in list_changes.)
6
+ columns, CHECK constraints, those that follow links included, filtered link targets (ref_where / ref_by), stale generated/identity values, missing inverse links, cycles of hierarchy and acyclic columns, front matter that cannot be read: code `unreadable`, with no column, for a note whose `table` line names the table but whose YAML is broken, which every other read leaves out). A CHECK violation has code `check` and the CHECK's name as `constraint` (every other code names its `column`), as in the `violations` of list_changes. Use it to find rows edited by hand that break the schema. Without `table`, every table is checked: each row then names its `table`, `tables_checked` counts the tables checked, and a table that cannot be checked is listed in `errors` ({table, error}) while the others are. The rows with violations are paged: `limit` (default 500, at most 5000) from `offset`; `total` counts them all. (Hand edits are also checked as they happen: see `violations` in list_changes.)
7
7
 
8
8
  ## Request body
9
9
 
@@ -16,7 +16,7 @@ digits, `_` (no dots: pass the catalog and the schema as their own arguments).
16
16
  order: filters/avg use the position), ref (link target, full `catalog.schema.table` or "self"),
17
17
  unique, label, description, aliases, `generated` (e.g. `"date(occurred_at)"`, `"{name} {surname}"`),
18
18
  `identity` ("filename" | "created"), `vocabulary` (allowed values = names of another table's rows),
19
- `hierarchy: true` (one parent link to its own table), `dense` (written on every row),
19
+ `hierarchy: true` (one parent link to its own table), `acyclic: true` (links to rows of its own table that never loop back, such as tasks blocked by tasks; with `inverse`, the rule holds on both sides), `dense` (written on every row),
20
20
  `inverse` on a link column: the column of the `ref` table that links back (`friends` ↔
21
21
  `friends`, `children` ↔ `parents`, `partner` ↔ `partner`); writes keep both sides in step.
22
22
  - `time_column`: rows named by that date/datetime; otherwise `filename` (e.g. `"{name}"`).
@@ -25,6 +25,7 @@ digits, `_` (no dots: pass the catalog and the schema as their own arguments).
25
25
  database's setting), `autolink_target` (true / false: may automatic links link its rows? default:
26
26
  yes, unless its rows are named by a date or a time, as with `time_column`).
27
27
  - `row_template`: markdown body of new rows. Shared columns come from bases: `set_table_bases`.
28
+ - `instructions`: how to fill a row, for agents (Markdown); describe_table returns them.
28
29
  - `tags`: key/value labels of the table, strings (`{"domain": "health"}`); list_tables and
29
30
  describe_table return them, alter_table changes them.
30
31
  Enum columns: `values` and, for each, its meaning in `value_descriptions` (`{"stated": "the user said it", …}`): describe_table shows them and a refused value lists them.
@@ -59,6 +60,7 @@ The schema changes only through operations like this one: never edit metastore.y
59
60
  | `history` | `boolean \| null` | no | `null` | Keep the table's versions (time travel, restore); default: the database's setting. |
60
61
  | `autolink_target` | `boolean \| null` | no | `null` | May automatic links link the table's rows? Default: yes, unless its rows are named by a date or a time. |
61
62
  | `row_template` | `string \| null` | no | `null` | Markdown body of new rows; {column} placeholders are filled from the row. |
63
+ | `instructions` | `string \| null` | no | `null` | How to fill a row, for agents (Markdown): which columns first, what to ask the user. describe_table returns them. |
62
64
  | `fields` | `object \| null` | no | `null` | Untyped fields of the table (name → default), kept on every row; a field cannot share a name with a column. |
63
65
  | `tags` | `object \| null` | no | `null` | Key/value labels of the table (strings), e.g. {"domain": "health"}. |
64
66
  | `dry_run` | `boolean` | no | `false` | |
@@ -6,7 +6,7 @@ Describe a table (`catalog.schema.table` or a shorter unambiguous name): `qualif
6
6
  `catalog`, `schema`, `description`, `aliases`, `layout` (`folder`, `row_folder`: the folder pattern
7
7
  of new rows, `archive`), `options` (`partition`, `filename`, `archive`, `exclude`), typed `columns`
8
8
  with label/aliases/description and the dense flag, `untyped_fields`, `row_count`, and when set:
9
- `tags`, `bases` (the bases it inherits from, in order), `constraints` (its CHECKs, name → where), `row_template`, `history`,
9
+ `tags`, `bases` (the bases it inherits from, in order), `constraints` (its CHECKs, name → where), `constraint_hints` (their own hints, name → hint), `instructions` (how to fill a row: follow them before you write one), `row_template`, `history`,
10
10
  `autolink_target`, `fields` (their values).
11
11
 
12
12
  Before adding a column (e.g. a new metric), read `columns` and match your concept against
@@ -5,8 +5,10 @@
5
5
  Direct neighbours of notes: what they link to (`out`) and what links to them (`in`,
6
6
  backlinks), with counts per edge label. `direction`: out, in or both (default).
7
7
 
8
- `from_nodes` selects the notes: `{"paths": [...]}` and/or `{"table": ..., "where": {...}}`.
9
- Edges are labelled with their link column (`people`, `event`, …) or `link` for other links.
8
+ `from_nodes` selects the notes: `{"paths": [...]}`, `{"table": ..., "where": {...}}` and/or
9
+ `{"tables": [...]}`: the rows of several tables, by name or pattern (`["*"]` every table, `["personal.*"]` a
10
+ catalog's). The whole vault's graph in one call: `{"tables": ["*"]}` with direction `out` (at most 5000
11
+ start notes). Edges are labelled with their link column (`people`, `event`, …) or `link` for other links.
10
12
 
11
13
  ## Request body
12
14
 
@@ -1,9 +1,9 @@
1
- # Shortest chain of links between two sets of notes (each `{"paths": [...]}` and/or
1
+ # Shortest chain of links between two sets of notes (each `{"paths": [...]}`, `{"table", "where"}`
2
2
 
3
3
  `POST /datanotes/operations/graph_path/` (operation `graph_path`)
4
4
 
5
- Shortest chain of links between two sets of notes (each `{"paths": [...]}` and/or
6
- `{"table", "where"}`): `found`, `length` and `path`: the steps (`from`, `label`, `direction`, `to`).
5
+ Shortest chain of links between two sets of notes (each `{"paths": [...]}`, `{"table", "where"}`
6
+ and/or `{"tables": [...]}`): `found`, `length` and `path`: the steps (`from`, `label`, `direction`, `to`).
7
7
  `direction` both by default; `max_depth` default 6 (max 10).
8
8
 
9
9
  ## Request body
@@ -1,8 +1,8 @@
1
- # Breadth-first walk over links from `from_nodes` (`{"paths": [...]}` and/or `{"table", "where"}`).
1
+ # Breadth-first walk over links from `from_nodes` (`{"paths": [...]}`, `{"table", "where"}` and/or `{"tables": [...]}`).
2
2
 
3
3
  `POST /datanotes/operations/graph_traverse/` (operation `graph_traverse`)
4
4
 
5
- Breadth-first walk over links from `from_nodes` (`{"paths": [...]}` and/or `{"table", "where"}`).
5
+ Breadth-first walk over links from `from_nodes` (`{"paths": [...]}`, `{"table", "where"}` and/or `{"tables": [...]}`).
6
6
 
7
7
  - `direction`: out (default, follow links), in (follow backlinks) or both.
8
8
  - `edges`: edge labels to follow (link columns such as `people`, `event`, or `link`).
@@ -1,8 +1,8 @@
1
- # Insert a row (new note). Values are validated against the columns; the note path comes from
1
+ # Insert a row (new note). When describe_table gives the table's `instructions`, fill the row as they say. Values are validated against the columns; the note path comes from
2
2
 
3
3
  `POST /datanotes/operations/insert_row/` (operation `insert_row`)
4
4
 
5
- Insert a row (new note). Values are validated against the columns; the note path comes from
5
+ Insert a row (new note). When describe_table gives the table's `instructions`, fill the row as they say. Values are validated against the columns; the note path comes from
6
6
  the table's `path`/`filename` patterns (e.g. an event logged today about last night lands in
7
7
  last night's day folder when you set `occurred_at` to last night). `body` is the note text.
8
8
  Generated / identity columns are computed (do not pass them); CHECK constraints are enforced.
@@ -23,6 +23,7 @@
23
23
  | `session` | `string` | no | | |
24
24
  | `result_chars` | `number` | no | | |
25
25
  | `result_tokens` | `number` | no | | |
26
+ | `summary` | `object` | no | | The result's summary, when `value` is sent already cut: the engine records it (the answer says `takes_summary`). |
26
27
 
27
28
  ## Responses
28
29
 
@@ -11,5 +11,5 @@ Operations that read rows and notes: select_rows, link targets, text and semanti
11
11
  - [The rows a link column may point to, for one row: what an agent or a picker offers before writing](https://datanotes.dev/api/operations/list_link_targets/): `POST /datanotes/operations/list_link_targets/`
12
12
  - [Find notes by meaning, not exact words: `{query, results: [{path, score, heading, snippet,](https://datanotes.dev/api/operations/semantic_search/): `POST /datanotes/operations/semantic_search/`
13
13
  - [Direct neighbours of notes: what they link to (`out`) and what links to them (`in`,](https://datanotes.dev/api/operations/graph_neighbors/): `POST /datanotes/operations/graph_neighbors/`
14
- - [Breadth-first walk over links from `from_nodes` (`{"paths": [...]}` and/or `{"table", "where"}`).](https://datanotes.dev/api/operations/graph_traverse/): `POST /datanotes/operations/graph_traverse/`
15
- - [Shortest chain of links between two sets of notes (each `{"paths": [...]}` and/or](https://datanotes.dev/api/operations/graph_path/): `POST /datanotes/operations/graph_path/`
14
+ - [Breadth-first walk over links from `from_nodes` (`{"paths": [...]}`, `{"table", "where"}` and/or `{"tables": [...]}`).](https://datanotes.dev/api/operations/graph_traverse/): `POST /datanotes/operations/graph_traverse/`
15
+ - [Shortest chain of links between two sets of notes (each `{"paths": [...]}`, `{"table", "where"}`](https://datanotes.dev/api/operations/graph_path/): `POST /datanotes/operations/graph_path/`
@@ -4,7 +4,7 @@ Operations that write rows: insert, update, merge, delete, move, adopt and batch
4
4
 
5
5
  ## Operations
6
6
 
7
- - [Insert a row (new note). Values are validated against the columns; the note path comes from](https://datanotes.dev/api/operations/insert_row/): `POST /datanotes/operations/insert_row/`
7
+ - [Insert a row (new note). When describe_table gives the table's `instructions`, fill the row as they say. Values are validated against the columns; the note path comes from](https://datanotes.dev/api/operations/insert_row/): `POST /datanotes/operations/insert_row/`
8
8
  - [Update every row matching `where` (required: `{}` is every row). `where`: keys AND-ed; values are equality or operators](https://datanotes.dev/api/operations/update_rows/): `POST /datanotes/operations/update_rows/`
9
9
  - [Update exactly ONE row, addressed by its `id` (the system UUID) or its `path`; refused when](https://datanotes.dev/api/operations/update_row/): `POST /datanotes/operations/update_row/`
10
10
  - [Delete rows matching `where`. `mode`: "archive" (default, move to the table's archive](https://datanotes.dev/api/operations/delete_rows/): `POST /datanotes/operations/delete_rows/`
package/dist/docs/api.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Datanotes HTTP API
2
2
 
3
- > OpenAPI 3.1.0 description of the Datanotes 3.0.0 HTTP API: https://datanotes.dev/openapi.json
3
+ > OpenAPI 3.1.0 description of the Datanotes 3.2.0 HTTP API: https://datanotes.dev/openapi.json
4
4
 
5
5
  Database of Markdown notes: tables of notes, rows, queries, history, a change feed and the vault's notes.
6
6
 
@@ -47,7 +47,7 @@ Operations that define the database: tables, columns, constraints, bases and vie
47
47
 
48
48
  Operations that write rows: insert, update, merge, delete, move, adopt and batch.
49
49
 
50
- - [Insert a row (new note). Values are validated against the columns; the note path comes from](https://datanotes.dev/api/operations/insert_row/): `POST /datanotes/operations/insert_row/`
50
+ - [Insert a row (new note). When describe_table gives the table's `instructions`, fill the row as they say. Values are validated against the columns; the note path comes from](https://datanotes.dev/api/operations/insert_row/): `POST /datanotes/operations/insert_row/`
51
51
  - [Update every row matching `where` (required: `{}` is every row). `where`: keys AND-ed; values are equality or operators](https://datanotes.dev/api/operations/update_rows/): `POST /datanotes/operations/update_rows/`
52
52
  - [Update exactly ONE row, addressed by its `id` (the system UUID) or its `path`; refused when](https://datanotes.dev/api/operations/update_row/): `POST /datanotes/operations/update_row/`
53
53
  - [Delete rows matching `where`. `mode`: "archive" (default, move to the table's archive](https://datanotes.dev/api/operations/delete_rows/): `POST /datanotes/operations/delete_rows/`
@@ -69,8 +69,8 @@ Operations that read rows and notes: select_rows, link targets, text and semanti
69
69
  - [The rows a link column may point to, for one row: what an agent or a picker offers before writing](https://datanotes.dev/api/operations/list_link_targets/): `POST /datanotes/operations/list_link_targets/`
70
70
  - [Find notes by meaning, not exact words: `{query, results: [{path, score, heading, snippet,](https://datanotes.dev/api/operations/semantic_search/): `POST /datanotes/operations/semantic_search/`
71
71
  - [Direct neighbours of notes: what they link to (`out`) and what links to them (`in`,](https://datanotes.dev/api/operations/graph_neighbors/): `POST /datanotes/operations/graph_neighbors/`
72
- - [Breadth-first walk over links from `from_nodes` (`{"paths": [...]}` and/or `{"table", "where"}`).](https://datanotes.dev/api/operations/graph_traverse/): `POST /datanotes/operations/graph_traverse/`
73
- - [Shortest chain of links between two sets of notes (each `{"paths": [...]}` and/or](https://datanotes.dev/api/operations/graph_path/): `POST /datanotes/operations/graph_path/`
72
+ - [Breadth-first walk over links from `from_nodes` (`{"paths": [...]}`, `{"table", "where"}` and/or `{"tables": [...]}`).](https://datanotes.dev/api/operations/graph_traverse/): `POST /datanotes/operations/graph_traverse/`
73
+ - [Shortest chain of links between two sets of notes (each `{"paths": [...]}`, `{"table", "where"}`](https://datanotes.dev/api/operations/graph_path/): `POST /datanotes/operations/graph_path/`
74
74
 
75
75
  ### History
76
76
 
@@ -24,7 +24,42 @@ Every client goes through that engine:
24
24
 
25
25
  ## Writes run one at a time
26
26
 
27
- The engine applies writes one at a time, under the lock of its transaction log. Reads don’t wait for that lock: they run in parallel, with each other and with writes.
27
+ The engine runs writes one at a time, in the order they arrive, under the lock of its transaction log. Reads run in parallel with each other.
28
+
29
+ A write that still runs after a minute lets the next write start, so one stuck write can’t hold up the rest. The engine logs a warning that names the operation.
30
+
31
+ ## Reads wait for writes
32
+
33
+ The engine runs on one thread. Under a stream of reads, a write would share that thread with every read that arrives while it runs, and take several times longer. So writes go first:
34
+
35
+ * **A write runs or waits**: a read that arrives waits until no write is left.
36
+ * **A read already under way**: it finishes normally. The write doesn’t wait for it.
37
+ * **A read waits at most `writePriorityMaxWaitMs`**: 100 ms by default. It also starts once 8 writes started after it came. A burst of writes never starves the reads.
38
+ * **A long poll of the change feed**: `list_changes` with `wait_ms` never waits, and never holds a write.
39
+
40
+ What each operation does is unchanged: a read sees every write that ended before it started. Set `writePriority` to `false` in the [settings](https://datanotes.dev/reference/settings/) to let reads and writes start in the order they arrive. The span `engine.schedule.wait` shows the wait in [traces](https://datanotes.dev/develop/telemetry/#the-spans-the-engine-sends).
41
+
42
+ ## Repeated reads come from memory
43
+
44
+ A read you send again with the same arguments, with the same token, often has the same answer. The engine keeps the answers of recent reads in memory, and sends the kept answer while nothing it read has changed:
45
+
46
+ * **A write**: a read of the tables it changed is computed again. Reads of other tables keep their answers.
47
+ * **Undo, a hand edit on disk, a sync tool**: they count as writes. The engine sees them before it answers.
48
+ * **A schema change**: every answer is computed again.
49
+ * **A view**: it depends on the tables its query reads.
50
+
51
+ Some reads depend on less than a table, or on more:
52
+
53
+ * **`read_note`**: it depends on its note. Deleting or renaming a folder also counts.
54
+ * **`list_folder`**: it depends on the notes and folders created, deleted or renamed in its folder. Deleting or renaming any folder also counts.
55
+ * **`list_tables`**: it depends on the schema only. A write to a table keeps its answer.
56
+ * **`describe_database`**: it depends on every table, and on the notes whose properties can’t be read.
57
+ * **A filter through a link column**: a key such as `project.status` follows the link column `project`. The read also depends on the tables the link leads to.
58
+ * **Reads that follow links or search the text**: `expand`, `within`, the graph operations and `search_notes` depend on the whole vault.
59
+
60
+ Reads that compare with the current time (`{now}`) are never kept. A read with `{today}` is kept 15 minutes at most. Reads of history, of the change feed and of `system.information_schema` are never kept either.
61
+
62
+ `readCacheMaxBytes` sets the memory for the answers: 33554432 bytes (32 MiB) by default. The least recently used answers go first. Set it to `0` to turn the cache off.
28
63
 
29
64
  The engine checks each write against the database as the writes before it left it. A write is checked first, then applied. If another write is applied while the engine checks yours, the engine checks yours again on the new state. It tries up to 5 times before it gives up.
30
65
 
@@ -32,7 +32,7 @@ An operation’s body takes the same arguments as the agent’s tool of the same
32
32
  * **[`list_folder`](https://datanotes.dev/reference/operations/queries/#list_folder)**: the notes and folders in a `folder`
33
33
  * **[`search_notes`](https://datanotes.dev/reference/operations/queries/#search_notes)**: the notes that contain a `query`
34
34
 
35
- Every route and field keeps working in every 3.x version: see [Compatibility](https://datanotes.dev/reference/compatibility/).
35
+ Routes and fields follow semantic versioning: see [Compatibility](https://datanotes.dev/reference/compatibility/).
36
36
 
37
37
  ## Record who makes each write
38
38
 
@@ -72,6 +72,16 @@ Several clients can write at the same time. [Concurrent writes](https://datanote
72
72
 
73
73
  The engine binds to `127.0.0.1` unless you start it with `--host`. It answers no Cross-Origin Resource Sharing (CORS) preflight, so web pages can’t call it. To reach it from another device, follow the [security model](https://datanotes.dev/guides/security/security-model/).
74
74
 
75
+ ## Get large answers compressed
76
+
77
+ A client on another machine gets answers of 16 KB or more compressed with gzip. It asks for that with the `Accept-Encoding: gzip` header, as most HTTP clients do. A client on the engine’s machine gets them as they are: compressing would cost the engine more time than it saves.
78
+
79
+ The `httpCompression` [setting](https://datanotes.dev/reference/settings/) changes this:
80
+
81
+ * **`remote`**: clients on other machines only (the default).
82
+ * **`always`**: this machine’s clients too.
83
+ * **`off`**: no compression.
84
+
75
85
  ## Generate a client or explore the routes
76
86
 
77
87
  The [HTTP API reference](https://datanotes.dev/api/) documents every route, generated from the same OpenAPI description, [openapi.json](https://datanotes.dev/openapi.json). From JavaScript, use the [JavaScript client](https://datanotes.dev/develop/client/). Agents use the [Model Context Protocol (MCP) server](https://datanotes.dev/start/connect-agents/).