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.
@@ -1,190 +1,257 @@
1
- ---
2
- canonical: https://thekevinscott.github.io/dirsql/getting-started
3
- ---
1
+ # Your first dirsql database
4
2
 
5
- # Getting Started
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
- > Online: <https://thekevinscott.github.io/dirsql/getting-started>
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
- ## Installation
10
+ It takes about five minutes.
10
11
 
11
- ::: code-group
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
- ```bash [Python]
14
- pip install dirsql
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
- ```bash [TypeScript]
22
- # The npm CLI requires **Node ≥ 20.11**.
23
- npm add dirsql
69
+ ::: code-group
70
+
71
+ ```bash [npm]
72
+ npx dirsql
24
73
  ```
25
74
 
26
- ```bash [CLI]
27
- # Pick whichever install path you already have handy
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
- See the [CLI section](./cli/) for details on the command-line interface, and the [Rust library README](https://github.com/thekevinscott/dirsql/tree/main/packages/rust) for the library-vs-CLI feature split.
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
- ## Quick start
92
+ ## 3. Query your files
40
93
 
41
- Suppose you have a directory of JSON files representing blog posts:
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
- my-blog/
45
- posts/
46
- hello.json # {"title": "Hello World", "author": "alice"}
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
- Index and query them with `dirsql`:
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
- ::: code-group
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
- ```python [Python]
58
- import asyncio
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
- ddl: 'CREATE TABLE posts (title TEXT, author TEXT)',
151
- glob: 'posts/*.json',
152
- extract: (path) => [JSON.parse(readFileSync(path, 'utf8'))],
122
+ "_path": "notes/alice/ideas.md",
123
+ "_size": 52
153
124
  },
154
125
  {
155
- ddl: 'CREATE TABLE authors (id TEXT, name TEXT)',
156
- glob: 'authors/*.json',
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
- const db = new DirSQL({ root: './my-blog', tables });
141
+ You have a working SQL database over your files. Next, teach it the
142
+ structure your folders already encode.
162
143
 
163
- const posts = await db.query('SELECT * FROM posts');
144
+ ## 4. Define a table
164
145
 
165
- const results = await db.query(`
166
- SELECT posts.title, authors.name
167
- FROM posts JOIN authors ON posts.author = authors.id
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
- ## What happens at startup
184
+ ```
185
+ Running at localhost:7117
186
+ ```
174
187
 
175
- 1. `dirsql` walks the directory tree
176
- 2. Files matching each table's glob pattern are identified
177
- 3. The `extract` function receives each matched file's path (relative to the scan root, or absolute when `root` is absolute) and returns rows
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
- The filesystem is always the source of truth. The database is rebuilt from files at startup.
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
- ## Next steps
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
- - [Defining Tables](./guide/tables.md) -- DDL, globs, and extract functions in detail
186
- - [Querying](./guide/querying.md) -- SQL queries and return format
187
- - [File Watching](./guide/watching.md) -- real-time change events
188
- - [Async API](./guide/async.md) -- async ready(), query(), and watch()
189
- - [Using `dirsql` from the CLI](./cli/) -- `dirsql` runs an HTTP server (`POST /query`, `GET /events` SSE)
190
- - [Collaboration with CRDTs](./guide/crdt.md) -- multi-writer document merging alongside `dirsql`
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
@@ -1,5 +1,4 @@
1
1
  ---
2
- canonical: https://thekevinscott.github.io/dirsql/
3
2
  layout: home
4
3
  hero:
5
4
  name: dirsql
@@ -1,8 +1,5 @@
1
1
  ---
2
2
  title: Migrations
3
- canonical: https://thekevinscott.github.io/dirsql/migrations
4
3
  ---
5
4
 
6
- > Online: <https://thekevinscott.github.io/dirsql/migrations>
7
-
8
5
  <!--@include: ../MIGRATIONS.md-->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dirsql",
3
- "version": "0.3.65",
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.65",
234
- "@dirsql/lib-linux-arm64-gnu": "0.3.65",
235
- "@dirsql/lib-darwin-x64": "0.3.65",
236
- "@dirsql/lib-darwin-arm64": "0.3.65",
237
- "@dirsql/lib-win32-x64-msvc": "0.3.65",
238
- "@dirsql/cli-linux-x64-gnu": "0.3.65",
239
- "@dirsql/cli-linux-arm64-gnu": "0.3.65",
240
- "@dirsql/cli-darwin-x64": "0.3.65",
241
- "@dirsql/cli-darwin-arm64": "0.3.65",
242
- "@dirsql/cli-win32-x64-msvc": "0.3.65"
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"`.