dirsql 0.3.65 → 0.3.66

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.
@@ -1,273 +0,0 @@
1
- ---
2
- canonical: https://thekevinscott.github.io/dirsql/guide/watching
3
- ---
4
-
5
- # File Watching
6
-
7
- > Online: <https://thekevinscott.github.io/dirsql/guide/watching>
8
-
9
- `dirsql` can monitor the filesystem for changes and emit events when rows are inserted, updated, or deleted. This is useful for building reactive applications that respond to file changes in real time.
10
-
11
- ::: tip From the CLI
12
- The `dirsql` HTTP server streams the same events over [`GET /events`](../cli/http-api.md#get-events) as a [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) stream. Each `data:` payload uses the same JSON schema described in [Event types](#event-types) below. See the [CLI section](../cli/) for the full server setup.
13
- :::
14
-
15
- ## Starting a watch stream
16
-
17
- ::: code-group
18
-
19
- ```python [Python]
20
- from dirsql import DirSQL, Table
21
- import json
22
-
23
- db = DirSQL(
24
- "./my-project",
25
- tables=[
26
- Table(
27
- ddl="CREATE TABLE comments (id TEXT, body TEXT, author TEXT)",
28
- glob="comments/**/*.json",
29
- extract=lambda path: [json.loads(open(path, encoding="utf-8").read())],
30
- ),
31
- ],
32
- )
33
-
34
- async for event in db.watch():
35
- print(f"{event.action} on {event.table}: {event.row}")
36
- ```
37
-
38
- ```rust [Rust]
39
- // `StreamExt` (for `.next()`) comes from the `futures` crate. dirsql only
40
- // depends on `futures` under its `cli` feature, so add it to your project:
41
- //
42
- // cargo add futures
43
- use dirsql::{DirSQL, RowEvent, Table, Value};
44
- use futures::StreamExt;
45
- use std::collections::HashMap;
46
-
47
- // See `row_from_json` in getting-started.md for a reusable helper.
48
- fn row_from_json(raw: &str) -> HashMap<String, Value> {
49
- let v: serde_json::Value = serde_json::from_str(raw).unwrap();
50
- let serde_json::Value::Object(obj) = v else { return HashMap::new() };
51
- obj.into_iter()
52
- .map(|(k, val)| {
53
- let v = match val {
54
- serde_json::Value::String(s) => Value::Text(s),
55
- serde_json::Value::Number(n) => n
56
- .as_i64()
57
- .map(Value::Integer)
58
- .unwrap_or_else(|| Value::Real(n.as_f64().unwrap_or(0.0))),
59
- serde_json::Value::Bool(b) => Value::Integer(b as i64),
60
- serde_json::Value::Null => Value::Null,
61
- other => Value::Text(other.to_string()),
62
- };
63
- (k, v)
64
- })
65
- .collect()
66
- }
67
-
68
- let db = DirSQL::new(
69
- "./my-project",
70
- vec![
71
- Table::new(
72
- "CREATE TABLE comments (id TEXT, body TEXT, author TEXT)",
73
- "comments/**/*.json",
74
- |path| vec![row_from_json(&std::fs::read_to_string(path).unwrap())],
75
- ),
76
- ],
77
- )?;
78
-
79
- let mut stream = db.watch()?;
80
- while let Some(event) = stream.next().await {
81
- match event {
82
- RowEvent::Insert { table, row, file_path } => {
83
- println!("insert on {table} ({file_path}): {row:?}")
84
- }
85
- RowEvent::Update { table, old_row, new_row, file_path } => {
86
- println!("update on {table} ({file_path}): {old_row:?} -> {new_row:?}")
87
- }
88
- RowEvent::Delete { table, row, file_path } => {
89
- println!("delete on {table} ({file_path}): {row:?}")
90
- }
91
- RowEvent::Error { table, file_path, error } => {
92
- println!("error on {table:?} {file_path:?}: {error}")
93
- }
94
- }
95
- }
96
- ```
97
-
98
- ```typescript [TypeScript]
99
- import { readFileSync } from 'node:fs';
100
- import { DirSQL, type TableDef } from 'dirsql';
101
-
102
- const tables: TableDef[] = [
103
- {
104
- ddl: 'CREATE TABLE comments (id TEXT, body TEXT, author TEXT)',
105
- glob: 'comments/**/*.json',
106
- extract: (path) => [JSON.parse(readFileSync(path, 'utf8'))],
107
- },
108
- ];
109
-
110
- const db = new DirSQL({ root: './my-project', tables });
111
-
112
- for await (const event of db.watch()) {
113
- console.log(`${event.action} on ${event.table}:`, event.row);
114
- }
115
- ```
116
-
117
- :::
118
-
119
- See [Async API](./async.md) for full details on the async `DirSQL` API (Python).
120
-
121
- ## Event types
122
-
123
- Each event is a `RowEvent` object with these attributes:
124
-
125
- ### `insert`
126
-
127
- A new row was added. This happens when a new file is created or an existing file gains additional rows.
128
-
129
- ::: code-group
130
-
131
- ```python [Python]
132
- event.action # "insert"
133
- event.table # "comments"
134
- event.row # {"id": "abc", "body": "new comment", "author": "alice"}
135
- event.old_row # None
136
- event.file_path # "comments/abc/index.json"
137
- ```
138
-
139
- ```rust [Rust]
140
- // RowEvent is an enum; match on the variant to destructure its fields.
141
- RowEvent::Insert {
142
- table, // "comments"
143
- row, // {"id": "abc", "body": "new comment", "author": "alice"}
144
- file_path, // "comments/abc/index.json"
145
- } => { /* ... */ }
146
- ```
147
-
148
- ```typescript [TypeScript]
149
- event.action // 'insert'
150
- event.table // 'comments'
151
- event.row // { id: 'abc', body: 'new comment', author: 'alice' }
152
- event.oldRow // undefined
153
- event.filePath // 'comments/abc/index.json'
154
- ```
155
-
156
- :::
157
-
158
- ### `update`
159
-
160
- An existing row was modified. `dirsql` diffs the old and new rows extracted from the file to detect changes.
161
-
162
- ::: code-group
163
-
164
- ```python [Python]
165
- event.action # "update"
166
- event.table # "comments"
167
- event.row # {"id": "abc", "body": "edited comment", "author": "alice"}
168
- event.old_row # {"id": "abc", "body": "original comment", "author": "alice"}
169
- event.file_path # "comments/abc/index.json"
170
- ```
171
-
172
- ```rust [Rust]
173
- RowEvent::Update {
174
- table, // "comments"
175
- old_row, // {"id": "abc", "body": "original comment", "author": "alice"}
176
- new_row, // {"id": "abc", "body": "edited comment", "author": "alice"}
177
- file_path, // "comments/abc/index.json"
178
- } => { /* ... */ }
179
- ```
180
-
181
- ```typescript [TypeScript]
182
- event.action // 'update'
183
- event.table // 'comments'
184
- event.row // { id: 'abc', body: 'edited comment', author: 'alice' }
185
- event.oldRow // { id: 'abc', body: 'original comment', author: 'alice' }
186
- event.filePath // 'comments/abc/index.json'
187
- ```
188
-
189
- :::
190
-
191
- ### `delete`
192
-
193
- A row was removed. This happens when a file is deleted or a file is modified to contain fewer rows.
194
-
195
- ::: code-group
196
-
197
- ```python [Python]
198
- event.action # "delete"
199
- event.table # "comments"
200
- event.row # {"id": "abc", "body": "deleted comment", "author": "alice"}
201
- event.old_row # None
202
- event.file_path # "comments/abc/index.json"
203
- ```
204
-
205
- ```rust [Rust]
206
- RowEvent::Delete {
207
- table, // "comments"
208
- row, // {"id": "abc", "body": "deleted comment", "author": "alice"}
209
- file_path, // "comments/abc/index.json"
210
- } => { /* ... */ }
211
- ```
212
-
213
- ```typescript [TypeScript]
214
- event.action // 'delete'
215
- event.table // 'comments'
216
- event.row // { id: 'abc', body: 'deleted comment', author: 'alice' }
217
- event.oldRow // undefined
218
- event.filePath // 'comments/abc/index.json'
219
- ```
220
-
221
- :::
222
-
223
- ### `error`
224
-
225
- An error occurred while processing a file change. The file was modified but the extract function failed, or the file could not be read.
226
-
227
- ::: code-group
228
-
229
- ```python [Python]
230
- event.action # "error"
231
- event.table # "comments" (or None if the error isn't tied to a table)
232
- event.error # "Extract error: ..."
233
- event.file_path # "comments/abc/index.json"
234
- event.row # None
235
- ```
236
-
237
- ```rust [Rust]
238
- // `table` is `Option<String>`: `Some("comments")` when the failing
239
- // file matched a table's glob; `None` for errors that aren't tied
240
- // to a specific table (e.g. a watch-channel failure).
241
- RowEvent::Error {
242
- table, // Some("comments")
243
- file_path, // PathBuf, e.g. "comments/abc/index.json"
244
- error, // "Extract error: ..."
245
- } => { /* ... */ }
246
- ```
247
-
248
- ```typescript [TypeScript]
249
- event.action // 'error'
250
- event.table // 'comments' (or null if the error isn't tied to a table)
251
- event.error // 'Extract error: ...'
252
- event.filePath // 'comments/abc/index.json'
253
- event.row // undefined
254
- ```
255
-
256
- :::
257
-
258
- ## How diffing works
259
-
260
- When a file changes, `dirsql`:
261
-
262
- 1. Re-reads the file and calls the extract function to get new rows
263
- 2. Compares new rows against the previously extracted rows for that file
264
- 3. Emits insert, update, and delete events based on the diff
265
- 4. Updates the in-memory database to reflect the new state
266
-
267
- Row identity is determined by position (row index within the file). If a file previously produced 3 rows and now produces 2, the first two rows are compared for updates and the third is emitted as a delete.
268
-
269
- ## Filesystem events
270
-
271
- Under the hood, `dirsql` uses the `notify` crate (inotify on Linux, FSEvents on macOS, ReadDirectoryChangesW on Windows) to receive filesystem events. Events are coalesced and filtered through the table matcher before being processed.
272
-
273
- Files that do not match any table glob or that match an ignore pattern are silently skipped.