dirsql 0.3.65 → 0.3.67
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/getting-started.md +217 -150
- package/docs/index.md +0 -1
- package/docs/migrations.md +0 -3
- package/package.json +11 -11
- package/docs/api/index.md +0 -238
- package/docs/guide/async.md +0 -268
- package/docs/guide/crdt.md +0 -161
- package/docs/guide/persistence.md +0 -177
- package/docs/guide/querying.md +0 -221
- package/docs/guide/tables.md +0 -269
- package/docs/guide/watching.md +0 -273
package/docs/getting-started.md
CHANGED
|
@@ -1,190 +1,257 @@
|
|
|
1
|
-
|
|
2
|
-
canonical: https://thekevinscott.github.io/dirsql/getting-started
|
|
3
|
-
---
|
|
1
|
+
# Your first dirsql database
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
In this tutorial you will turn a directory of three tiny markdown files into
|
|
4
|
+
a SQL database you can query over HTTP — without writing any code. You will:
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
1. Create the directory and files.
|
|
7
|
+
2. Start `dirsql` with zero configuration and query it with `curl`.
|
|
8
|
+
3. Define your own table in a `.dirsql.toml` and query the new shape.
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
It takes about five minutes.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
**You need:** a terminal with `curl` and [`jq`](https://jqlang.org/), and
|
|
13
|
+
Node ≥ 20.11 (for `npx`). Every `npx dirsql` step below also has a `uvx`
|
|
14
|
+
tab that behaves identically, if you prefer Python tooling
|
|
15
|
+
([`uv`](https://docs.astral.sh/uv/)).
|
|
16
|
+
|
|
17
|
+
## 1. Create three files
|
|
18
|
+
|
|
19
|
+
Make a working directory with two subfolders — one per note author:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
mkdir -p my-notes/notes/alice my-notes/notes/bob
|
|
23
|
+
cd my-notes
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Create the three notes by pasting each block exactly as shown:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cat > notes/alice/welcome.md <<'EOF'
|
|
30
|
+
# Welcome
|
|
31
|
+
|
|
32
|
+
Start here. This folder is about to become a database.
|
|
33
|
+
EOF
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cat > notes/alice/ideas.md <<'EOF'
|
|
38
|
+
# Ideas
|
|
39
|
+
|
|
40
|
+
- query files with SQL
|
|
41
|
+
- watch for changes
|
|
42
|
+
EOF
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cat > notes/bob/reading-list.md <<'EOF'
|
|
47
|
+
# Reading list
|
|
48
|
+
|
|
49
|
+
- The SQLite file format
|
|
50
|
+
EOF
|
|
51
|
+
```
|
|
12
52
|
|
|
13
|
-
|
|
14
|
-
|
|
53
|
+
Check that all three files are in place:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
find notes -type f | sort
|
|
15
57
|
```
|
|
16
58
|
|
|
17
|
-
```bash [Rust]
|
|
18
|
-
cargo add dirsql
|
|
19
59
|
```
|
|
60
|
+
notes/alice/ideas.md
|
|
61
|
+
notes/alice/welcome.md
|
|
62
|
+
notes/bob/reading-list.md
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 2. Start the server
|
|
66
|
+
|
|
67
|
+
From inside `my-notes`, start `dirsql`:
|
|
20
68
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
npm
|
|
69
|
+
::: code-group
|
|
70
|
+
|
|
71
|
+
```bash [npm]
|
|
72
|
+
npx dirsql
|
|
24
73
|
```
|
|
25
74
|
|
|
26
|
-
```bash [
|
|
27
|
-
|
|
28
|
-
npx dirsql --version
|
|
29
|
-
uvx dirsql --version
|
|
30
|
-
cargo install dirsql --features cli
|
|
75
|
+
```bash [PyPI]
|
|
76
|
+
uvx dirsql
|
|
31
77
|
```
|
|
32
78
|
|
|
33
79
|
:::
|
|
34
80
|
|
|
81
|
+
The first run downloads the package (`npx` asks for confirmation — answer
|
|
82
|
+
`y`; `uvx` prints download progress), then the server starts:
|
|
35
83
|
|
|
84
|
+
```
|
|
85
|
+
Running at localhost:7117
|
|
86
|
+
```
|
|
36
87
|
|
|
37
|
-
|
|
88
|
+
That one command scanned the directory, built an in-memory SQLite database
|
|
89
|
+
with one row per file, and started an HTTP server. Leave it running and
|
|
90
|
+
open a **second terminal** for the next step.
|
|
38
91
|
|
|
39
|
-
##
|
|
92
|
+
## 3. Query your files
|
|
40
93
|
|
|
41
|
-
|
|
94
|
+
You gave `dirsql` no configuration, so it serves a single default table
|
|
95
|
+
named `files` ([zero-config mode](./reference/cli.md#zero-config-mode)).
|
|
96
|
+
Ask it how many rows it has:
|
|
42
97
|
|
|
98
|
+
```bash
|
|
99
|
+
curl -s http://localhost:7117/query \
|
|
100
|
+
-H 'content-type: application/json' \
|
|
101
|
+
-d '{"sql":"SELECT COUNT(*) AS files FROM files"}'
|
|
43
102
|
```
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
second.json # {"title": "Second Post", "author": "bob"}
|
|
48
|
-
authors/
|
|
49
|
-
alice.json # {"id": "alice", "name": "Alice"}
|
|
50
|
-
bob.json # {"id": "bob", "name": "Bob"}
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
[{"files":3}]
|
|
51
106
|
```
|
|
52
107
|
|
|
53
|
-
|
|
108
|
+
Three files, three rows. The response is always a JSON array of row
|
|
109
|
+
objects ([HTTP API](./reference/http-api.md)) — from here on we pipe it
|
|
110
|
+
through `jq` to pretty-print. Now select some columns:
|
|
54
111
|
|
|
55
|
-
|
|
112
|
+
```bash
|
|
113
|
+
curl -s http://localhost:7117/query \
|
|
114
|
+
-H 'content-type: application/json' \
|
|
115
|
+
-d '{"sql":"SELECT _path, _size FROM files ORDER BY _path"}' \
|
|
116
|
+
| jq
|
|
117
|
+
```
|
|
56
118
|
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
import json
|
|
60
|
-
from dirsql import DirSQL, Table
|
|
61
|
-
|
|
62
|
-
async def main():
|
|
63
|
-
db = DirSQL(
|
|
64
|
-
"./my-blog",
|
|
65
|
-
tables=[
|
|
66
|
-
Table(
|
|
67
|
-
ddl="CREATE TABLE posts (title TEXT, author TEXT)",
|
|
68
|
-
glob="posts/*.json",
|
|
69
|
-
extract=lambda path: [json.loads(open(path, encoding="utf-8").read())],
|
|
70
|
-
),
|
|
71
|
-
Table(
|
|
72
|
-
ddl="CREATE TABLE authors (id TEXT, name TEXT)",
|
|
73
|
-
glob="authors/*.json",
|
|
74
|
-
extract=lambda path: [json.loads(open(path, encoding="utf-8").read())],
|
|
75
|
-
),
|
|
76
|
-
],
|
|
77
|
-
)
|
|
78
|
-
await db.ready()
|
|
79
|
-
|
|
80
|
-
# Query all posts
|
|
81
|
-
posts = await db.query("SELECT * FROM posts")
|
|
82
|
-
# [{"title": "Hello World", "author": "alice"}, {"title": "Second Post", "author": "bob"}]
|
|
83
|
-
|
|
84
|
-
# Join across tables
|
|
85
|
-
results = await db.query("""
|
|
86
|
-
SELECT posts.title, authors.name
|
|
87
|
-
FROM posts
|
|
88
|
-
JOIN authors ON posts.author = authors.id
|
|
89
|
-
""")
|
|
90
|
-
# [{"title": "Hello World", "name": "Alice"}, {"title": "Second Post", "name": "Bob"}]
|
|
91
|
-
|
|
92
|
-
asyncio.run(main())
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
```rust [Rust]
|
|
96
|
-
use dirsql::{DirSQL, Table, Value};
|
|
97
|
-
use std::collections::HashMap;
|
|
98
|
-
|
|
99
|
-
// Convert a JSON object string into a dirsql row.
|
|
100
|
-
fn row_from_json(raw: &str) -> HashMap<String, Value> {
|
|
101
|
-
let v: serde_json::Value = serde_json::from_str(raw).unwrap();
|
|
102
|
-
let serde_json::Value::Object(obj) = v else { return HashMap::new() };
|
|
103
|
-
obj.into_iter()
|
|
104
|
-
.map(|(k, val)| {
|
|
105
|
-
let v = match val {
|
|
106
|
-
serde_json::Value::String(s) => Value::Text(s),
|
|
107
|
-
serde_json::Value::Number(n) => n
|
|
108
|
-
.as_i64()
|
|
109
|
-
.map(Value::Integer)
|
|
110
|
-
.unwrap_or_else(|| Value::Real(n.as_f64().unwrap_or(0.0))),
|
|
111
|
-
serde_json::Value::Bool(b) => Value::Integer(b as i64),
|
|
112
|
-
serde_json::Value::Null => Value::Null,
|
|
113
|
-
other => Value::Text(other.to_string()),
|
|
114
|
-
};
|
|
115
|
-
(k, v)
|
|
116
|
-
})
|
|
117
|
-
.collect()
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
let db = DirSQL::new(
|
|
121
|
-
"./my-blog",
|
|
122
|
-
vec![
|
|
123
|
-
Table::new(
|
|
124
|
-
"CREATE TABLE posts (title TEXT, author TEXT)",
|
|
125
|
-
"posts/*.json",
|
|
126
|
-
|path| vec![row_from_json(&std::fs::read_to_string(path).unwrap())],
|
|
127
|
-
),
|
|
128
|
-
Table::new(
|
|
129
|
-
"CREATE TABLE authors (id TEXT, name TEXT)",
|
|
130
|
-
"authors/*.json",
|
|
131
|
-
|path| vec![row_from_json(&std::fs::read_to_string(path).unwrap())],
|
|
132
|
-
),
|
|
133
|
-
],
|
|
134
|
-
)?;
|
|
135
|
-
|
|
136
|
-
let posts = db.query("SELECT * FROM posts")?;
|
|
137
|
-
|
|
138
|
-
let results = db.query(
|
|
139
|
-
"SELECT posts.title, authors.name \
|
|
140
|
-
FROM posts JOIN authors ON posts.author = authors.id"
|
|
141
|
-
)?;
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
```typescript [TypeScript]
|
|
145
|
-
import { readFileSync } from 'node:fs';
|
|
146
|
-
import { DirSQL, type TableDef } from 'dirsql';
|
|
147
|
-
|
|
148
|
-
const tables: TableDef[] = [
|
|
119
|
+
```json
|
|
120
|
+
[
|
|
149
121
|
{
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
extract: (path) => [JSON.parse(readFileSync(path, 'utf8'))],
|
|
122
|
+
"_path": "notes/alice/ideas.md",
|
|
123
|
+
"_size": 52
|
|
153
124
|
},
|
|
154
125
|
{
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
extract: (path) => [JSON.parse(readFileSync(path, 'utf8'))],
|
|
126
|
+
"_path": "notes/alice/welcome.md",
|
|
127
|
+
"_size": 66
|
|
158
128
|
},
|
|
159
|
-
|
|
129
|
+
{
|
|
130
|
+
"_path": "notes/bob/reading-list.md",
|
|
131
|
+
"_size": 41
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`_path` and `_size` are two of the built-in file columns `dirsql` collects
|
|
137
|
+
for every file — see [virtual columns](./reference/columns.md#virtual-columns)
|
|
138
|
+
for the full list. (The `_size` values are byte counts; they match the
|
|
139
|
+
output above because you pasted the files exactly.)
|
|
160
140
|
|
|
161
|
-
|
|
141
|
+
You have a working SQL database over your files. Next, teach it the
|
|
142
|
+
structure your folders already encode.
|
|
162
143
|
|
|
163
|
-
|
|
144
|
+
## 4. Define a table
|
|
164
145
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
146
|
+
Look at the paths again: `notes/alice/ideas.md`, `notes/bob/reading-list.md`
|
|
147
|
+
— the author's name is a directory segment. A config file can capture it as
|
|
148
|
+
a real column.
|
|
149
|
+
|
|
150
|
+
In your second terminal, still inside `my-notes`, create a `.dirsql.toml`:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
cat > .dirsql.toml <<'EOF'
|
|
154
|
+
[[table]]
|
|
155
|
+
ddl = "CREATE TABLE notes (author TEXT, _basename TEXT, _size INTEGER)"
|
|
156
|
+
glob = "notes/{author}/*.md"
|
|
157
|
+
EOF
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Two keys define the table:
|
|
161
|
+
|
|
162
|
+
- `glob` selects which files feed the table, and `{author}` is a
|
|
163
|
+
[glob capture](./reference/columns.md#glob-captures): whatever directory
|
|
164
|
+
name matches that segment becomes the row's `author` value.
|
|
165
|
+
- `ddl` is ordinary `CREATE TABLE` SQL naming the columns you want to keep.
|
|
166
|
+
|
|
167
|
+
## 5. Restart and query the new shape
|
|
168
|
+
|
|
169
|
+
Config is read at startup, so go back to the **first terminal**, stop the
|
|
170
|
+
server with `Ctrl-C`, and start it again:
|
|
171
|
+
|
|
172
|
+
::: code-group
|
|
173
|
+
|
|
174
|
+
```bash [npm]
|
|
175
|
+
npx dirsql
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
```bash [PyPI]
|
|
179
|
+
uvx dirsql
|
|
169
180
|
```
|
|
170
181
|
|
|
171
182
|
:::
|
|
172
183
|
|
|
173
|
-
|
|
184
|
+
```
|
|
185
|
+
Running at localhost:7117
|
|
186
|
+
```
|
|
174
187
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
4. Rows are inserted into an in-memory SQLite database
|
|
179
|
-
5. SQL queries run against that database
|
|
188
|
+
This time `dirsql` found your `.dirsql.toml` and served the `notes` table
|
|
189
|
+
you defined instead of the default `files` table. Query it from the second
|
|
190
|
+
terminal:
|
|
180
191
|
|
|
181
|
-
|
|
192
|
+
```bash
|
|
193
|
+
curl -s http://localhost:7117/query \
|
|
194
|
+
-H 'content-type: application/json' \
|
|
195
|
+
-d '{"sql":"SELECT author, _basename, _size FROM notes ORDER BY author, _basename"}' \
|
|
196
|
+
| jq
|
|
197
|
+
```
|
|
182
198
|
|
|
183
|
-
|
|
199
|
+
```json
|
|
200
|
+
[
|
|
201
|
+
{
|
|
202
|
+
"_basename": "ideas.md",
|
|
203
|
+
"_size": 52,
|
|
204
|
+
"author": "alice"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"_basename": "welcome.md",
|
|
208
|
+
"_size": 66,
|
|
209
|
+
"author": "alice"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"_basename": "reading-list.md",
|
|
213
|
+
"_size": 41,
|
|
214
|
+
"author": "bob"
|
|
215
|
+
}
|
|
216
|
+
]
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Every row now carries an `author` column extracted from its path — no
|
|
220
|
+
extraction code, just a glob. And it is a real SQL column, so you can
|
|
221
|
+
aggregate on it:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
curl -s http://localhost:7117/query \
|
|
225
|
+
-H 'content-type: application/json' \
|
|
226
|
+
-d '{"sql":"SELECT author, COUNT(*) AS notes FROM notes GROUP BY author"}' \
|
|
227
|
+
| jq
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
[
|
|
232
|
+
{
|
|
233
|
+
"author": "alice",
|
|
234
|
+
"notes": 2
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"author": "bob",
|
|
238
|
+
"notes": 1
|
|
239
|
+
}
|
|
240
|
+
]
|
|
241
|
+
```
|
|
184
242
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
- [
|
|
243
|
+
That's the whole loop: files in a directory, a declarative table on top,
|
|
244
|
+
SQL over HTTP.
|
|
245
|
+
|
|
246
|
+
## Where to go next
|
|
247
|
+
|
|
248
|
+
- [Configuration file](./reference/config.md) — the complete `.dirsql.toml`
|
|
249
|
+
reference: more tables, ignore patterns, persistence, hooks.
|
|
250
|
+
- [CLI](./reference/cli.md) — flags like `--port` and `--config`, plus
|
|
251
|
+
`dirsql init`.
|
|
252
|
+
- [HTTP API](./reference/http-api.md) — `POST /query` in full, plus
|
|
253
|
+
`GET /events`, a live stream of row changes as files change.
|
|
254
|
+
- [SDK](./reference/sdk.md) — embed `dirsql` in a Python, Rust, or
|
|
255
|
+
TypeScript program instead of running the server.
|
|
256
|
+
- Why is the database rebuilt from your files on every startup? See
|
|
257
|
+
[how `dirsql` thinks](./explanation.md).
|
package/docs/index.md
CHANGED
package/docs/migrations.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dirsql",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.67",
|
|
4
4
|
"description": "Ephemeral SQL index over a local directory",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "https://github.com/thekevinscott/dirsql",
|
|
@@ -230,15 +230,15 @@
|
|
|
230
230
|
]
|
|
231
231
|
},
|
|
232
232
|
"optionalDependencies": {
|
|
233
|
-
"@dirsql/lib-linux-x64-gnu": "0.3.
|
|
234
|
-
"@dirsql/lib-linux-arm64-gnu": "0.3.
|
|
235
|
-
"@dirsql/lib-darwin-x64": "0.3.
|
|
236
|
-
"@dirsql/lib-darwin-arm64": "0.3.
|
|
237
|
-
"@dirsql/lib-win32-x64-msvc": "0.3.
|
|
238
|
-
"@dirsql/cli-linux-x64-gnu": "0.3.
|
|
239
|
-
"@dirsql/cli-linux-arm64-gnu": "0.3.
|
|
240
|
-
"@dirsql/cli-darwin-x64": "0.3.
|
|
241
|
-
"@dirsql/cli-darwin-arm64": "0.3.
|
|
242
|
-
"@dirsql/cli-win32-x64-msvc": "0.3.
|
|
233
|
+
"@dirsql/lib-linux-x64-gnu": "0.3.67",
|
|
234
|
+
"@dirsql/lib-linux-arm64-gnu": "0.3.67",
|
|
235
|
+
"@dirsql/lib-darwin-x64": "0.3.67",
|
|
236
|
+
"@dirsql/lib-darwin-arm64": "0.3.67",
|
|
237
|
+
"@dirsql/lib-win32-x64-msvc": "0.3.67",
|
|
238
|
+
"@dirsql/cli-linux-x64-gnu": "0.3.67",
|
|
239
|
+
"@dirsql/cli-linux-arm64-gnu": "0.3.67",
|
|
240
|
+
"@dirsql/cli-darwin-x64": "0.3.67",
|
|
241
|
+
"@dirsql/cli-darwin-arm64": "0.3.67",
|
|
242
|
+
"@dirsql/cli-win32-x64-msvc": "0.3.67"
|
|
243
243
|
}
|
|
244
244
|
}
|
package/docs/api/index.md
DELETED
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
canonical: https://thekevinscott.github.io/dirsql/api/
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# API Reference
|
|
6
|
-
|
|
7
|
-
> Online: <https://thekevinscott.github.io/dirsql/api/>
|
|
8
|
-
|
|
9
|
-
## DirSQL
|
|
10
|
-
|
|
11
|
-
### Import
|
|
12
|
-
|
|
13
|
-
::: code-group
|
|
14
|
-
|
|
15
|
-
```python [Python]
|
|
16
|
-
from dirsql import DirSQL
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
```rust [Rust]
|
|
20
|
-
use dirsql::DirSQL;
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
```typescript [TypeScript]
|
|
24
|
-
import { DirSQL } from 'dirsql';
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
:::
|
|
28
|
-
|
|
29
|
-
### Constructor
|
|
30
|
-
|
|
31
|
-
::: code-group
|
|
32
|
-
|
|
33
|
-
```python [Python]
|
|
34
|
-
DirSQL(
|
|
35
|
-
root: str | None = None,
|
|
36
|
-
*,
|
|
37
|
-
tables: list[Table] | None = None,
|
|
38
|
-
ignore: list[str] | None = None,
|
|
39
|
-
config: str | None = None,
|
|
40
|
-
extensions: list[dict] | None = None, # [{ "path": str, "entrypoint"?: str }]
|
|
41
|
-
)
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
```rust [Rust]
|
|
45
|
-
DirSQL::builder()
|
|
46
|
-
.root(root) // optional
|
|
47
|
-
.tables(tables) // optional; append with .table(t)
|
|
48
|
-
.ignore(patterns) // optional
|
|
49
|
-
.config(config_toml_path) // optional
|
|
50
|
-
.extensions(extensions) // optional; Extension { path, entrypoint }
|
|
51
|
-
.build() // -> Result<DirSQL>
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
```typescript [TypeScript]
|
|
55
|
-
new DirSQL(configPath: string)
|
|
56
|
-
// or
|
|
57
|
-
new DirSQL({
|
|
58
|
-
root?: string,
|
|
59
|
-
tables?: TableDef[],
|
|
60
|
-
ignore?: string[],
|
|
61
|
-
config?: string,
|
|
62
|
-
extensions?: ExtensionSpec[], // [{ path: string, entrypoint?: string }]
|
|
63
|
-
})
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
:::
|
|
67
|
-
|
|
68
|
-
Creates an in-memory SQLite index over the given directory. At least one of `root` or `config` must be supplied.
|
|
69
|
-
|
|
70
|
-
When both `root` and `config` are supplied -- or when `config` declares `[dirsql].root` -- the explicit `root` wins and a warning is emitted on stderr. A `[dirsql].root` declared in the config file is resolved relative to the config file's parent directory.
|
|
71
|
-
|
|
72
|
-
In Python, the constructor starts scanning in a background thread and returns immediately. Call `await db.ready()` before querying. In Rust, `.build()` scans synchronously; use `.build_async()` (via `AsyncDirSQL`) for the tokio-driven equivalent. In TypeScript, scanning starts immediately and `db.ready` resolves when the scan finishes.
|
|
73
|
-
|
|
74
|
-
**Parameters:**
|
|
75
|
-
|
|
76
|
-
- `root` -- Path to the directory to index. Optional if `config` is supplied.
|
|
77
|
-
- `tables` -- List of `Table` definitions. Each defines a SQLite table, a glob pattern, and an extract function.
|
|
78
|
-
- `ignore` -- Optional list of glob patterns. Files matching any ignore pattern are skipped regardless of table globs.
|
|
79
|
-
- `config` -- Optional path to a `.dirsql.toml` config file. Its `[[table]]` entries are appended to any programmatic `tables`; its `[dirsql].ignore` patterns are appended to any explicit `ignore`; its optional `[dirsql].root` supplies the root directory when `root` is not passed explicitly; its `[[dirsql.extension]]` entries are appended to any programmatic `extensions`.
|
|
80
|
-
- `extensions` -- Optional SQLite extensions to load onto the connection at startup, before any table DDL (enable → load → disable, so the SQL `load_extension()` function is never left exposed). Each entry pairs a shared-library `path` with an optional `entrypoint` init-symbol override (Python: `{ "path", "entrypoint"? }` dicts; Rust: `Extension { path, entrypoint }`; TypeScript: `{ path, entrypoint? }` objects). A `path` is either a file path or a bare **package name**; a package name is resolved from the installed package (Python `importlib` / TypeScript `node_modules`, file-first, erroring on zero or multiple matches). This works on the Python/TypeScript constructor — for both the programmatic `extensions` list and a `config` file's `[[dirsql.extension]]` entries — and on the CLI; the Rust binary and SDK are file-path-only. Programmatic entries load first, then any `[[dirsql.extension]]` from `config`. See [Loading extensions](../cli/config.md#loading-extensions).
|
|
81
|
-
|
|
82
|
-
### Methods
|
|
83
|
-
|
|
84
|
-
#### `ready`
|
|
85
|
-
|
|
86
|
-
::: code-group
|
|
87
|
-
|
|
88
|
-
```python [Python]
|
|
89
|
-
await db.ready() -> None
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
```rust [Rust]
|
|
93
|
-
db.ready().await -> Result<()>
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
```typescript [TypeScript]
|
|
97
|
-
await db.ready // awaitable property
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
:::
|
|
101
|
-
|
|
102
|
-
Wait for the initial scan to complete. Re-raises any exception from the scan. Safe to call multiple times.
|
|
103
|
-
|
|
104
|
-
#### `query`
|
|
105
|
-
|
|
106
|
-
::: code-group
|
|
107
|
-
|
|
108
|
-
```python [Python]
|
|
109
|
-
await db.query(sql: str) -> list[dict]
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
```rust [Rust]
|
|
113
|
-
db.query(sql: &str) -> Result<Vec<HashMap<String, Value>>>
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
```typescript [TypeScript]
|
|
117
|
-
await db.query(sql: string): Promise<Record<string, unknown>[]>
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
:::
|
|
121
|
-
|
|
122
|
-
Execute a SQL query against the in-memory database. Returns results keyed by column name. Internal tracking columns (`_dirsql_file_path`, `_dirsql_row_index`) are excluded from results.
|
|
123
|
-
|
|
124
|
-
#### `watch`
|
|
125
|
-
|
|
126
|
-
::: code-group
|
|
127
|
-
|
|
128
|
-
```python [Python]
|
|
129
|
-
async for event in db.watch(): # AsyncIterator[RowEvent]
|
|
130
|
-
...
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
```rust [Rust]
|
|
134
|
-
let mut stream = db.watch(); // impl Stream<Item = RowEvent>
|
|
135
|
-
while let Some(event) = stream.next().await { ... }
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
```typescript [TypeScript]
|
|
139
|
-
for await (const event of db.watch()) { // AsyncIterable<RowEvent>
|
|
140
|
-
...
|
|
141
|
-
}
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
:::
|
|
145
|
-
|
|
146
|
-
Returns an async iterable of `RowEvent` objects. The file watcher starts automatically on first iteration. The iterator never terminates on its own.
|
|
147
|
-
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
## Table
|
|
151
|
-
|
|
152
|
-
### Import
|
|
153
|
-
|
|
154
|
-
::: code-group
|
|
155
|
-
|
|
156
|
-
```python [Python]
|
|
157
|
-
from dirsql import Table
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
```rust [Rust]
|
|
161
|
-
use dirsql::Table;
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
```typescript [TypeScript]
|
|
165
|
-
import { Table } from 'dirsql';
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
:::
|
|
169
|
-
|
|
170
|
-
### Constructor
|
|
171
|
-
|
|
172
|
-
::: code-group
|
|
173
|
-
|
|
174
|
-
```python [Python]
|
|
175
|
-
Table(*, ddl: str, glob: str, extract: Callable[[str], list[dict]], strict: bool = False)
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
```rust [Rust]
|
|
179
|
-
// Row = HashMap<String, Value>
|
|
180
|
-
Table::new(ddl: &str, glob: &str, extract: fn(&str) -> Vec<HashMap<String, Value>>)
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
```typescript [TypeScript]
|
|
184
|
-
new Table({ ddl: string, glob: string, extract: (path: string) => Record<string, unknown>[] })
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
:::
|
|
188
|
-
|
|
189
|
-
Defines a mapping from files to SQLite table rows.
|
|
190
|
-
|
|
191
|
-
**Parameters:**
|
|
192
|
-
|
|
193
|
-
- `ddl` -- A `CREATE TABLE` statement. The table name is parsed from this DDL.
|
|
194
|
-
- `glob` -- A glob pattern matched against file paths relative to the root directory.
|
|
195
|
-
- `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.
|
|
196
|
-
- `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.
|
|
197
|
-
|
|
198
|
-
**Attributes:**
|
|
199
|
-
|
|
200
|
-
- `ddl` -- The DDL string (read-only).
|
|
201
|
-
- `glob` -- The glob pattern (read-only).
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## RowEvent
|
|
206
|
-
|
|
207
|
-
### Import
|
|
208
|
-
|
|
209
|
-
::: code-group
|
|
210
|
-
|
|
211
|
-
```python [Python]
|
|
212
|
-
from dirsql import RowEvent
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
```rust [Rust]
|
|
216
|
-
use dirsql::RowEvent;
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
```typescript [TypeScript]
|
|
220
|
-
import type { RowEvent } from 'dirsql';
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
:::
|
|
224
|
-
|
|
225
|
-
Emitted by the watch stream. Represents a change to a row in the database caused by a filesystem event.
|
|
226
|
-
|
|
227
|
-
**Attributes:**
|
|
228
|
-
|
|
229
|
-
| Attribute | Python | Rust | TypeScript |
|
|
230
|
-
|-----------|--------|------|------------|
|
|
231
|
-
| Table name | `table: str` | `table: String` | `table: string` |
|
|
232
|
-
| Action | `action: str` | `action: Action` | `action: string` |
|
|
233
|
-
| Current/new row | `row: dict \| None` | `row: Option<HashMap>` | `row?: Record` |
|
|
234
|
-
| Previous row | `old_row: dict \| None` | `old_row: Option<HashMap>` | `oldRow?: Record` |
|
|
235
|
-
| Error message | `error: str \| None` | `error: Option<String>` | `error?: string` |
|
|
236
|
-
| File path | `file_path: str \| None` | `file_path: Option<String>` | `filePath?: string` |
|
|
237
|
-
|
|
238
|
-
Action values: `"insert"`, `"update"`, `"delete"`, `"error"`.
|