dirsql 0.0.21 → 0.0.25
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/README.md +22 -204
- package/dirsql.node +0 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +129 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -3
- package/LICENSE +0 -21
- package/index.js +0 -5
package/README.md
CHANGED
|
@@ -1,225 +1,43 @@
|
|
|
1
|
-
# dirsql
|
|
1
|
+
# `dirsql` (TypeScript SDK)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Why
|
|
6
|
-
|
|
7
|
-
Structured data stored as flat files (JSONL, JSON) is easy to read, write, diff, and version. But querying across many files is slow -- "show me all unresolved comments across 50 documents" requires opening and parsing every file.
|
|
8
|
-
|
|
9
|
-
dirsql bridges this gap: files remain the source of truth, but you get SQL queries and real-time change events for free.
|
|
3
|
+
TypeScript SDK for `dirsql` -- napi-rs bindings wrapping the Rust core (`dirsql`).
|
|
10
4
|
|
|
11
5
|
## Installation
|
|
12
6
|
|
|
13
7
|
```bash
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Rust (library only, no Python bindings):
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
cargo add dirsql
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Quick Start
|
|
24
|
-
|
|
25
|
-
```python
|
|
26
|
-
import json
|
|
27
|
-
import os
|
|
28
|
-
import tempfile
|
|
29
|
-
from dirsql import DirSQL, Table
|
|
30
|
-
|
|
31
|
-
# Create some data files
|
|
32
|
-
root = tempfile.mkdtemp()
|
|
33
|
-
os.makedirs(os.path.join(root, "comments", "abc"), exist_ok=True)
|
|
34
|
-
os.makedirs(os.path.join(root, "comments", "def"), exist_ok=True)
|
|
35
|
-
|
|
36
|
-
with open(os.path.join(root, "comments", "abc", "index.jsonl"), "w") as f:
|
|
37
|
-
f.write(json.dumps({"body": "looks good", "author": "alice"}) + "\n")
|
|
38
|
-
f.write(json.dumps({"body": "needs work", "author": "bob"}) + "\n")
|
|
39
|
-
|
|
40
|
-
with open(os.path.join(root, "comments", "def", "index.jsonl"), "w") as f:
|
|
41
|
-
f.write(json.dumps({"body": "agreed", "author": "carol"}) + "\n")
|
|
42
|
-
|
|
43
|
-
# Define a table: DDL, glob pattern, and an extract function
|
|
44
|
-
db = DirSQL(
|
|
45
|
-
root,
|
|
46
|
-
tables=[
|
|
47
|
-
Table(
|
|
48
|
-
ddl="CREATE TABLE comments (id TEXT, body TEXT, author TEXT)",
|
|
49
|
-
glob="comments/**/index.jsonl",
|
|
50
|
-
extract=lambda path, content: [
|
|
51
|
-
{
|
|
52
|
-
"id": os.path.basename(os.path.dirname(path)),
|
|
53
|
-
"body": row["body"],
|
|
54
|
-
"author": row["author"],
|
|
55
|
-
}
|
|
56
|
-
for line in content.splitlines()
|
|
57
|
-
for row in [json.loads(line)]
|
|
58
|
-
],
|
|
59
|
-
),
|
|
60
|
-
],
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
# Query with SQL
|
|
64
|
-
results = db.query("SELECT * FROM comments WHERE author = 'alice'")
|
|
65
|
-
# [{"id": "abc", "body": "looks good", "author": "alice"}]
|
|
8
|
+
pnpm add dirsql
|
|
66
9
|
```
|
|
67
10
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
```python
|
|
71
|
-
db = DirSQL(
|
|
72
|
-
root,
|
|
73
|
-
tables=[
|
|
74
|
-
Table(
|
|
75
|
-
ddl="CREATE TABLE posts (title TEXT, author_id TEXT)",
|
|
76
|
-
glob="posts/*.json",
|
|
77
|
-
extract=lambda path, content: [json.loads(content)],
|
|
78
|
-
),
|
|
79
|
-
Table(
|
|
80
|
-
ddl="CREATE TABLE authors (id TEXT, name TEXT)",
|
|
81
|
-
glob="authors/*.json",
|
|
82
|
-
extract=lambda path, content: [json.loads(content)],
|
|
83
|
-
),
|
|
84
|
-
],
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
results = db.query("""
|
|
88
|
-
SELECT posts.title, authors.name
|
|
89
|
-
FROM posts JOIN authors ON posts.author_id = authors.id
|
|
90
|
-
""")
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Async API
|
|
94
|
-
|
|
95
|
-
`AsyncDirSQL` wraps the synchronous API for use with asyncio. Initialization is awaitable, and `watch()` returns an async iterator of row-level change events.
|
|
11
|
+
Requires a native build step (Rust toolchain). The native module is compiled during `pnpm build`.
|
|
96
12
|
|
|
97
|
-
|
|
98
|
-
import asyncio
|
|
99
|
-
import json
|
|
100
|
-
import os
|
|
101
|
-
from dirsql import AsyncDirSQL, Table
|
|
13
|
+
## Usage
|
|
102
14
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
"/path/to/data",
|
|
106
|
-
tables=[
|
|
107
|
-
Table(
|
|
108
|
-
ddl="CREATE TABLE items (name TEXT)",
|
|
109
|
-
glob="**/*.json",
|
|
110
|
-
extract=lambda path, content: [json.loads(content)],
|
|
111
|
-
),
|
|
112
|
-
],
|
|
113
|
-
)
|
|
114
|
-
await db.ready() # wait for initial scan to complete
|
|
15
|
+
```typescript
|
|
16
|
+
import { DirSQL } from "dirsql";
|
|
115
17
|
|
|
116
|
-
|
|
117
|
-
|
|
18
|
+
const db = new DirSQL("/path/to/directory", [
|
|
19
|
+
{
|
|
20
|
+
ddl: "CREATE TABLE users (name TEXT, age INTEGER)",
|
|
21
|
+
glob: "data/*.json",
|
|
22
|
+
extract: (filePath, content) => JSON.parse(content),
|
|
23
|
+
},
|
|
24
|
+
]);
|
|
118
25
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
print(f"{event.action} on {event.table}: {event.row}")
|
|
122
|
-
if event.action == "error":
|
|
123
|
-
print(f" error: {event.error}")
|
|
124
|
-
|
|
125
|
-
asyncio.run(main())
|
|
26
|
+
const rows = db.query("SELECT * FROM users WHERE age > 25");
|
|
27
|
+
console.log(rows);
|
|
126
28
|
```
|
|
127
29
|
|
|
128
|
-
##
|
|
129
|
-
|
|
130
|
-
Pass `ignore` patterns to skip files during scanning and watching:
|
|
30
|
+
## Building
|
|
131
31
|
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
ignore=["**/drafts/**", "**/.git/**"],
|
|
136
|
-
tables=[...],
|
|
137
|
-
)
|
|
32
|
+
```bash
|
|
33
|
+
pnpm install
|
|
34
|
+
pnpm build
|
|
138
35
|
```
|
|
139
36
|
|
|
140
|
-
##
|
|
141
|
-
|
|
142
|
-
### `Table(*, ddl, glob, extract)`
|
|
143
|
-
|
|
144
|
-
Defines how files map to a SQL table.
|
|
145
|
-
|
|
146
|
-
- **`ddl`** (`str`): A `CREATE TABLE` statement defining the schema.
|
|
147
|
-
- **`glob`** (`str`): A glob pattern matched against file paths relative to root.
|
|
148
|
-
- **`extract`** (`Callable[[str, str], list[dict]]`): A function receiving `(relative_path, file_content)` and returning a list of row dicts. Each dict's keys must match the DDL column names.
|
|
149
|
-
|
|
150
|
-
### `DirSQL(root, *, tables, ignore=None)`
|
|
151
|
-
|
|
152
|
-
Creates an in-memory SQLite database indexed from the directory at `root`.
|
|
153
|
-
|
|
154
|
-
- **`root`** (`str`): Path to the directory to index.
|
|
155
|
-
- **`tables`** (`list[Table]`): Table definitions.
|
|
156
|
-
- **`ignore`** (`list[str] | None`): Glob patterns for paths to skip.
|
|
157
|
-
|
|
158
|
-
#### `DirSQL.query(sql) -> list[dict]`
|
|
159
|
-
|
|
160
|
-
Execute a SQL query. Returns a list of dicts keyed by column name. Internal tracking columns (`_dirsql_*`) are excluded from results.
|
|
161
|
-
|
|
162
|
-
### `AsyncDirSQL(root, *, tables, ignore=None)`
|
|
163
|
-
|
|
164
|
-
Async wrapper. Constructor is sync (returns immediately). Call `await db.ready()` to wait for the initial scan.
|
|
165
|
-
|
|
166
|
-
#### `await AsyncDirSQL.ready()`
|
|
167
|
-
|
|
168
|
-
Wait for the initial scan to complete. Idempotent -- safe to call multiple times. Raises any exception that occurred during init.
|
|
169
|
-
|
|
170
|
-
#### `await AsyncDirSQL.query(sql) -> list[dict]`
|
|
171
|
-
|
|
172
|
-
Same as `DirSQL.query`, but async.
|
|
173
|
-
|
|
174
|
-
#### `AsyncDirSQL.watch() -> AsyncIterator[RowEvent]`
|
|
175
|
-
|
|
176
|
-
Returns an async iterator that yields `RowEvent` objects as files change on disk. Starts the filesystem watcher on first iteration.
|
|
177
|
-
|
|
178
|
-
### `RowEvent`
|
|
179
|
-
|
|
180
|
-
Emitted by `watch()` when a file change produces row-level diffs.
|
|
181
|
-
|
|
182
|
-
- **`table`** (`str`): The affected table name.
|
|
183
|
-
- **`action`** (`str`): One of `"insert"`, `"update"`, `"delete"`, `"error"`.
|
|
184
|
-
- **`row`** (`dict | None`): The new row (for insert/update) or deleted row (for delete).
|
|
185
|
-
- **`old_row`** (`dict | None`): The previous row (for update only).
|
|
186
|
-
- **`error`** (`str | None`): Error message (for error events).
|
|
187
|
-
- **`file_path`** (`str | None`): The relative file path that triggered the event.
|
|
188
|
-
|
|
189
|
-
## How It Works
|
|
190
|
-
|
|
191
|
-
The Rust core (`rusqlite` + `notify` + `walkdir`) does the heavy lifting:
|
|
192
|
-
|
|
193
|
-
1. **Startup scan**: Walks the directory tree, matches files to tables via glob patterns, calls the user-provided `extract` function for each file, and inserts rows into an in-memory SQLite database.
|
|
194
|
-
2. **File watching**: Uses the `notify` crate (inotify on Linux, FSEvents on macOS) to detect file creates, modifications, and deletions.
|
|
195
|
-
3. **Row diffing**: When a file changes, the new rows are diffed against the previous rows for that file, producing granular insert/update/delete events.
|
|
196
|
-
4. **Python bindings**: PyO3 exposes the Rust core as a native Python extension module. The async layer runs blocking operations in a thread pool via `asyncio.to_thread`.
|
|
197
|
-
|
|
198
|
-
The SQLite database is purely ephemeral -- it exists only in memory and is discarded when the `DirSQL` instance is garbage collected. The filesystem is always the source of truth.
|
|
199
|
-
|
|
200
|
-
## Development
|
|
201
|
-
|
|
202
|
-
### Prerequisites
|
|
203
|
-
|
|
204
|
-
- Rust (stable)
|
|
205
|
-
- Python >= 3.12
|
|
206
|
-
- [maturin](https://github.com/PyO3/maturin) for building the Python extension
|
|
207
|
-
- [just](https://github.com/casey/just) as a task runner
|
|
208
|
-
|
|
209
|
-
### Build and Test
|
|
37
|
+
## Testing
|
|
210
38
|
|
|
211
39
|
```bash
|
|
212
|
-
|
|
213
|
-
maturin develop
|
|
214
|
-
|
|
215
|
-
# Run all CI checks
|
|
216
|
-
just ci
|
|
217
|
-
|
|
218
|
-
# Individual targets
|
|
219
|
-
just test-rust # Rust unit tests
|
|
220
|
-
just test-integration # Python integration tests
|
|
221
|
-
just clippy # Rust lints
|
|
222
|
-
just lint # Python lints (ruff)
|
|
40
|
+
pnpm test
|
|
223
41
|
```
|
|
224
42
|
|
|
225
43
|
## License
|
package/dirsql.node
ADDED
|
Binary file
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/** Definition of a SQL-indexed table backed by files on disk. */
|
|
2
|
+
export interface TableDef {
|
|
3
|
+
/** SQL DDL statement, e.g. `CREATE TABLE users (name TEXT, age INTEGER)`. */
|
|
4
|
+
ddl: string;
|
|
5
|
+
/** Glob pattern (relative to the DirSQL root) for files backing this table. */
|
|
6
|
+
glob: string;
|
|
7
|
+
/** Extract rows from a file's contents. Returns an array of row objects. */
|
|
8
|
+
extract: (filePath: string, content: string) => Record<string, unknown>[];
|
|
9
|
+
/** If true, reject rows with columns not declared in `ddl`. */
|
|
10
|
+
strict?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** A row-level event emitted by the file watcher. */
|
|
13
|
+
export interface RowEvent {
|
|
14
|
+
table: string;
|
|
15
|
+
action: "insert" | "update" | "delete" | "error";
|
|
16
|
+
row?: Record<string, unknown> | null;
|
|
17
|
+
oldRow?: Record<string, unknown> | null;
|
|
18
|
+
error?: string | null;
|
|
19
|
+
filePath?: string | null;
|
|
20
|
+
}
|
|
21
|
+
interface NativeDirSQL {
|
|
22
|
+
query(sql: string): Record<string, unknown>[];
|
|
23
|
+
startWatcher(): void;
|
|
24
|
+
pollEvents(timeoutMs: number): RowEvent[];
|
|
25
|
+
}
|
|
26
|
+
interface NativeDirSQLConstructor {
|
|
27
|
+
new (root: string, tables: TableDef[], ignore?: string[]): NativeDirSQL;
|
|
28
|
+
fromConfig(configPath: string): NativeDirSQL;
|
|
29
|
+
}
|
|
30
|
+
interface CoreModule {
|
|
31
|
+
DirSQL: NativeDirSQLConstructor;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* **Test-only.** Replace the core module used by the SDK with a fake.
|
|
35
|
+
*
|
|
36
|
+
* This is an internal escape hatch for unit tests that want to mock the
|
|
37
|
+
* napi-rs binding layer without loading the real native binary. Passing
|
|
38
|
+
* `null` resets to the default (lazy native load on next access). Not
|
|
39
|
+
* part of the public API; do not use in application code.
|
|
40
|
+
*/
|
|
41
|
+
export declare function __setCoreForTesting(fake: CoreModule | null): void;
|
|
42
|
+
/**
|
|
43
|
+
* Ephemeral SQL index over a local directory.
|
|
44
|
+
*
|
|
45
|
+
* Constructing a `DirSQL` scans `root`, matches files against each
|
|
46
|
+
* {@link TableDef}'s `glob`, extracts rows via `extract`, and builds an
|
|
47
|
+
* in-memory SQLite database. `query` / `startWatcher` / `pollEvents`
|
|
48
|
+
* are synchronous; {@link ready} and {@link watch} expose the same
|
|
49
|
+
* surface in an async-idiomatic shape so TypeScript consumers don't
|
|
50
|
+
* need a separate `AsyncDirSQL` class.
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* const db = new DirSQL(root, tables);
|
|
54
|
+
* await db.ready;
|
|
55
|
+
* const rows = db.query("SELECT ...");
|
|
56
|
+
* for await (const event of db.watch()) { ... }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare class DirSQL {
|
|
60
|
+
/**
|
|
61
|
+
* Resolves once the initial directory scan has completed. Scanning
|
|
62
|
+
* runs synchronously inside the constructor, so this Promise is
|
|
63
|
+
* already resolved by the time the constructor returns; construction
|
|
64
|
+
* failures throw synchronously rather than surfacing here. Exposed
|
|
65
|
+
* as a Promise purely so consumers can write async-style code
|
|
66
|
+
* uniformly across SDKs.
|
|
67
|
+
*/
|
|
68
|
+
readonly ready: Promise<void>;
|
|
69
|
+
private _inner;
|
|
70
|
+
constructor(root: string, tables: TableDef[], ignore?: string[]);
|
|
71
|
+
/**
|
|
72
|
+
* Load a {@link DirSQL} instance from a `.dirsql.toml` config file.
|
|
73
|
+
*
|
|
74
|
+
* The root directory is derived from the config file's parent. Tables
|
|
75
|
+
* are parsed using the built-in parser for each format declared in the
|
|
76
|
+
* config. No JS `extract` callback is required.
|
|
77
|
+
*/
|
|
78
|
+
static fromConfig(configPath: string): DirSQL;
|
|
79
|
+
/** Execute a SQL query and return results as an array of row objects. */
|
|
80
|
+
query(sql: string): Record<string, unknown>[];
|
|
81
|
+
/**
|
|
82
|
+
* Start the file watcher. Must be called before {@link pollEvents}.
|
|
83
|
+
* Idempotent — safe to call multiple times.
|
|
84
|
+
*/
|
|
85
|
+
startWatcher(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Poll for file change events, blocking up to `timeoutMs` for the first
|
|
88
|
+
* event. Returns all events observed in the window (possibly empty).
|
|
89
|
+
*/
|
|
90
|
+
pollEvents(timeoutMs: number): RowEvent[];
|
|
91
|
+
/**
|
|
92
|
+
* Watch for file change events as an async iterable.
|
|
93
|
+
*
|
|
94
|
+
* ```ts
|
|
95
|
+
* for await (const event of db.watch()) { ... }
|
|
96
|
+
* ```
|
|
97
|
+
*
|
|
98
|
+
* Starts the underlying watcher on first iteration, then polls in 200ms
|
|
99
|
+
* increments and yields each {@link RowEvent}. The iterator runs
|
|
100
|
+
* indefinitely; break out of the `for await` loop to stop.
|
|
101
|
+
*/
|
|
102
|
+
watch(): AsyncGenerator<RowEvent, void, unknown>;
|
|
103
|
+
}
|
|
104
|
+
export {};
|
|
105
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../ts/index.ts"],"names":[],"mappings":"AAgBA,iEAAiE;AACjE,MAAM,WAAW,QAAQ;IACvB,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,OAAO,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAC1E,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,qDAAqD;AACrD,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACrC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAGD,UAAU,YAAY;IACpB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAC9C,YAAY,IAAI,IAAI,CAAC;IACrB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,EAAE,CAAC;CAC3C;AAED,UAAU,uBAAuB;IAC/B,KAAK,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,YAAY,CAAC;IACxE,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,CAAC;CAC9C;AAID,UAAU,UAAU;IAClB,MAAM,EAAE,uBAAuB,CAAC;CACjC;AAwBD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI,CAEjE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,MAAM;IACjB;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9B,OAAO,CAAC,MAAM,CAAe;gBAEjB,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE;IAS/D;;;;;;OAMG;IACH,MAAM,CAAC,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAW7C,yEAAyE;IACzE,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE;IAI7C;;;OAGG;IACH,YAAY,IAAI,IAAI;IAIpB;;;OAGG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,EAAE;IAIzC;;;;;;;;;;OAUG;IACI,KAAK,IAAI,cAAc,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC;CASxD"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// dirsql TypeScript SDK.
|
|
3
|
+
//
|
|
4
|
+
// The public surface is implemented in Rust via napi-rs. `pnpm build` runs
|
|
5
|
+
// `napi build` which produces `dirsql.node` at the package root, then
|
|
6
|
+
// `tsc` compiles this file to `dist/index.js` + `dist/index.d.ts`, which
|
|
7
|
+
// is what consumers import via the package's `main` / `types` / `exports`
|
|
8
|
+
// fields.
|
|
9
|
+
//
|
|
10
|
+
// The native binary lives at the package root (not in `dist/`) because
|
|
11
|
+
// that is where napi-rs writes it and where `napi prepublish` expects it.
|
|
12
|
+
// We resolve it relative to this file's location at runtime so the
|
|
13
|
+
// loader works whether the package is consumed via `node_modules/dirsql`
|
|
14
|
+
// or via a pnpm workspace self-reference from `test/`.
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.DirSQL = void 0;
|
|
17
|
+
exports.__setCoreForTesting = __setCoreForTesting;
|
|
18
|
+
const node_path_1 = require("node:path");
|
|
19
|
+
// Lazy-loaded reference to the core module. Populated on first access
|
|
20
|
+
// by `loadNativeCore()`, or by `__setCoreForTesting()` for tests.
|
|
21
|
+
let core = null;
|
|
22
|
+
/**
|
|
23
|
+
* Load the native napi-rs binary. Resolved relative to this compiled
|
|
24
|
+
* module: after `tsc` emits to `dist/`, `__dirname` is `<pkg>/dist`, so
|
|
25
|
+
* `..` reaches the package root where napi-rs writes `dirsql.node`.
|
|
26
|
+
*/
|
|
27
|
+
function loadNativeCore() {
|
|
28
|
+
const bindingPath = (0, node_path_1.join)(__dirname, "..", "dirsql.node");
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
30
|
+
return require(bindingPath);
|
|
31
|
+
}
|
|
32
|
+
function getCore() {
|
|
33
|
+
if (core === null) {
|
|
34
|
+
core = loadNativeCore();
|
|
35
|
+
}
|
|
36
|
+
return core;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* **Test-only.** Replace the core module used by the SDK with a fake.
|
|
40
|
+
*
|
|
41
|
+
* This is an internal escape hatch for unit tests that want to mock the
|
|
42
|
+
* napi-rs binding layer without loading the real native binary. Passing
|
|
43
|
+
* `null` resets to the default (lazy native load on next access). Not
|
|
44
|
+
* part of the public API; do not use in application code.
|
|
45
|
+
*/
|
|
46
|
+
function __setCoreForTesting(fake) {
|
|
47
|
+
core = fake;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Ephemeral SQL index over a local directory.
|
|
51
|
+
*
|
|
52
|
+
* Constructing a `DirSQL` scans `root`, matches files against each
|
|
53
|
+
* {@link TableDef}'s `glob`, extracts rows via `extract`, and builds an
|
|
54
|
+
* in-memory SQLite database. `query` / `startWatcher` / `pollEvents`
|
|
55
|
+
* are synchronous; {@link ready} and {@link watch} expose the same
|
|
56
|
+
* surface in an async-idiomatic shape so TypeScript consumers don't
|
|
57
|
+
* need a separate `AsyncDirSQL` class.
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
60
|
+
* const db = new DirSQL(root, tables);
|
|
61
|
+
* await db.ready;
|
|
62
|
+
* const rows = db.query("SELECT ...");
|
|
63
|
+
* for await (const event of db.watch()) { ... }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
class DirSQL {
|
|
67
|
+
constructor(root, tables, ignore) {
|
|
68
|
+
const Ctor = getCore().DirSQL;
|
|
69
|
+
this._inner =
|
|
70
|
+
ignore === undefined
|
|
71
|
+
? new Ctor(root, tables)
|
|
72
|
+
: new Ctor(root, tables, ignore);
|
|
73
|
+
this.ready = Promise.resolve();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Load a {@link DirSQL} instance from a `.dirsql.toml` config file.
|
|
77
|
+
*
|
|
78
|
+
* The root directory is derived from the config file's parent. Tables
|
|
79
|
+
* are parsed using the built-in parser for each format declared in the
|
|
80
|
+
* config. No JS `extract` callback is required.
|
|
81
|
+
*/
|
|
82
|
+
static fromConfig(configPath) {
|
|
83
|
+
const instance = Object.create(DirSQL.prototype);
|
|
84
|
+
const writable = instance;
|
|
85
|
+
writable._inner = getCore().DirSQL.fromConfig(configPath);
|
|
86
|
+
writable.ready = Promise.resolve();
|
|
87
|
+
return instance;
|
|
88
|
+
}
|
|
89
|
+
/** Execute a SQL query and return results as an array of row objects. */
|
|
90
|
+
query(sql) {
|
|
91
|
+
return this._inner.query(sql);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Start the file watcher. Must be called before {@link pollEvents}.
|
|
95
|
+
* Idempotent — safe to call multiple times.
|
|
96
|
+
*/
|
|
97
|
+
startWatcher() {
|
|
98
|
+
this._inner.startWatcher();
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Poll for file change events, blocking up to `timeoutMs` for the first
|
|
102
|
+
* event. Returns all events observed in the window (possibly empty).
|
|
103
|
+
*/
|
|
104
|
+
pollEvents(timeoutMs) {
|
|
105
|
+
return this._inner.pollEvents(timeoutMs);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Watch for file change events as an async iterable.
|
|
109
|
+
*
|
|
110
|
+
* ```ts
|
|
111
|
+
* for await (const event of db.watch()) { ... }
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* Starts the underlying watcher on first iteration, then polls in 200ms
|
|
115
|
+
* increments and yields each {@link RowEvent}. The iterator runs
|
|
116
|
+
* indefinitely; break out of the `for await` loop to stop.
|
|
117
|
+
*/
|
|
118
|
+
async *watch() {
|
|
119
|
+
this._inner.startWatcher();
|
|
120
|
+
while (true) {
|
|
121
|
+
const events = this._inner.pollEvents(200);
|
|
122
|
+
for (const event of events) {
|
|
123
|
+
yield event;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.DirSQL = DirSQL;
|
|
129
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../ts/index.ts"],"names":[],"mappings":";AAAA,yBAAyB;AACzB,EAAE;AACF,2EAA2E;AAC3E,sEAAsE;AACtE,yEAAyE;AACzE,0EAA0E;AAC1E,UAAU;AACV,EAAE;AACF,uEAAuE;AACvE,0EAA0E;AAC1E,mEAAmE;AACnE,yEAAyE;AACzE,uDAAuD;;;AA0EvD,kDAEC;AA1ED,yCAAiC;AA0CjC,sEAAsE;AACtE,kEAAkE;AAClE,IAAI,IAAI,GAAsB,IAAI,CAAC;AAEnC;;;;GAIG;AACH,SAAS,cAAc;IACrB,MAAM,WAAW,GAAG,IAAA,gBAAI,EAAC,SAAS,EAAE,IAAI,EAAE,aAAa,CAAC,CAAC;IACzD,iEAAiE;IACjE,OAAO,OAAO,CAAC,WAAW,CAAe,CAAC;AAC5C,CAAC;AAED,SAAS,OAAO;IACd,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,IAAI,GAAG,cAAc,EAAE,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,mBAAmB,CAAC,IAAuB;IACzD,IAAI,GAAG,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAa,MAAM;IAajB,YAAY,IAAY,EAAE,MAAkB,EAAE,MAAiB;QAC7D,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC,MAAM,CAAC;QAC9B,IAAI,CAAC,MAAM;YACT,MAAM,KAAK,SAAS;gBAClB,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC;gBACxB,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IACjC,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,UAAU,CAAC,UAAkB;QAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAW,CAAC;QAC3D,MAAM,QAAQ,GAAG,QAGhB,CAAC;QACF,QAAQ,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QAC1D,QAAQ,CAAC,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QACnC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,yEAAyE;IACzE,KAAK,CAAC,GAAW;QACf,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAED;;;OAGG;IACH,YAAY;QACV,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;IAC7B,CAAC;IAED;;;OAGG;IACH,UAAU,CAAC,SAAiB;QAC1B,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAC3C,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,CAAC,KAAK;QACV,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;QAC3B,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YAC3C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAjFD,wBAiFC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dirsql",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"description": "Ephemeral SQL index over a local directory",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "https://github.com/thekevinscott/dirsql",
|
|
7
|
-
"main": "index.js"
|
|
8
|
-
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/",
|
|
17
|
+
"dirsql.node"
|
|
18
|
+
],
|
|
19
|
+
"napi": {
|
|
20
|
+
"binaryName": "dirsql",
|
|
21
|
+
"triples": {}
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"artifacts": "napi artifacts",
|
|
25
|
+
"build": "napi build --release && tsc",
|
|
26
|
+
"build:debug": "napi build && tsc",
|
|
27
|
+
"prepublishOnly": "napi prepublish -t npm",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:unit": "vitest run --dir src --passWithNoTests",
|
|
30
|
+
"test:integration": "vitest run --dir test --passWithNoTests",
|
|
31
|
+
"coverage": "vitest run --coverage",
|
|
32
|
+
"lint": "biome check .",
|
|
33
|
+
"lint:fix": "biome check --write .",
|
|
34
|
+
"format": "biome format --write .",
|
|
35
|
+
"format:check": "biome format ."
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@biomejs/biome": "^1.9.0",
|
|
39
|
+
"@napi-rs/cli": "^3.0.0",
|
|
40
|
+
"@types/node": "^22.0.0",
|
|
41
|
+
"typescript": "^5.8.0",
|
|
42
|
+
"vitest": "^3.1.1",
|
|
43
|
+
"@vitest/coverage-v8": "^3.1.1"
|
|
44
|
+
},
|
|
45
|
+
"pnpm": {
|
|
46
|
+
"onlyBuiltDependencies": [
|
|
47
|
+
"@biomejs/biome",
|
|
48
|
+
"esbuild"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"optionalDependencies": {}
|
|
52
|
+
}
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Kevin Scott
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|