dirsql 0.3.25 → 0.3.26
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.
- package/docs/api/index.md +6 -4
- package/docs/getting-started.md +1 -1
- package/docs/guide/async.md +55 -12
- package/docs/guide/crdt.md +1 -1
- package/docs/guide/querying.md +3 -2
- package/docs/guide/tables.md +1 -1
- package/docs/guide/watching.md +6 -2
- package/package.json +11 -11
package/docs/api/index.md
CHANGED
|
@@ -188,11 +188,12 @@ import { Table } from 'dirsql';
|
|
|
188
188
|
::: code-group
|
|
189
189
|
|
|
190
190
|
```python [Python]
|
|
191
|
-
Table(*, ddl: str, glob: str, extract: Callable[[str], list[dict]])
|
|
191
|
+
Table(*, ddl: str, glob: str, extract: Callable[[str], list[dict]], strict: bool = False)
|
|
192
192
|
```
|
|
193
193
|
|
|
194
194
|
```rust [Rust]
|
|
195
|
-
|
|
195
|
+
// Row = HashMap<String, Value>
|
|
196
|
+
Table::new(ddl: &str, glob: &str, extract: fn(&str) -> Vec<HashMap<String, Value>>)
|
|
196
197
|
```
|
|
197
198
|
|
|
198
199
|
```typescript [TypeScript]
|
|
@@ -207,7 +208,8 @@ Defines a mapping from files to SQLite table rows.
|
|
|
207
208
|
|
|
208
209
|
- `ddl` -- A `CREATE TABLE` statement. The table name is parsed from this DDL.
|
|
209
210
|
- `glob` -- A glob pattern matched against file paths relative to the root directory.
|
|
210
|
-
- `extract` -- A callable `(path) -> list[dict]`. Receives the
|
|
211
|
+
- `extract` -- A callable `(path) -> list[dict]`. Receives the path of the matched file -- relative to the scan root, or absolute when `root` is absolute. `dirsql` does not read file contents; a callback that needs the file body reads `path` itself. Returns a list of dicts/maps mapping column names to values. Return an empty list to skip a file.
|
|
212
|
+
- `strict` -- Optional (default `False`). Controls row/schema validation. In the default relaxed mode, extra row keys are dropped and missing columns become `NULL`. When `True`, every row key must be a valid column identifier and any extra or missing key raises an error. Surfaced in [serialization](#serialization) above as part of each table's `{ ddl, glob, strict }`.
|
|
211
213
|
|
|
212
214
|
**Attributes:**
|
|
213
215
|
|
|
@@ -231,7 +233,7 @@ use dirsql::RowEvent;
|
|
|
231
233
|
```
|
|
232
234
|
|
|
233
235
|
```typescript [TypeScript]
|
|
234
|
-
import { RowEvent } from 'dirsql';
|
|
236
|
+
import type { RowEvent } from 'dirsql';
|
|
235
237
|
```
|
|
236
238
|
|
|
237
239
|
:::
|
package/docs/getting-started.md
CHANGED
|
@@ -171,7 +171,7 @@ const results = await db.query(`
|
|
|
171
171
|
|
|
172
172
|
1. `dirsql` walks the directory tree
|
|
173
173
|
2. Files matching each table's glob pattern are identified
|
|
174
|
-
3. The `extract` function receives each matched file's absolute
|
|
174
|
+
3. The `extract` function receives each matched file's path (relative to the scan root, or absolute when `root` is absolute) and returns rows
|
|
175
175
|
4. Rows are inserted into an in-memory SQLite database
|
|
176
176
|
5. SQL queries run against that database
|
|
177
177
|
|
package/docs/guide/async.md
CHANGED
|
@@ -28,6 +28,7 @@ async def main():
|
|
|
28
28
|
),
|
|
29
29
|
],
|
|
30
30
|
)
|
|
31
|
+
await db.ready()
|
|
31
32
|
|
|
32
33
|
# Query (runs in a thread, does not block the event loop)
|
|
33
34
|
results = await db.query("SELECT * FROM items WHERE value > 10")
|
|
@@ -37,7 +38,31 @@ asyncio.run(main())
|
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
```rust [Rust]
|
|
40
|
-
use dirsql::{DirSQL, Table};
|
|
41
|
+
use dirsql::{DirSQL, Table, Value};
|
|
42
|
+
use std::collections::HashMap;
|
|
43
|
+
|
|
44
|
+
// See `row_from_json` in getting-started.md for a reusable helper that
|
|
45
|
+
// turns a JSON object into a dirsql row (dirsql::Value is not Deserialize,
|
|
46
|
+
// so a row can't be produced by serde_json::from_str directly).
|
|
47
|
+
fn row_from_json(raw: &str) -> HashMap<String, Value> {
|
|
48
|
+
let v: serde_json::Value = serde_json::from_str(raw).unwrap();
|
|
49
|
+
let serde_json::Value::Object(obj) = v else { return HashMap::new() };
|
|
50
|
+
obj.into_iter()
|
|
51
|
+
.map(|(k, val)| {
|
|
52
|
+
let v = match val {
|
|
53
|
+
serde_json::Value::String(s) => Value::Text(s),
|
|
54
|
+
serde_json::Value::Number(n) => n
|
|
55
|
+
.as_i64()
|
|
56
|
+
.map(Value::Integer)
|
|
57
|
+
.unwrap_or_else(|| Value::Real(n.as_f64().unwrap_or(0.0))),
|
|
58
|
+
serde_json::Value::Bool(b) => Value::Integer(b as i64),
|
|
59
|
+
serde_json::Value::Null => Value::Null,
|
|
60
|
+
other => Value::Text(other.to_string()),
|
|
61
|
+
};
|
|
62
|
+
(k, v)
|
|
63
|
+
})
|
|
64
|
+
.collect()
|
|
65
|
+
}
|
|
41
66
|
|
|
42
67
|
#[tokio::main]
|
|
43
68
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
@@ -47,7 +72,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
47
72
|
Table::new(
|
|
48
73
|
"CREATE TABLE items (name TEXT, value INTEGER)",
|
|
49
74
|
"data/*.json",
|
|
50
|
-
|path| vec![
|
|
75
|
+
|path| vec![row_from_json(&std::fs::read_to_string(path).unwrap())],
|
|
51
76
|
),
|
|
52
77
|
],
|
|
53
78
|
)?;
|
|
@@ -83,7 +108,7 @@ console.log(results);
|
|
|
83
108
|
## Constructor
|
|
84
109
|
|
|
85
110
|
```python
|
|
86
|
-
DirSQL(root=None, *, tables=None, ignore=None, config=None)
|
|
111
|
+
DirSQL(root=None, *, tables=None, ignore=None, config=None, persist=False, persist_path=None)
|
|
87
112
|
```
|
|
88
113
|
|
|
89
114
|
The constructor immediately starts scanning in a background thread via `asyncio.ensure_future`. The constructor itself returns immediately without blocking.
|
|
@@ -132,15 +157,29 @@ async for event in db.watch():
|
|
|
132
157
|
```
|
|
133
158
|
|
|
134
159
|
```rust [Rust]
|
|
160
|
+
// `RowEvent` is an enum; match on the variant to destructure its fields.
|
|
161
|
+
// `StreamExt` (for `.next()`) comes from the `futures` crate, which is only a
|
|
162
|
+
// dirsql dependency under its `cli` feature -- add it to your own project:
|
|
163
|
+
//
|
|
164
|
+
// cargo add futures
|
|
165
|
+
use dirsql::RowEvent;
|
|
135
166
|
use futures::StreamExt;
|
|
136
167
|
|
|
137
|
-
let mut stream = db.watch()
|
|
168
|
+
let mut stream = db.watch()?; // watch() returns Result<WatchStream>
|
|
138
169
|
while let Some(event) = stream.next().await {
|
|
139
|
-
match event
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
170
|
+
match event {
|
|
171
|
+
RowEvent::Insert { table, row, file_path } => {
|
|
172
|
+
println!("New row in {table} ({file_path}): {row:?}")
|
|
173
|
+
}
|
|
174
|
+
RowEvent::Update { table, new_row, file_path, .. } => {
|
|
175
|
+
println!("Updated row in {table} ({file_path}): {new_row:?}")
|
|
176
|
+
}
|
|
177
|
+
RowEvent::Delete { table, row, file_path } => {
|
|
178
|
+
println!("Deleted row from {table} ({file_path}): {row:?}")
|
|
179
|
+
}
|
|
180
|
+
RowEvent::Error { file_path, error, .. } => {
|
|
181
|
+
eprintln!("Error on {file_path:?}: {error}")
|
|
182
|
+
}
|
|
144
183
|
}
|
|
145
184
|
}
|
|
146
185
|
```
|
|
@@ -188,16 +227,20 @@ async def main():
|
|
|
188
227
|
```
|
|
189
228
|
|
|
190
229
|
```rust [Rust]
|
|
191
|
-
|
|
192
|
-
|
|
230
|
+
// `.next()` needs `StreamExt` from the `futures` crate (`cargo add futures`).
|
|
231
|
+
use futures::StreamExt;
|
|
232
|
+
|
|
233
|
+
async fn watch_and_serve(db: &DirSQL) -> Result<(), Box<dyn std::error::Error>> {
|
|
234
|
+
let mut stream = db.watch()?; // watch() returns Result<WatchStream>
|
|
193
235
|
while let Some(event) = stream.next().await {
|
|
194
236
|
notify_clients(&event).await;
|
|
195
237
|
}
|
|
238
|
+
Ok(())
|
|
196
239
|
}
|
|
197
240
|
|
|
198
241
|
#[tokio::main]
|
|
199
242
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
200
|
-
let db = DirSQL::new("./data", vec![
|
|
243
|
+
let db = DirSQL::new("./data", vec![/* tables */])?;
|
|
201
244
|
|
|
202
245
|
tokio::join!(
|
|
203
246
|
watch_and_serve(&db),
|
package/docs/guide/crdt.md
CHANGED
package/docs/guide/querying.md
CHANGED
|
@@ -160,8 +160,9 @@ assert!(matches!(err, dirsql::DirSqlError::WriteForbidden));
|
|
|
160
160
|
```
|
|
161
161
|
|
|
162
162
|
```typescript [TypeScript]
|
|
163
|
-
//
|
|
164
|
-
|
|
163
|
+
// Rejects with an Error whose message explains writes are not accepted.
|
|
164
|
+
// `db.query` is async, so assert on the rejected promise.
|
|
165
|
+
await expect(db.query('DELETE FROM posts')).rejects.toThrow(/read-only/i);
|
|
165
166
|
```
|
|
166
167
|
|
|
167
168
|
:::
|
package/docs/guide/tables.md
CHANGED
|
@@ -91,7 +91,7 @@ Glob syntax follows standard Unix globbing rules. `**` matches any number of dir
|
|
|
91
91
|
|
|
92
92
|
A callable `(path: str) -> list[dict]` that converts a file into rows.
|
|
93
93
|
|
|
94
|
-
- `path` is the **
|
|
94
|
+
- `path` is the path of the matched file, **relative to the scan root** (or absolute when the `root` passed to `DirSQL` is absolute)
|
|
95
95
|
- Return a list of dicts, where each dict maps column names to values
|
|
96
96
|
- Return an empty list to skip a file
|
|
97
97
|
|
package/docs/guide/watching.md
CHANGED
|
@@ -36,6 +36,10 @@ async for event in db.watch():
|
|
|
36
36
|
```
|
|
37
37
|
|
|
38
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
|
|
39
43
|
use dirsql::{DirSQL, RowEvent, Table, Value};
|
|
40
44
|
use futures::StreamExt;
|
|
41
45
|
use std::collections::HashMap;
|
|
@@ -84,8 +88,8 @@ while let Some(event) = stream.next().await {
|
|
|
84
88
|
RowEvent::Delete { table, row, file_path } => {
|
|
85
89
|
println!("delete on {table} ({file_path}): {row:?}")
|
|
86
90
|
}
|
|
87
|
-
RowEvent::Error { file_path, error } => {
|
|
88
|
-
println!("error on {file_path:?}: {error}")
|
|
91
|
+
RowEvent::Error { table, file_path, error } => {
|
|
92
|
+
println!("error on {table:?} {file_path:?}: {error}")
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
95
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dirsql",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.26",
|
|
4
4
|
"description": "Ephemeral SQL index over a local directory",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "https://github.com/thekevinscott/dirsql",
|
|
@@ -195,15 +195,15 @@
|
|
|
195
195
|
"smol-toml": "^1.6.1"
|
|
196
196
|
},
|
|
197
197
|
"optionalDependencies": {
|
|
198
|
-
"@dirsql/lib-linux-x64-gnu": "0.3.
|
|
199
|
-
"@dirsql/lib-linux-arm64-gnu": "0.3.
|
|
200
|
-
"@dirsql/lib-darwin-x64": "0.3.
|
|
201
|
-
"@dirsql/lib-darwin-arm64": "0.3.
|
|
202
|
-
"@dirsql/lib-win32-x64-msvc": "0.3.
|
|
203
|
-
"@dirsql/cli-linux-x64-gnu": "0.3.
|
|
204
|
-
"@dirsql/cli-linux-arm64-gnu": "0.3.
|
|
205
|
-
"@dirsql/cli-darwin-x64": "0.3.
|
|
206
|
-
"@dirsql/cli-darwin-arm64": "0.3.
|
|
207
|
-
"@dirsql/cli-win32-x64-msvc": "0.3.
|
|
198
|
+
"@dirsql/lib-linux-x64-gnu": "0.3.26",
|
|
199
|
+
"@dirsql/lib-linux-arm64-gnu": "0.3.26",
|
|
200
|
+
"@dirsql/lib-darwin-x64": "0.3.26",
|
|
201
|
+
"@dirsql/lib-darwin-arm64": "0.3.26",
|
|
202
|
+
"@dirsql/lib-win32-x64-msvc": "0.3.26",
|
|
203
|
+
"@dirsql/cli-linux-x64-gnu": "0.3.26",
|
|
204
|
+
"@dirsql/cli-linux-arm64-gnu": "0.3.26",
|
|
205
|
+
"@dirsql/cli-darwin-x64": "0.3.26",
|
|
206
|
+
"@dirsql/cli-darwin-arm64": "0.3.26",
|
|
207
|
+
"@dirsql/cli-win32-x64-msvc": "0.3.26"
|
|
208
208
|
}
|
|
209
209
|
}
|