dirsql 0.3.58__tar.gz → 0.3.60__tar.gz

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.
Files changed (126) hide show
  1. {dirsql-0.3.58 → dirsql-0.3.60}/Cargo.lock +1 -1
  2. {dirsql-0.3.58 → dirsql-0.3.60}/PKG-INFO +1 -1
  3. dirsql-0.3.60/docs/AGENTS.md +95 -0
  4. {dirsql-0.3.58 → dirsql-0.3.60}/docs/cli/config.md +23 -5
  5. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/Cargo.toml +1 -1
  6. dirsql-0.3.60/packages/python/docs/AGENTS.md +95 -0
  7. {dirsql-0.3.58/packages/rust → dirsql-0.3.60/packages/python}/docs/cli/config.md +23 -5
  8. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/e2e-attestation.json +2 -2
  9. {dirsql-0.3.58/packages/python → dirsql-0.3.60/packages/rust}/docs/cli/config.md +23 -5
  10. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/bin/dirsql.rs +14 -4
  11. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/cli/mod.rs +45 -4
  12. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/cli/router.rs +10 -15
  13. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/command.rs +5 -0
  14. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/config.rs +118 -15
  15. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/db.rs +70 -389
  16. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/lib.rs +27 -10
  17. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/persist.rs +8 -7
  18. dirsql-0.3.58/docs/AGENTS.md +0 -63
  19. dirsql-0.3.58/packages/python/docs/AGENTS.md +0 -63
  20. {dirsql-0.3.58 → dirsql-0.3.60}/Cargo.toml +0 -0
  21. {dirsql-0.3.58 → dirsql-0.3.60}/README.md +0 -0
  22. {dirsql-0.3.58 → dirsql-0.3.60}/dirsql/__init__.py +0 -0
  23. {dirsql-0.3.58 → dirsql-0.3.60}/dirsql/_async.py +0 -0
  24. {dirsql-0.3.58 → dirsql-0.3.60}/dirsql/_dirsql.pyi +0 -0
  25. {dirsql-0.3.58 → dirsql-0.3.60}/dirsql/cli/__init__.py +0 -0
  26. {dirsql-0.3.58 → dirsql-0.3.60}/dirsql/cli/binary_path.py +0 -0
  27. {dirsql-0.3.58 → dirsql-0.3.60}/dirsql/cli/interpret/__init__.py +0 -0
  28. {dirsql-0.3.58 → dirsql-0.3.60}/dirsql/cli/is_windows.py +0 -0
  29. {dirsql-0.3.58 → dirsql-0.3.60}/dirsql/cli/main.py +0 -0
  30. {dirsql-0.3.58 → dirsql-0.3.60}/dirsql/cli/resolve_config_extensions.py +0 -0
  31. {dirsql-0.3.58 → dirsql-0.3.60}/dirsql/py.typed +0 -0
  32. {dirsql-0.3.58 → dirsql-0.3.60}/dirsql/resolve_config_extensions.py +0 -0
  33. {dirsql-0.3.58 → dirsql-0.3.60}/dirsql/resolve_extension.py +0 -0
  34. {dirsql-0.3.58 → dirsql-0.3.60}/docs/.claude/CLAUDE.md +0 -0
  35. {dirsql-0.3.58 → dirsql-0.3.60}/docs/.vitepress/config.ts +0 -0
  36. {dirsql-0.3.58 → dirsql-0.3.60}/docs/.vitepress/theme/index.ts +0 -0
  37. {dirsql-0.3.58 → dirsql-0.3.60}/docs/.vitepress/theme/lang.ts +0 -0
  38. {dirsql-0.3.58 → dirsql-0.3.60}/docs/api/index.md +0 -0
  39. {dirsql-0.3.58 → dirsql-0.3.60}/docs/cli/http-api.md +0 -0
  40. {dirsql-0.3.58 → dirsql-0.3.60}/docs/cli/index.md +0 -0
  41. {dirsql-0.3.58 → dirsql-0.3.60}/docs/cli/init.md +0 -0
  42. {dirsql-0.3.58 → dirsql-0.3.60}/docs/cli/server.md +0 -0
  43. {dirsql-0.3.58 → dirsql-0.3.60}/docs/getting-started.md +0 -0
  44. {dirsql-0.3.58 → dirsql-0.3.60}/docs/guide/async.md +0 -0
  45. {dirsql-0.3.58 → dirsql-0.3.60}/docs/guide/crdt.md +0 -0
  46. {dirsql-0.3.58 → dirsql-0.3.60}/docs/guide/persistence.md +0 -0
  47. {dirsql-0.3.58 → dirsql-0.3.60}/docs/guide/querying.md +0 -0
  48. {dirsql-0.3.58 → dirsql-0.3.60}/docs/guide/tables.md +0 -0
  49. {dirsql-0.3.58 → dirsql-0.3.60}/docs/guide/watching.md +0 -0
  50. {dirsql-0.3.58 → dirsql-0.3.60}/docs/index.md +0 -0
  51. {dirsql-0.3.58 → dirsql-0.3.60}/docs/migrations.md +0 -0
  52. {dirsql-0.3.58 → dirsql-0.3.60}/docs/package.json +0 -0
  53. {dirsql-0.3.58 → dirsql-0.3.60}/docs/playwright.config.ts +0 -0
  54. {dirsql-0.3.58 → dirsql-0.3.60}/docs/pnpm-lock.yaml +0 -0
  55. {dirsql-0.3.58 → dirsql-0.3.60}/docs/pnpm-workspace.yaml +0 -0
  56. {dirsql-0.3.58 → dirsql-0.3.60}/docs/tests/integration/home.spec.ts +0 -0
  57. {dirsql-0.3.58 → dirsql-0.3.60}/docs/tests/integration/language-flag.spec.ts +0 -0
  58. {dirsql-0.3.58 → dirsql-0.3.60}/docs/tests/integration/sidebar.spec.ts +0 -0
  59. {dirsql-0.3.58 → dirsql-0.3.60}/docs/tests/unit/config.test.ts +0 -0
  60. {dirsql-0.3.58 → dirsql-0.3.60}/docs/tests/unit/lang.test.ts +0 -0
  61. {dirsql-0.3.58 → dirsql-0.3.60}/docs/vitest.config.ts +0 -0
  62. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/README.md +0 -0
  63. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/conftest.py +0 -0
  64. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/.claude/CLAUDE.md +0 -0
  65. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/.vitepress/config.ts +0 -0
  66. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  67. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  68. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/api/index.md +0 -0
  69. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/cli/http-api.md +0 -0
  70. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/cli/index.md +0 -0
  71. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/cli/init.md +0 -0
  72. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/cli/server.md +0 -0
  73. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/getting-started.md +0 -0
  74. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/guide/async.md +0 -0
  75. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/guide/crdt.md +0 -0
  76. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/guide/persistence.md +0 -0
  77. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/guide/querying.md +0 -0
  78. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/guide/tables.md +0 -0
  79. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/guide/watching.md +0 -0
  80. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/index.md +0 -0
  81. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/migrations.md +0 -0
  82. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/package.json +0 -0
  83. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/playwright.config.ts +0 -0
  84. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/pnpm-lock.yaml +0 -0
  85. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/pnpm-workspace.yaml +0 -0
  86. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/tests/integration/home.spec.ts +0 -0
  87. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
  88. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/tests/integration/sidebar.spec.ts +0 -0
  89. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/tests/unit/config.test.ts +0 -0
  90. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/tests/unit/lang.test.ts +0 -0
  91. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/docs/vitest.config.ts +0 -0
  92. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/src/lib.rs +0 -0
  93. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/tests/__init__.py +0 -0
  94. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/tests/binding/__init__.py +0 -0
  95. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/tests/conftest.py +0 -0
  96. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/tests/e2e/__init__.py +0 -0
  97. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/tests/integration/__init__.py +0 -0
  98. {dirsql-0.3.58 → dirsql-0.3.60}/packages/python/tests/smoke/__init__.py +0 -0
  99. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/Cargo.toml +0 -0
  100. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/README.md +0 -0
  101. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/benches/db_bench.rs +0 -0
  102. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/benches/differ_bench.rs +0 -0
  103. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/benches/matcher_bench.rs +0 -0
  104. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/benches/scanner_bench.rs +0 -0
  105. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/api/index.md +0 -0
  106. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/cli/http-api.md +0 -0
  107. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/cli/index.md +0 -0
  108. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/cli/init.md +0 -0
  109. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/cli/server.md +0 -0
  110. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/getting-started.md +0 -0
  111. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/guide/async.md +0 -0
  112. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/guide/crdt.md +0 -0
  113. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/guide/persistence.md +0 -0
  114. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/guide/querying.md +0 -0
  115. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/guide/tables.md +0 -0
  116. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/guide/watching.md +0 -0
  117. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/index.md +0 -0
  118. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/docs/migrations.md +0 -0
  119. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/cli/init.rs +0 -0
  120. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/cli/serialize.rs +0 -0
  121. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/cli/server.rs +0 -0
  122. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/differ.rs +0 -0
  123. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/matcher.rs +0 -0
  124. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/scanner.rs +0 -0
  125. {dirsql-0.3.58 → dirsql-0.3.60}/packages/rust/src/watcher.rs +0 -0
  126. {dirsql-0.3.58 → dirsql-0.3.60}/pyproject.toml +0 -0
@@ -500,7 +500,7 @@ dependencies = [
500
500
 
501
501
  [[package]]
502
502
  name = "dirsql-py-ext"
503
- version = "0.3.58"
503
+ version = "0.3.60"
504
504
  dependencies = [
505
505
  "dirsql",
506
506
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.3.58
3
+ Version: 0.3.60
4
4
  Requires-Dist: pytest>=8 ; extra == 'dev'
5
5
  Requires-Dist: pytest-describe>=2 ; extra == 'dev'
6
6
  Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
@@ -0,0 +1,95 @@
1
+ # Documentation Development
2
+
3
+ Instructions for agents working on `dirsql` documentation.
4
+
5
+ ## Stack
6
+
7
+ The docs site uses [VitePress](https://vitepress.dev/). Source files are in `docs/` at the project root.
8
+
9
+ ## Running locally
10
+
11
+ ```bash
12
+ cd docs
13
+ pnpm install
14
+ pnpm dev
15
+ ```
16
+
17
+ This starts a local dev server (default: `http://localhost:5173/dirsql/`). The site hot-reloads on file changes.
18
+
19
+ ## Building
20
+
21
+ ```bash
22
+ cd docs
23
+ pnpm build
24
+ ```
25
+
26
+ The build must succeed before pushing. VitePress will fail on broken links, missing assets, and syntax errors in markdown.
27
+
28
+ ## Testing changes
29
+
30
+ Before pushing any docs changes:
31
+
32
+ 1. Run `pnpm build` in `docs/` and confirm it exits cleanly
33
+ 2. Spot-check the built output with `pnpm preview`
34
+ 3. Verify sidebar navigation, code blocks, and internal links render correctly
35
+
36
+ ## Structure
37
+
38
+ The docs follow the [Diataxis](https://diataxis.fr/) framework. **Type is
39
+ the only organizational axis** -- there are no product-area sections (no
40
+ "CLI" section; #353). The nav and the sidebar mirror the four types exactly.
41
+
42
+ The **primary reader is the CLI user**: someone with a directory of files,
43
+ one command (`uvx` / `npx dirsql`), and a `.dirsql.toml`. The SDKs are the
44
+ secondary audience and appear **only in Reference**, plus the single
45
+ "Embed `dirsql` in your application" how-to.
46
+
47
+ Target tree (the spec for #353; existing pages are *quarried* into it, not
48
+ migrated -- a page survives only if a slot wants its content):
49
+
50
+ - **Tutorial** -- one lesson: *Your first dirsql database*. The reader
51
+ performs every step and sees output at each one; success is
52
+ author-guaranteed (toy dataset, no branching).
53
+ - **How-to Guides** -- goal-named recipes: define tables for your files;
54
+ derive columns from file paths; extract rows from file contents
55
+ (`on-file`); search documents by meaning; skip files; load a SQLite
56
+ extension; keep the index across restarts; react to file changes; embed
57
+ `dirsql` in an application.
58
+ - **Reference** -- CLI flags and defaults; the complete `.dirsql.toml`
59
+ schema; the command hook contract (placeholders, stdout protocol, exit
60
+ codes, timeouts); virtual columns and glob captures; the HTTP API;
61
+ per-language SDK pages (the sole SDK home).
62
+ - **Explanation** -- one page: how `dirsql` thinks (the filesystem is the
63
+ source of truth; the database is a derived, ephemeral, read-only view;
64
+ reconcile and diffing). Its canonical home is the root `ARCHITECTURE.md`;
65
+ #374 surfaces it via an include page, the same mechanism
66
+ `docs/migrations.md` uses for `MIGRATIONS.md`. Edit the root file, never
67
+ a rendered include.
68
+
69
+ Working rules:
70
+
71
+ - **Facts live once, in Reference.** Tutorials and how-tos link to reference
72
+ material; they never re-list constructor parameters or duplicate API tables.
73
+ - **A how-to opens with a 1-2 line goal/motivation statement.** Deep
74
+ rationale (tradeoffs, alternatives considered, theory) moves to Explanation
75
+ only when it is substantial enough to stand alone and is reused across
76
+ pages. Do not manufacture stub pages for a paragraph of "why".
77
+ - **Tutorial vs how-to:** a tutorial is a lesson along a path the author
78
+ guarantees; a how-to serves a competent reader pursuing their own goal.
79
+ - **One global sidebar.** Never add a path-scoped sidebar key -- it swaps
80
+ out the whole tree and hides every other section while inside one (#301;
81
+ see the comment above `sidebar` in `config.ts`).
82
+
83
+ ## Conventions
84
+
85
+ - **Lead with the use case.** Open each feature description with *why* a
86
+ reader would reach for it before *how* it works. Don't frame a feature
87
+ by what an adjacent feature can't do.
88
+ *Don't:* "Persistence avoids the thing the default mode can't do..."
89
+ *Do:* "Persistence keeps the SQLite index on disk between runs so large
90
+ directories don't re-scan on every startup."
91
+ - Wrap `dirsql` in backticks in all prose text
92
+ - Use VitePress [code group](https://vitepress.dev/guide/markdown#code-groups) syntax (`::: code-group`) for multi-language examples with `Python`, `Rust`, and `TypeScript` tabs
93
+ - Internal links use relative paths (e.g., `./guide/tables.md`)
94
+ - The VitePress config is at `docs/.vitepress/config.ts`
95
+ - The site is deployed under the `/dirsql/` base path
@@ -244,6 +244,13 @@ file is skipped (it contributes no rows) and a one-line warning naming the file
244
244
  and the error is written to stderr. One bad file never aborts the scan; the
245
245
  other files' rows are indexed normally.
246
246
 
247
+ **Timeout.** Each run is bounded by the default 30-second timeout. When a
248
+ command legitimately needs longer — it downloads a model on first run, or calls
249
+ a rate-limited API — raise the bound with the global
250
+ [`[dirsql].hook-timeout`](#command-execution) key (positive whole seconds). A
251
+ run exceeding the bound is killed and handled with the per-file error isolation
252
+ above (the file is skipped with a stderr warning).
253
+
247
254
  See [Command execution](#command-execution) for the full contract (argv
248
255
  splitting, injection safety, cwd, environment, timeout, and output framing).
249
256
 
@@ -279,7 +286,8 @@ key and the `{"sql": …}` contract returns.
279
286
  **On failure** — a non-zero exit, a timeout, or a spawn error — the request
280
287
  returns `500 Internal Server Error` with the command's stderr tail in the JSON
281
288
  `error` body. The command runs in the config file's directory and is bounded by
282
- a fixed **30-second** timeout.
289
+ a **30-second** timeout by default; a slow translator (e.g. LLM-backed) can
290
+ raise it with the global [`[dirsql].hook-timeout`](#command-execution) key.
283
291
 
284
292
  #### The hook owns SQL safety
285
293
 
@@ -361,7 +369,8 @@ returns.
361
369
  **On failure** — a non-zero exit, a timeout, or a spawn error — the request
362
370
  returns `500 Internal Server Error` with the command's stderr tail in the JSON
363
371
  `error` body. The command runs in the config file's directory and is bounded by
364
- a fixed **30-second** timeout.
372
+ a **30-second** timeout by default; raise it with the global
373
+ [`[dirsql].hook-timeout`](#command-execution) key.
365
374
 
366
375
  See [Command execution](#command-execution) for the full contract (argv
367
376
  splitting, injection safety, cwd, environment, timeout, and output framing).
@@ -409,9 +418,18 @@ Config keys that run an external command — today `on-file`, `pre-query`, and
409
418
  - **Output framing.** The command's result is the **last non-empty line of
410
419
  stdout**; any log/chatter lines above it are ignored. stderr is never data —
411
420
  it is captured only to enrich error messages.
412
- - **Timeout.** Each command run is bounded by a fixed **30-second** timeout (no
413
- per-table override yet); a command that exceeds it is killed and treated as a
414
- failure.
421
+ - **Timeout.** Each command run is bounded by a **30-second** timeout by
422
+ default; a command that exceeds it is killed and treated as a failure. Raise
423
+ (or tighten) it for **all** hooks — `on-file`, `pre-query`, and `post-query`
424
+ alike — with a single global `[dirsql].hook-timeout` key, in positive whole
425
+ seconds:
426
+
427
+ ```toml
428
+ [dirsql]
429
+ hook-timeout = 300
430
+ ```
431
+
432
+ Zero and negative values are rejected as a config error.
415
433
  - **Errors.** A non-zero exit, a timeout, a spawn failure, or output that does
416
434
  not parse as expected is a per-file failure: the file is skipped with a
417
435
  stderr warning and the scan continues.
@@ -4,7 +4,7 @@ name = "dirsql-py-ext"
4
4
  # pypi/maturin handler can rewrite it via `write-version` before
5
5
  # `maturin build`. `pyproject.toml` declares `dynamic = ["version"]`
6
6
  # and maturin reads this field. Mirrors `packages/rust/Cargo.toml`.
7
- version = "0.3.58"
7
+ version = "0.3.60"
8
8
  edition.workspace = true
9
9
  publish = false
10
10
  readme = "README.md"
@@ -0,0 +1,95 @@
1
+ # Documentation Development
2
+
3
+ Instructions for agents working on `dirsql` documentation.
4
+
5
+ ## Stack
6
+
7
+ The docs site uses [VitePress](https://vitepress.dev/). Source files are in `docs/` at the project root.
8
+
9
+ ## Running locally
10
+
11
+ ```bash
12
+ cd docs
13
+ pnpm install
14
+ pnpm dev
15
+ ```
16
+
17
+ This starts a local dev server (default: `http://localhost:5173/dirsql/`). The site hot-reloads on file changes.
18
+
19
+ ## Building
20
+
21
+ ```bash
22
+ cd docs
23
+ pnpm build
24
+ ```
25
+
26
+ The build must succeed before pushing. VitePress will fail on broken links, missing assets, and syntax errors in markdown.
27
+
28
+ ## Testing changes
29
+
30
+ Before pushing any docs changes:
31
+
32
+ 1. Run `pnpm build` in `docs/` and confirm it exits cleanly
33
+ 2. Spot-check the built output with `pnpm preview`
34
+ 3. Verify sidebar navigation, code blocks, and internal links render correctly
35
+
36
+ ## Structure
37
+
38
+ The docs follow the [Diataxis](https://diataxis.fr/) framework. **Type is
39
+ the only organizational axis** -- there are no product-area sections (no
40
+ "CLI" section; #353). The nav and the sidebar mirror the four types exactly.
41
+
42
+ The **primary reader is the CLI user**: someone with a directory of files,
43
+ one command (`uvx` / `npx dirsql`), and a `.dirsql.toml`. The SDKs are the
44
+ secondary audience and appear **only in Reference**, plus the single
45
+ "Embed `dirsql` in your application" how-to.
46
+
47
+ Target tree (the spec for #353; existing pages are *quarried* into it, not
48
+ migrated -- a page survives only if a slot wants its content):
49
+
50
+ - **Tutorial** -- one lesson: *Your first dirsql database*. The reader
51
+ performs every step and sees output at each one; success is
52
+ author-guaranteed (toy dataset, no branching).
53
+ - **How-to Guides** -- goal-named recipes: define tables for your files;
54
+ derive columns from file paths; extract rows from file contents
55
+ (`on-file`); search documents by meaning; skip files; load a SQLite
56
+ extension; keep the index across restarts; react to file changes; embed
57
+ `dirsql` in an application.
58
+ - **Reference** -- CLI flags and defaults; the complete `.dirsql.toml`
59
+ schema; the command hook contract (placeholders, stdout protocol, exit
60
+ codes, timeouts); virtual columns and glob captures; the HTTP API;
61
+ per-language SDK pages (the sole SDK home).
62
+ - **Explanation** -- one page: how `dirsql` thinks (the filesystem is the
63
+ source of truth; the database is a derived, ephemeral, read-only view;
64
+ reconcile and diffing). Its canonical home is the root `ARCHITECTURE.md`;
65
+ #374 surfaces it via an include page, the same mechanism
66
+ `docs/migrations.md` uses for `MIGRATIONS.md`. Edit the root file, never
67
+ a rendered include.
68
+
69
+ Working rules:
70
+
71
+ - **Facts live once, in Reference.** Tutorials and how-tos link to reference
72
+ material; they never re-list constructor parameters or duplicate API tables.
73
+ - **A how-to opens with a 1-2 line goal/motivation statement.** Deep
74
+ rationale (tradeoffs, alternatives considered, theory) moves to Explanation
75
+ only when it is substantial enough to stand alone and is reused across
76
+ pages. Do not manufacture stub pages for a paragraph of "why".
77
+ - **Tutorial vs how-to:** a tutorial is a lesson along a path the author
78
+ guarantees; a how-to serves a competent reader pursuing their own goal.
79
+ - **One global sidebar.** Never add a path-scoped sidebar key -- it swaps
80
+ out the whole tree and hides every other section while inside one (#301;
81
+ see the comment above `sidebar` in `config.ts`).
82
+
83
+ ## Conventions
84
+
85
+ - **Lead with the use case.** Open each feature description with *why* a
86
+ reader would reach for it before *how* it works. Don't frame a feature
87
+ by what an adjacent feature can't do.
88
+ *Don't:* "Persistence avoids the thing the default mode can't do..."
89
+ *Do:* "Persistence keeps the SQLite index on disk between runs so large
90
+ directories don't re-scan on every startup."
91
+ - Wrap `dirsql` in backticks in all prose text
92
+ - Use VitePress [code group](https://vitepress.dev/guide/markdown#code-groups) syntax (`::: code-group`) for multi-language examples with `Python`, `Rust`, and `TypeScript` tabs
93
+ - Internal links use relative paths (e.g., `./guide/tables.md`)
94
+ - The VitePress config is at `docs/.vitepress/config.ts`
95
+ - The site is deployed under the `/dirsql/` base path
@@ -244,6 +244,13 @@ file is skipped (it contributes no rows) and a one-line warning naming the file
244
244
  and the error is written to stderr. One bad file never aborts the scan; the
245
245
  other files' rows are indexed normally.
246
246
 
247
+ **Timeout.** Each run is bounded by the default 30-second timeout. When a
248
+ command legitimately needs longer — it downloads a model on first run, or calls
249
+ a rate-limited API — raise the bound with the global
250
+ [`[dirsql].hook-timeout`](#command-execution) key (positive whole seconds). A
251
+ run exceeding the bound is killed and handled with the per-file error isolation
252
+ above (the file is skipped with a stderr warning).
253
+
247
254
  See [Command execution](#command-execution) for the full contract (argv
248
255
  splitting, injection safety, cwd, environment, timeout, and output framing).
249
256
 
@@ -279,7 +286,8 @@ key and the `{"sql": …}` contract returns.
279
286
  **On failure** — a non-zero exit, a timeout, or a spawn error — the request
280
287
  returns `500 Internal Server Error` with the command's stderr tail in the JSON
281
288
  `error` body. The command runs in the config file's directory and is bounded by
282
- a fixed **30-second** timeout.
289
+ a **30-second** timeout by default; a slow translator (e.g. LLM-backed) can
290
+ raise it with the global [`[dirsql].hook-timeout`](#command-execution) key.
283
291
 
284
292
  #### The hook owns SQL safety
285
293
 
@@ -361,7 +369,8 @@ returns.
361
369
  **On failure** — a non-zero exit, a timeout, or a spawn error — the request
362
370
  returns `500 Internal Server Error` with the command's stderr tail in the JSON
363
371
  `error` body. The command runs in the config file's directory and is bounded by
364
- a fixed **30-second** timeout.
372
+ a **30-second** timeout by default; raise it with the global
373
+ [`[dirsql].hook-timeout`](#command-execution) key.
365
374
 
366
375
  See [Command execution](#command-execution) for the full contract (argv
367
376
  splitting, injection safety, cwd, environment, timeout, and output framing).
@@ -409,9 +418,18 @@ Config keys that run an external command — today `on-file`, `pre-query`, and
409
418
  - **Output framing.** The command's result is the **last non-empty line of
410
419
  stdout**; any log/chatter lines above it are ignored. stderr is never data —
411
420
  it is captured only to enrich error messages.
412
- - **Timeout.** Each command run is bounded by a fixed **30-second** timeout (no
413
- per-table override yet); a command that exceeds it is killed and treated as a
414
- failure.
421
+ - **Timeout.** Each command run is bounded by a **30-second** timeout by
422
+ default; a command that exceeds it is killed and treated as a failure. Raise
423
+ (or tighten) it for **all** hooks — `on-file`, `pre-query`, and `post-query`
424
+ alike — with a single global `[dirsql].hook-timeout` key, in positive whole
425
+ seconds:
426
+
427
+ ```toml
428
+ [dirsql]
429
+ hook-timeout = 300
430
+ ```
431
+
432
+ Zero and negative values are rejected as a config error.
415
433
  - **Errors.** A non-zero exit, a timeout, a spawn failure, or output that does
416
434
  not parse as expected is a per-file failure: the file is skipped with a
417
435
  stderr warning and the scan continues.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "command": "uv run python -m pytest tests/e2e/ -x -q",
3
- "ran_at": 1783118080,
3
+ "ran_at": 1783167859,
4
4
  "exit_code": 0,
5
- "commit": "b45791b9ad04fbac360618f3ff885e5f84a8289e"
5
+ "commit": "edd031c6e37ab86d3e7906b6e67b22d7c37ba43d"
6
6
  }
@@ -244,6 +244,13 @@ file is skipped (it contributes no rows) and a one-line warning naming the file
244
244
  and the error is written to stderr. One bad file never aborts the scan; the
245
245
  other files' rows are indexed normally.
246
246
 
247
+ **Timeout.** Each run is bounded by the default 30-second timeout. When a
248
+ command legitimately needs longer — it downloads a model on first run, or calls
249
+ a rate-limited API — raise the bound with the global
250
+ [`[dirsql].hook-timeout`](#command-execution) key (positive whole seconds). A
251
+ run exceeding the bound is killed and handled with the per-file error isolation
252
+ above (the file is skipped with a stderr warning).
253
+
247
254
  See [Command execution](#command-execution) for the full contract (argv
248
255
  splitting, injection safety, cwd, environment, timeout, and output framing).
249
256
 
@@ -279,7 +286,8 @@ key and the `{"sql": …}` contract returns.
279
286
  **On failure** — a non-zero exit, a timeout, or a spawn error — the request
280
287
  returns `500 Internal Server Error` with the command's stderr tail in the JSON
281
288
  `error` body. The command runs in the config file's directory and is bounded by
282
- a fixed **30-second** timeout.
289
+ a **30-second** timeout by default; a slow translator (e.g. LLM-backed) can
290
+ raise it with the global [`[dirsql].hook-timeout`](#command-execution) key.
283
291
 
284
292
  #### The hook owns SQL safety
285
293
 
@@ -361,7 +369,8 @@ returns.
361
369
  **On failure** — a non-zero exit, a timeout, or a spawn error — the request
362
370
  returns `500 Internal Server Error` with the command's stderr tail in the JSON
363
371
  `error` body. The command runs in the config file's directory and is bounded by
364
- a fixed **30-second** timeout.
372
+ a **30-second** timeout by default; raise it with the global
373
+ [`[dirsql].hook-timeout`](#command-execution) key.
365
374
 
366
375
  See [Command execution](#command-execution) for the full contract (argv
367
376
  splitting, injection safety, cwd, environment, timeout, and output framing).
@@ -409,9 +418,18 @@ Config keys that run an external command — today `on-file`, `pre-query`, and
409
418
  - **Output framing.** The command's result is the **last non-empty line of
410
419
  stdout**; any log/chatter lines above it are ignored. stderr is never data —
411
420
  it is captured only to enrich error messages.
412
- - **Timeout.** Each command run is bounded by a fixed **30-second** timeout (no
413
- per-table override yet); a command that exceeds it is killed and treated as a
414
- failure.
421
+ - **Timeout.** Each command run is bounded by a **30-second** timeout by
422
+ default; a command that exceeds it is killed and treated as a failure. Raise
423
+ (or tighten) it for **all** hooks — `on-file`, `pre-query`, and `post-query`
424
+ alike — with a single global `[dirsql].hook-timeout` key, in positive whole
425
+ seconds:
426
+
427
+ ```toml
428
+ [dirsql]
429
+ hook-timeout = 300
430
+ ```
431
+
432
+ Zero and negative values are rejected as a config error.
415
433
  - **Errors.** A non-zero exit, a timeout, a spawn failure, or output that does
416
434
  not parse as expected is a per-file failure: the file is skipped with a
417
435
  stderr warning and the scan continues.
@@ -228,9 +228,14 @@ fn load_pre_query(cli: &Cli) -> Option<PreQuery> {
228
228
  return None;
229
229
  }
230
230
  let resolved = config_path.canonicalize().ok()?;
231
- let command = dirsql::config::load_config(&resolved).ok()?.pre_query?;
231
+ let config = dirsql::config::load_config(&resolved).ok()?;
232
+ let command = config.pre_query?;
232
233
  let config_dir = resolved.parent()?.to_path_buf();
233
- Some(PreQuery::new(command, config_dir))
234
+ let mut pre_query = PreQuery::new(command, config_dir);
235
+ if let Some(timeout) = config.hook_timeout {
236
+ pre_query = pre_query.with_timeout(timeout);
237
+ }
238
+ Some(pre_query)
234
239
  }
235
240
 
236
241
  /// Extract the server-wide `post-query` hook from the config, if any.
@@ -245,9 +250,14 @@ fn load_post_query(cli: &Cli) -> Option<PostQuery> {
245
250
  return None;
246
251
  }
247
252
  let resolved = config_path.canonicalize().ok()?;
248
- let command = dirsql::config::load_config(&resolved).ok()?.post_query?;
253
+ let config = dirsql::config::load_config(&resolved).ok()?;
254
+ let command = config.post_query?;
249
255
  let config_dir = resolved.parent()?.to_path_buf();
250
- Some(PostQuery::new(command, config_dir))
256
+ let mut post_query = PostQuery::new(command, config_dir);
257
+ if let Some(timeout) = config.hook_timeout {
258
+ post_query = post_query.with_timeout(timeout);
259
+ }
260
+ Some(post_query)
251
261
  }
252
262
 
253
263
  /// Zero-config fallback. When no `.dirsql.toml` is found, dirsql indexes the
@@ -27,6 +27,7 @@ use tokio::task::JoinError;
27
27
  use tokio::task::JoinHandle;
28
28
 
29
29
  use crate::DirSQL;
30
+ use crate::command::DEFAULT_COMMAND_TIMEOUT;
30
31
 
31
32
  pub mod init;
32
33
  pub mod router;
@@ -51,16 +52,29 @@ pub struct PreQuery {
51
52
  pub command: String,
52
53
  /// The command's working directory — the config file's parent.
53
54
  pub config_dir: PathBuf,
55
+ /// Per-run timeout. Defaults to the shared 30-second
56
+ /// [`DEFAULT_COMMAND_TIMEOUT`]; override it via [`Self::with_timeout`]
57
+ /// (the CLI wires the global `[dirsql].hook-timeout` here, #351).
58
+ pub timeout: Duration,
54
59
  }
55
60
 
56
61
  impl PreQuery {
57
- /// Build a [`PreQuery`] from a command template and its working directory.
62
+ /// Build a [`PreQuery`] from a command template and its working directory,
63
+ /// with the default 30-second timeout.
58
64
  pub fn new(command: impl Into<String>, config_dir: impl Into<PathBuf>) -> Self {
59
65
  Self {
60
66
  command: command.into(),
61
67
  config_dir: config_dir.into(),
68
+ timeout: DEFAULT_COMMAND_TIMEOUT,
62
69
  }
63
70
  }
71
+
72
+ /// Override the per-run timeout (from the global `[dirsql].hook-timeout`).
73
+ /// A run exceeding it is killed and the request returns 500.
74
+ pub fn with_timeout(mut self, timeout: Duration) -> Self {
75
+ self.timeout = timeout;
76
+ self
77
+ }
64
78
  }
65
79
 
66
80
  /// A server-wide `post-query` command hook, carrying the command template plus
@@ -76,16 +90,29 @@ pub struct PostQuery {
76
90
  pub command: String,
77
91
  /// The command's working directory — the config file's parent.
78
92
  pub config_dir: PathBuf,
93
+ /// Per-run timeout. Defaults to the shared 30-second
94
+ /// [`DEFAULT_COMMAND_TIMEOUT`]; override it via [`Self::with_timeout`]
95
+ /// (the CLI wires the global `[dirsql].hook-timeout` here, #351).
96
+ pub timeout: Duration,
79
97
  }
80
98
 
81
99
  impl PostQuery {
82
- /// Build a [`PostQuery`] from a command template and its working directory.
100
+ /// Build a [`PostQuery`] from a command template and its working directory,
101
+ /// with the default 30-second timeout.
83
102
  pub fn new(command: impl Into<String>, config_dir: impl Into<PathBuf>) -> Self {
84
103
  Self {
85
104
  command: command.into(),
86
105
  config_dir: config_dir.into(),
106
+ timeout: DEFAULT_COMMAND_TIMEOUT,
87
107
  }
88
108
  }
109
+
110
+ /// Override the per-run timeout (from the global `[dirsql].hook-timeout`).
111
+ /// A run exceeding it is killed and the request returns 500.
112
+ pub fn with_timeout(mut self, timeout: Duration) -> Self {
113
+ self.timeout = timeout;
114
+ self
115
+ }
89
116
  }
90
117
 
91
118
  /// Configure how the server binds. Defaults to `localhost:7117` with a
@@ -258,10 +285,17 @@ mod tests {
258
285
  #[test]
259
286
  fn pre_query_constructor_carries_command_and_dir() {
260
287
  // `PreQuery::new` is pure data plumbing: the command template and the
261
- // working directory it will run in.
288
+ // working directory it will run in, with the shared default timeout.
262
289
  let pq = PreQuery::new("to_sql.py {args}", "/proj");
263
290
  assert_eq!(pq.command, "to_sql.py {args}");
264
291
  assert_eq!(pq.config_dir, PathBuf::from("/proj"));
292
+ assert_eq!(pq.timeout, Duration::from_secs(30));
293
+ }
294
+
295
+ #[test]
296
+ fn pre_query_with_timeout_overrides_the_default() {
297
+ let pq = PreQuery::new("cmd {args}", "/proj").with_timeout(Duration::from_secs(60));
298
+ assert_eq!(pq.timeout, Duration::from_secs(60));
265
299
  }
266
300
 
267
301
  #[test]
@@ -275,10 +309,17 @@ mod tests {
275
309
  #[test]
276
310
  fn post_query_constructor_carries_command_and_dir() {
277
311
  // `PostQuery::new` is pure data plumbing: the command template and the
278
- // working directory it will run in.
312
+ // working directory it will run in, with the shared default timeout.
279
313
  let pq = PostQuery::new("jq '{results: .}'", "/proj");
280
314
  assert_eq!(pq.command, "jq '{results: .}'");
281
315
  assert_eq!(pq.config_dir, PathBuf::from("/proj"));
316
+ assert_eq!(pq.timeout, Duration::from_secs(30));
317
+ }
318
+
319
+ #[test]
320
+ fn post_query_with_timeout_overrides_the_default() {
321
+ let pq = PostQuery::new("reshape {args}", "/proj").with_timeout(Duration::from_secs(60));
322
+ assert_eq!(pq.timeout, Duration::from_secs(60));
282
323
  }
283
324
 
284
325
  #[test]
@@ -21,15 +21,6 @@ use super::{AppState, PostQuery, PreQuery};
21
21
  use crate::command::{Placeholder, run_command};
22
22
  use crate::{DirSQL, DirSqlError};
23
23
 
24
- /// Fixed timeout for a server-wide `pre-query` command. There is no override
25
- /// key yet; this module constant is the documented current default (mirrors
26
- /// `on-file`'s `ON_FILE_TIMEOUT`).
27
- const PRE_QUERY_TIMEOUT: Duration = Duration::from_secs(30);
28
-
29
- /// Fixed timeout for a server-wide `post-query` command (mirrors
30
- /// `PRE_QUERY_TIMEOUT`).
31
- const POST_QUERY_TIMEOUT: Duration = Duration::from_secs(30);
32
-
33
24
  /// Cap on the serialized result payload passed as the `{args}` argv token.
34
25
  /// Beyond this, `{args}` is emptied and the operator is directed to stdin
35
26
  /// (which always carries the full payload) — comfortably under Linux's 128 KiB
@@ -155,15 +146,17 @@ fn parse_sql_body(body: &str) -> Result<String, Response> {
155
146
  async fn run_pre_query(pq: &PreQuery, raw_body: String) -> Result<String, Response> {
156
147
  let command = pq.command.clone();
157
148
  let config_dir = pq.config_dir.clone();
149
+ let timeout = pq.timeout;
158
150
  // `run_command` is blocking — it spawns a child and joins drain threads —
159
- // so run it off the async runtime. It enforces `PRE_QUERY_TIMEOUT`
160
- // internally, so no outer `tokio::time::timeout` is needed.
151
+ // so run it off the async runtime. It enforces the hook's timeout
152
+ // (the global `[dirsql].hook-timeout`, default 30s) internally, so no outer
153
+ // `tokio::time::timeout` is needed.
161
154
  let outcome = tokio::task::spawn_blocking(move || {
162
155
  run_command(
163
156
  &command,
164
157
  &[Placeholder::new("args", &raw_body)],
165
158
  &config_dir,
166
- PRE_QUERY_TIMEOUT,
159
+ timeout,
167
160
  None,
168
161
  )
169
162
  })
@@ -198,9 +191,11 @@ async fn run_post_query(
198
191
  .map_err(|err| error_response(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?;
199
192
  let command = pq.command.clone();
200
193
  let config_dir = pq.config_dir.clone();
194
+ let timeout = pq.timeout;
201
195
  // `run_command` is blocking — it spawns a child and joins drain threads —
202
- // so run it off the async runtime. It enforces `POST_QUERY_TIMEOUT`
203
- // internally, so no outer `tokio::time::timeout` is needed.
196
+ // so run it off the async runtime. It enforces the hook's timeout
197
+ // (the global `[dirsql].hook-timeout`, default 30s) internally, so no outer
198
+ // `tokio::time::timeout` is needed.
204
199
  let outcome = tokio::task::spawn_blocking(move || {
205
200
  let args_value = if payload.len() <= POST_QUERY_ARGS_MAX {
206
201
  payload.clone()
@@ -217,7 +212,7 @@ async fn run_post_query(
217
212
  &command,
218
213
  &[Placeholder::new("args", &args_value)],
219
214
  &config_dir,
220
- POST_QUERY_TIMEOUT,
215
+ timeout,
221
216
  Some(payload.as_bytes()),
222
217
  )
223
218
  })
@@ -36,6 +36,11 @@ use std::time::Duration;
36
36
 
37
37
  use wait_timeout::ChildExt;
38
38
 
39
+ /// The default timeout for every command-backed event (`on-file`,
40
+ /// `pre-query`, `post-query`) when the config declares no override
41
+ /// (`[dirsql].hook-timeout`, positive whole seconds).
42
+ pub const DEFAULT_COMMAND_TIMEOUT: Duration = Duration::from_secs(30);
43
+
39
44
  /// A named placeholder substituted into a command's argv.
40
45
  ///
41
46
  /// `name` is the bare identifier (no braces): a `name` of `path` matches the