dirsql 0.3.39__tar.gz → 0.3.41__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 (139) hide show
  1. {dirsql-0.3.39 → dirsql-0.3.41}/Cargo.lock +1 -1
  2. {dirsql-0.3.39 → dirsql-0.3.41}/PKG-INFO +1 -1
  3. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/_async.py +4 -5
  4. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/cli/interpret/run.py +20 -1
  5. {dirsql-0.3.39/packages/python → dirsql-0.3.41}/docs/.vitepress/config.ts +10 -13
  6. {dirsql-0.3.39/packages/python → dirsql-0.3.41}/docs/cli/config.md +12 -7
  7. {dirsql-0.3.39/packages/rust → dirsql-0.3.41}/docs/cli/index.md +1 -1
  8. dirsql-0.3.41/docs/tests/integration/sidebar.spec.ts +46 -0
  9. dirsql-0.3.41/docs/tests/unit/config.test.ts +49 -0
  10. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/Cargo.toml +1 -1
  11. {dirsql-0.3.39 → dirsql-0.3.41/packages/python}/docs/.vitepress/config.ts +10 -13
  12. {dirsql-0.3.39 → dirsql-0.3.41/packages/python}/docs/cli/config.md +12 -7
  13. {dirsql-0.3.39 → dirsql-0.3.41/packages/python}/docs/cli/index.md +1 -1
  14. dirsql-0.3.41/packages/python/docs/tests/integration/sidebar.spec.ts +46 -0
  15. dirsql-0.3.41/packages/python/docs/tests/unit/config.test.ts +49 -0
  16. dirsql-0.3.41/packages/python/e2e-attestation.json +6 -0
  17. dirsql-0.3.41/packages/python/tests/e2e/__fixtures__/interpret/dirsql.config_nested.py +13 -0
  18. dirsql-0.3.41/packages/python/tests/e2e/__fixtures__/interpret/dirsql.config_no_root.py +26 -0
  19. dirsql-0.3.41/packages/python/tests/e2e/__fixtures__/interpret/nested.dirsql.toml +6 -0
  20. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/cli/config.md +12 -7
  21. {dirsql-0.3.39/packages/python → dirsql-0.3.41/packages/rust}/docs/cli/index.md +1 -1
  22. dirsql-0.3.39/docs/tests/unit/config.test.ts +0 -22
  23. dirsql-0.3.39/packages/python/docs/tests/unit/config.test.ts +0 -22
  24. dirsql-0.3.39/packages/python/e2e-attestation.json +0 -6
  25. {dirsql-0.3.39 → dirsql-0.3.41}/Cargo.toml +0 -0
  26. {dirsql-0.3.39 → dirsql-0.3.41}/README.md +0 -0
  27. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/__init__.py +0 -0
  28. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/_dirsql.pyi +0 -0
  29. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/cli/__init__.py +0 -0
  30. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/cli/binary_path.py +0 -0
  31. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/cli/interpret/__init__.py +0 -0
  32. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/cli/interpret/dispatch_extract.py +0 -0
  33. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/cli/interpret/load_app.py +0 -0
  34. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/cli/interpret/write_message.py +0 -0
  35. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/cli/is_windows.py +0 -0
  36. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/cli/main.py +0 -0
  37. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/py.typed +0 -0
  38. {dirsql-0.3.39 → dirsql-0.3.41}/dirsql/resolve_config.py +0 -0
  39. {dirsql-0.3.39 → dirsql-0.3.41}/docs/.claude/CLAUDE.md +0 -0
  40. {dirsql-0.3.39 → dirsql-0.3.41}/docs/.vitepress/theme/index.ts +0 -0
  41. {dirsql-0.3.39 → dirsql-0.3.41}/docs/.vitepress/theme/lang.ts +0 -0
  42. {dirsql-0.3.39 → dirsql-0.3.41}/docs/AGENTS.md +0 -0
  43. {dirsql-0.3.39 → dirsql-0.3.41}/docs/api/index.md +0 -0
  44. {dirsql-0.3.39 → dirsql-0.3.41}/docs/cli/http-api.md +0 -0
  45. {dirsql-0.3.39 → dirsql-0.3.41}/docs/cli/init.md +0 -0
  46. {dirsql-0.3.39 → dirsql-0.3.41}/docs/cli/server.md +0 -0
  47. {dirsql-0.3.39 → dirsql-0.3.41}/docs/getting-started.md +0 -0
  48. {dirsql-0.3.39 → dirsql-0.3.41}/docs/guide/async.md +0 -0
  49. {dirsql-0.3.39 → dirsql-0.3.41}/docs/guide/crdt.md +0 -0
  50. {dirsql-0.3.39 → dirsql-0.3.41}/docs/guide/persistence.md +0 -0
  51. {dirsql-0.3.39 → dirsql-0.3.41}/docs/guide/querying.md +0 -0
  52. {dirsql-0.3.39 → dirsql-0.3.41}/docs/guide/tables.md +0 -0
  53. {dirsql-0.3.39 → dirsql-0.3.41}/docs/guide/watching.md +0 -0
  54. {dirsql-0.3.39 → dirsql-0.3.41}/docs/index.md +0 -0
  55. {dirsql-0.3.39 → dirsql-0.3.41}/docs/migrations.md +0 -0
  56. {dirsql-0.3.39 → dirsql-0.3.41}/docs/package.json +0 -0
  57. {dirsql-0.3.39 → dirsql-0.3.41}/docs/playwright.config.ts +0 -0
  58. {dirsql-0.3.39 → dirsql-0.3.41}/docs/pnpm-lock.yaml +0 -0
  59. {dirsql-0.3.39 → dirsql-0.3.41}/docs/pnpm-workspace.yaml +0 -0
  60. {dirsql-0.3.39 → dirsql-0.3.41}/docs/tests/integration/home.spec.ts +0 -0
  61. {dirsql-0.3.39 → dirsql-0.3.41}/docs/tests/integration/language-flag.spec.ts +0 -0
  62. {dirsql-0.3.39 → dirsql-0.3.41}/docs/tests/unit/lang.test.ts +0 -0
  63. {dirsql-0.3.39 → dirsql-0.3.41}/docs/vitest.config.ts +0 -0
  64. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/README.md +0 -0
  65. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/conftest.py +0 -0
  66. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/.claude/CLAUDE.md +0 -0
  67. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  68. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  69. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/AGENTS.md +0 -0
  70. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/api/index.md +0 -0
  71. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/cli/http-api.md +0 -0
  72. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/cli/init.md +0 -0
  73. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/cli/server.md +0 -0
  74. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/getting-started.md +0 -0
  75. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/guide/async.md +0 -0
  76. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/guide/crdt.md +0 -0
  77. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/guide/persistence.md +0 -0
  78. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/guide/querying.md +0 -0
  79. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/guide/tables.md +0 -0
  80. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/guide/watching.md +0 -0
  81. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/index.md +0 -0
  82. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/migrations.md +0 -0
  83. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/package.json +0 -0
  84. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/playwright.config.ts +0 -0
  85. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/pnpm-lock.yaml +0 -0
  86. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/pnpm-workspace.yaml +0 -0
  87. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/tests/integration/home.spec.ts +0 -0
  88. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/tests/integration/language-flag.spec.ts +0 -0
  89. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/tests/unit/lang.test.ts +0 -0
  90. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/docs/vitest.config.ts +0 -0
  91. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/src/lib.rs +0 -0
  92. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/tests/__init__.py +0 -0
  93. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/tests/conftest.py +0 -0
  94. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/tests/e2e/__fixtures__/data/a/meta.json +0 -0
  95. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/tests/e2e/__fixtures__/data/b/meta.json +0 -0
  96. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/tests/e2e/__fixtures__/dirsql.config.py +0 -0
  97. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/tests/e2e/__fixtures__/interpret/data/a/meta.json +0 -0
  98. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/tests/e2e/__fixtures__/interpret/data/b/meta.json +0 -0
  99. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/tests/e2e/__fixtures__/interpret/dirsql.config.py +0 -0
  100. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/tests/e2e/__fixtures__/interpret/dirsql.config_no_app.py +0 -0
  101. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/tests/e2e/__fixtures__/interpret/dirsql.config_raises.py +0 -0
  102. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/tests/e2e/__init__.py +0 -0
  103. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/tests/e2e/interpret_subprocess.py +0 -0
  104. {dirsql-0.3.39 → dirsql-0.3.41}/packages/python/tests/integration/__init__.py +0 -0
  105. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/Cargo.toml +0 -0
  106. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/README.md +0 -0
  107. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/benches/db_bench.rs +0 -0
  108. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/benches/differ_bench.rs +0 -0
  109. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/benches/matcher_bench.rs +0 -0
  110. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/benches/scanner_bench.rs +0 -0
  111. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/api/index.md +0 -0
  112. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/cli/http-api.md +0 -0
  113. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/cli/init.md +0 -0
  114. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/cli/server.md +0 -0
  115. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/getting-started.md +0 -0
  116. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/guide/async.md +0 -0
  117. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/guide/crdt.md +0 -0
  118. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/guide/persistence.md +0 -0
  119. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/guide/querying.md +0 -0
  120. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/guide/tables.md +0 -0
  121. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/guide/watching.md +0 -0
  122. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/index.md +0 -0
  123. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/docs/migrations.md +0 -0
  124. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/bin/dirsql.rs +0 -0
  125. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/cli/init.rs +0 -0
  126. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/cli/mod.rs +0 -0
  127. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/cli/native_config.rs +0 -0
  128. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/cli/router.rs +0 -0
  129. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/cli/serialize.rs +0 -0
  130. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/cli/server.rs +0 -0
  131. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/config.rs +0 -0
  132. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/db.rs +0 -0
  133. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/differ.rs +0 -0
  134. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/lib.rs +0 -0
  135. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/matcher.rs +0 -0
  136. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/persist.rs +0 -0
  137. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/scanner.rs +0 -0
  138. {dirsql-0.3.39 → dirsql-0.3.41}/packages/rust/src/watcher.rs +0 -0
  139. {dirsql-0.3.39 → dirsql-0.3.41}/pyproject.toml +0 -0
@@ -499,7 +499,7 @@ dependencies = [
499
499
 
500
500
  [[package]]
501
501
  name = "dirsql-py-ext"
502
- version = "0.3.39"
502
+ version = "0.3.41"
503
503
  dependencies = [
504
504
  "dirsql",
505
505
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.3.39
3
+ Version: 0.3.41
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'
@@ -55,9 +55,10 @@ class DirSQL:
55
55
  async for event in db.watch():
56
56
  ...
57
57
 
58
- At least one of ``root`` or ``config`` must be supplied. When both are
59
- set, the explicit ``root`` wins over any ``[dirsql].root`` in the config
60
- file (a warning is emitted on stderr).
58
+ Supply a ``root``, a ``config`` path, or both. When both are set, the
59
+ explicit ``root`` wins over any ``[dirsql].root`` in the config file (a
60
+ warning is emitted on stderr). If neither is given, the initial scan
61
+ fails with a "no root directory" error raised by the core.
61
62
 
62
63
  Pass ``persist=True`` to keep an on-disk SQLite cache (default location:
63
64
  ``<root>/.dirsql/cache.db``). Override the location with ``persist_path``.
@@ -79,8 +80,6 @@ class DirSQL:
79
80
  persist_path=None,
80
81
  extensions=None,
81
82
  ):
82
- if root is None and config is None:
83
- raise TypeError("DirSQL requires either a root directory or a config= path")
84
83
  self._root = root
85
84
  self._tables = tables
86
85
  self._ignore = ignore
@@ -20,6 +20,7 @@ line on stderr if the config can't be loaded.
20
20
  from __future__ import annotations
21
21
 
22
22
  import json
23
+ import os
23
24
  import sys
24
25
 
25
26
  from .dispatch_extract import dispatch_extract
@@ -41,6 +42,16 @@ def run(argv: list[str]) -> int:
41
42
  sys.stderr.write(f"dirsql interpret: {exc}\n")
42
43
  return 1
43
44
 
45
+ # A config file describes a single DirSQL; it must not itself delegate to
46
+ # another `config=` path. The interpret handshake has no field for a
47
+ # nested config and would recurse, so reject it up front.
48
+ if app._config is not None:
49
+ sys.stderr.write(
50
+ "dirsql interpret: a config file cannot itself set config= "
51
+ "(nested config is not supported)\n"
52
+ )
53
+ return 1
54
+
44
55
  # Name comes from `dirsql::db::parse_table_name` -- the canonical
45
56
  # core parser, surfaced via PyO3 on `Table.name` (#196). No regex
46
57
  # duplication on the Python side. Tables with a name the parser
@@ -49,7 +60,15 @@ def run(argv: list[str]) -> int:
49
60
  # `await app.ready()` would re-raise -- not interpret's job to
50
61
  # second-guess.
51
62
  tables = {t.name: t for t in (app._tables or []) if t.name is not None}
52
- write_message({"type": "config", "state": vars(app)})
63
+
64
+ # When the config supplies neither `root` nor `config=`, the resolved
65
+ # root is None. Default it to the process cwd -- the directory the
66
+ # `dirsql` command was launched from, which interpret inherits from the
67
+ # parent binary -- so a root-less config indexes "here".
68
+ state = vars(app)
69
+ if state.get("root") is None:
70
+ state["root"] = os.getcwd()
71
+ write_message({"type": "config", "state": state})
53
72
 
54
73
  for line in sys.stdin:
55
74
  stripped = line.strip()
@@ -19,19 +19,12 @@ export default defineConfig({
19
19
  { text: 'GitHub', link: 'https://github.com/thekevinscott/dirsql' }
20
20
  ],
21
21
 
22
+ // A single global sidebar shown on every page. The CLI section is a
23
+ // self-contained group with all its subpages -- but it never *replaces*
24
+ // the rest of the nav. There is intentionally no path-scoped (`/cli/`)
25
+ // key: a path-scoped sidebar swaps the whole tree out, which deletes the
26
+ // other sections when you enter CLI (see #301). Keep one sidebar.
22
27
  sidebar: {
23
- '/cli/': [
24
- {
25
- text: 'CLI',
26
- items: [
27
- { text: 'Overview & Installation', link: '/cli/' },
28
- { text: 'Running the Server', link: '/cli/server' },
29
- { text: 'Generating a Config (`init`)', link: '/cli/init' },
30
- { text: 'Configuration File', link: '/cli/config' },
31
- { text: 'HTTP API', link: '/cli/http-api' }
32
- ]
33
- }
34
- ],
35
28
  '/': [
36
29
  {
37
30
  text: 'Tutorials',
@@ -53,7 +46,11 @@ export default defineConfig({
53
46
  {
54
47
  text: 'CLI',
55
48
  items: [
56
- { text: 'Using `dirsql` from the CLI', link: '/cli/' }
49
+ { text: 'Overview & Installation', link: '/cli/' },
50
+ { text: 'Running the Server', link: '/cli/server' },
51
+ { text: 'Generating a Config (`init`)', link: '/cli/init' },
52
+ { text: 'Configuration File', link: '/cli/config' },
53
+ { text: 'HTTP API', link: '/cli/http-api' }
57
54
  ]
58
55
  },
59
56
  {
@@ -239,7 +239,7 @@ def extract_meta(path):
239
239
 
240
240
  # Python must export a module-level `app`.
241
241
  app = DirSQL(
242
- root="papers", # required see "Set a root" below
242
+ root="papers", # optional; defaults to the current directory
243
243
  tables=[
244
244
  Table(
245
245
  ddl="CREATE TABLE papers (title TEXT, _path TEXT)",
@@ -269,7 +269,7 @@ import { readFileSync } from "node:fs";
269
269
  import { DirSQL } from "dirsql";
270
270
 
271
271
  export default new DirSQL({
272
- root: "papers", // required see "Set a root" below
272
+ root: "papers", // optional; defaults to the current directory
273
273
  tables: [
274
274
  {
275
275
  ddl: "CREATE TABLE papers (title TEXT, _path TEXT)",
@@ -285,7 +285,7 @@ const { readFileSync } = require("node:fs");
285
285
  const { DirSQL } = require("dirsql");
286
286
 
287
287
  module.exports = new DirSQL({
288
- root: "papers", // required see "Set a root" below
288
+ root: "papers", // optional; defaults to the current directory
289
289
  tables: [
290
290
  {
291
291
  ddl: "CREATE TABLE papers (title TEXT, _path TEXT)",
@@ -308,10 +308,15 @@ These apply to both the Python and JavaScript forms above.
308
308
  package) uses `module.exports = new DirSQL(...)`. Only the extension
309
309
  matters — the file can be named anything; `dirsql.config.{py,mjs,cjs}` is the
310
310
  suggested convention, not a requirement.
311
- - **Set a `root`.** Unlike TOML configs (which default the scan root to the
312
- config file's directory), native-language configs require an explicit `root`.
313
- Without one the Python launcher errors and the JavaScript launcher silently
314
- indexes nothing.
311
+ - **`root` defaults to the current directory.** A native-language config with
312
+ no `root` indexes the process's current working directory the directory you
313
+ ran `dirsql` from. (This differs from TOML configs, which default the scan
314
+ root to the config file's own directory.) Pass `root` explicitly to index
315
+ somewhere else.
316
+ - **No nested `config=`.** A native-language config builds its `DirSQL` from
317
+ `tables` and an optional `root`; it must not itself set `config=` to delegate
318
+ to another config file. `dirsql interpret` rejects such a config (a nested
319
+ config can't be represented in the handshake and would recurse).
315
320
  - **Install the launcher on your `PATH`.** To run your `extract`, the server
316
321
  spawns `dirsql interpret`, so the matching `dirsql` launcher must be installed
317
322
  and on your `PATH` — a global `pip`/`uv` install for `.py`, or `npm` for
@@ -20,7 +20,7 @@ Everything you need to run `dirsql` as a CLI lives in this section:
20
20
  - **[Configuration File](./config.md)** — the `.dirsql.toml` format and the
21
21
  `.py` / `.js` native-language alternative. Custom tables
22
22
  are defined through a config file; without one, the server runs in
23
- [zero-config mode](./server.md#zero-config-mode).
23
+ [zero-config mode](./server.md#defaults).
24
24
  - **[HTTP API](./http-api.md)** — the `POST /query` and `GET /events`
25
25
  endpoints, status codes, and event streaming.
26
26
 
@@ -0,0 +1,46 @@
1
+ import { expect, test } from '@playwright/test'
2
+
3
+ // Regression for #301: entering the CLI section must NOT replace the sidebar.
4
+ // The full set of sections has to stay visible on every page, including
5
+ // `/cli/*`, so navigation never loses its place.
6
+ const SECTIONS = ['Tutorials', 'How-to Guides', 'CLI', 'Reference']
7
+
8
+ async function sidebarSections(page: import('@playwright/test').Page) {
9
+ return await page.evaluate(() =>
10
+ Array.from(document.querySelectorAll('.VPSidebar .group .text'))
11
+ .map((el) => el.textContent?.trim())
12
+ .filter(Boolean)
13
+ )
14
+ }
15
+
16
+ for (const path of ['guide/tables.html', 'cli/index.html', 'cli/server.html']) {
17
+ test(`every section stays in the sidebar on ${path}`, async ({ page }) => {
18
+ await page.goto(`./${path}`)
19
+ await page.waitForSelector('.VPSidebar .group')
20
+ const sections = await sidebarSections(page)
21
+ for (const section of SECTIONS) {
22
+ expect(sections).toContain(section)
23
+ }
24
+ })
25
+ }
26
+
27
+ test('the CLI sidebar group lists all CLI subpages', async ({ page }) => {
28
+ await page.goto('./cli/index.html')
29
+ await page.waitForSelector('.VPSidebar .group')
30
+ const cliLinks = await page.evaluate(() => {
31
+ const groups = Array.from(document.querySelectorAll('.VPSidebar .group'))
32
+ const cli = groups.find(
33
+ (g) => g.querySelector('.text')?.textContent?.trim() === 'CLI'
34
+ )
35
+ return Array.from(cli?.querySelectorAll('a') ?? []).map(
36
+ (a) => new URL((a as HTMLAnchorElement).href).pathname
37
+ )
38
+ })
39
+ expect(cliLinks).toEqual([
40
+ '/dirsql/cli/',
41
+ '/dirsql/cli/server.html',
42
+ '/dirsql/cli/init.html',
43
+ '/dirsql/cli/config.html',
44
+ '/dirsql/cli/http-api.html'
45
+ ])
46
+ })
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import config from '../../.vitepress/config'
3
+
4
+ type SidebarItem = { text?: string; link?: string; items?: SidebarItem[] }
5
+
6
+ describe('vitepress config', () => {
7
+ it('has the expected site title and base path', () => {
8
+ expect(config.title).toBe('dirsql')
9
+ expect(config.base).toBe('/dirsql/')
10
+ })
11
+
12
+ // Regression guard: CLI docs live in their own `/cli/` section, not
13
+ // interleaved with the SDK how-to guides (see #179).
14
+ it('keeps CLI pages out of the How-to Guides group', () => {
15
+ const sidebar = config.themeConfig!.sidebar as Record<string, SidebarItem[]>
16
+ const howTo = sidebar['/'].find((group) => group.text === 'How-to Guides')
17
+ const links = (howTo!.items ?? []).map((item) => item.link)
18
+ expect(links).not.toContain('/guide/cli')
19
+ expect(links).not.toContain('/guide/init')
20
+ expect(links).not.toContain('/guide/config')
21
+ })
22
+
23
+ // Regression guard: the sidebar must never *replace* itself when entering a
24
+ // section. A path-scoped key (e.g. `/cli/`) swaps the whole tree out, which
25
+ // deletes the other sections and reads as the nav breaking (see #301). There
26
+ // must be exactly one global `/` sidebar.
27
+ it('has a single global sidebar with no replacing path-scoped keys', () => {
28
+ const sidebar = config.themeConfig!.sidebar as Record<string, SidebarItem[]>
29
+ expect(Object.keys(sidebar)).toEqual(['/'])
30
+ })
31
+
32
+ // The CLI section is self-contained: its group carries all CLI subpages,
33
+ // alongside (never instead of) the other sections (see #301).
34
+ it('shows every CLI subpage in a self-contained CLI group', () => {
35
+ const sidebar = config.themeConfig!.sidebar as Record<string, SidebarItem[]>
36
+ const groupTexts = sidebar['/'].map((group) => group.text)
37
+ expect(groupTexts).toEqual(['Tutorials', 'How-to Guides', 'CLI', 'Reference'])
38
+
39
+ const cli = sidebar['/'].find((group) => group.text === 'CLI')
40
+ const links = (cli!.items ?? []).map((item) => item.link)
41
+ expect(links).toEqual([
42
+ '/cli/',
43
+ '/cli/server',
44
+ '/cli/init',
45
+ '/cli/config',
46
+ '/cli/http-api'
47
+ ])
48
+ })
49
+ })
@@ -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.39"
7
+ version = "0.3.41"
8
8
  edition.workspace = true
9
9
  publish = false
10
10
  readme = "README.md"
@@ -19,19 +19,12 @@ export default defineConfig({
19
19
  { text: 'GitHub', link: 'https://github.com/thekevinscott/dirsql' }
20
20
  ],
21
21
 
22
+ // A single global sidebar shown on every page. The CLI section is a
23
+ // self-contained group with all its subpages -- but it never *replaces*
24
+ // the rest of the nav. There is intentionally no path-scoped (`/cli/`)
25
+ // key: a path-scoped sidebar swaps the whole tree out, which deletes the
26
+ // other sections when you enter CLI (see #301). Keep one sidebar.
22
27
  sidebar: {
23
- '/cli/': [
24
- {
25
- text: 'CLI',
26
- items: [
27
- { text: 'Overview & Installation', link: '/cli/' },
28
- { text: 'Running the Server', link: '/cli/server' },
29
- { text: 'Generating a Config (`init`)', link: '/cli/init' },
30
- { text: 'Configuration File', link: '/cli/config' },
31
- { text: 'HTTP API', link: '/cli/http-api' }
32
- ]
33
- }
34
- ],
35
28
  '/': [
36
29
  {
37
30
  text: 'Tutorials',
@@ -53,7 +46,11 @@ export default defineConfig({
53
46
  {
54
47
  text: 'CLI',
55
48
  items: [
56
- { text: 'Using `dirsql` from the CLI', link: '/cli/' }
49
+ { text: 'Overview & Installation', link: '/cli/' },
50
+ { text: 'Running the Server', link: '/cli/server' },
51
+ { text: 'Generating a Config (`init`)', link: '/cli/init' },
52
+ { text: 'Configuration File', link: '/cli/config' },
53
+ { text: 'HTTP API', link: '/cli/http-api' }
57
54
  ]
58
55
  },
59
56
  {
@@ -239,7 +239,7 @@ def extract_meta(path):
239
239
 
240
240
  # Python must export a module-level `app`.
241
241
  app = DirSQL(
242
- root="papers", # required see "Set a root" below
242
+ root="papers", # optional; defaults to the current directory
243
243
  tables=[
244
244
  Table(
245
245
  ddl="CREATE TABLE papers (title TEXT, _path TEXT)",
@@ -269,7 +269,7 @@ import { readFileSync } from "node:fs";
269
269
  import { DirSQL } from "dirsql";
270
270
 
271
271
  export default new DirSQL({
272
- root: "papers", // required see "Set a root" below
272
+ root: "papers", // optional; defaults to the current directory
273
273
  tables: [
274
274
  {
275
275
  ddl: "CREATE TABLE papers (title TEXT, _path TEXT)",
@@ -285,7 +285,7 @@ const { readFileSync } = require("node:fs");
285
285
  const { DirSQL } = require("dirsql");
286
286
 
287
287
  module.exports = new DirSQL({
288
- root: "papers", // required see "Set a root" below
288
+ root: "papers", // optional; defaults to the current directory
289
289
  tables: [
290
290
  {
291
291
  ddl: "CREATE TABLE papers (title TEXT, _path TEXT)",
@@ -308,10 +308,15 @@ These apply to both the Python and JavaScript forms above.
308
308
  package) uses `module.exports = new DirSQL(...)`. Only the extension
309
309
  matters — the file can be named anything; `dirsql.config.{py,mjs,cjs}` is the
310
310
  suggested convention, not a requirement.
311
- - **Set a `root`.** Unlike TOML configs (which default the scan root to the
312
- config file's directory), native-language configs require an explicit `root`.
313
- Without one the Python launcher errors and the JavaScript launcher silently
314
- indexes nothing.
311
+ - **`root` defaults to the current directory.** A native-language config with
312
+ no `root` indexes the process's current working directory the directory you
313
+ ran `dirsql` from. (This differs from TOML configs, which default the scan
314
+ root to the config file's own directory.) Pass `root` explicitly to index
315
+ somewhere else.
316
+ - **No nested `config=`.** A native-language config builds its `DirSQL` from
317
+ `tables` and an optional `root`; it must not itself set `config=` to delegate
318
+ to another config file. `dirsql interpret` rejects such a config (a nested
319
+ config can't be represented in the handshake and would recurse).
315
320
  - **Install the launcher on your `PATH`.** To run your `extract`, the server
316
321
  spawns `dirsql interpret`, so the matching `dirsql` launcher must be installed
317
322
  and on your `PATH` — a global `pip`/`uv` install for `.py`, or `npm` for
@@ -20,7 +20,7 @@ Everything you need to run `dirsql` as a CLI lives in this section:
20
20
  - **[Configuration File](./config.md)** — the `.dirsql.toml` format and the
21
21
  `.py` / `.js` native-language alternative. Custom tables
22
22
  are defined through a config file; without one, the server runs in
23
- [zero-config mode](./server.md#zero-config-mode).
23
+ [zero-config mode](./server.md#defaults).
24
24
  - **[HTTP API](./http-api.md)** — the `POST /query` and `GET /events`
25
25
  endpoints, status codes, and event streaming.
26
26
 
@@ -0,0 +1,46 @@
1
+ import { expect, test } from '@playwright/test'
2
+
3
+ // Regression for #301: entering the CLI section must NOT replace the sidebar.
4
+ // The full set of sections has to stay visible on every page, including
5
+ // `/cli/*`, so navigation never loses its place.
6
+ const SECTIONS = ['Tutorials', 'How-to Guides', 'CLI', 'Reference']
7
+
8
+ async function sidebarSections(page: import('@playwright/test').Page) {
9
+ return await page.evaluate(() =>
10
+ Array.from(document.querySelectorAll('.VPSidebar .group .text'))
11
+ .map((el) => el.textContent?.trim())
12
+ .filter(Boolean)
13
+ )
14
+ }
15
+
16
+ for (const path of ['guide/tables.html', 'cli/index.html', 'cli/server.html']) {
17
+ test(`every section stays in the sidebar on ${path}`, async ({ page }) => {
18
+ await page.goto(`./${path}`)
19
+ await page.waitForSelector('.VPSidebar .group')
20
+ const sections = await sidebarSections(page)
21
+ for (const section of SECTIONS) {
22
+ expect(sections).toContain(section)
23
+ }
24
+ })
25
+ }
26
+
27
+ test('the CLI sidebar group lists all CLI subpages', async ({ page }) => {
28
+ await page.goto('./cli/index.html')
29
+ await page.waitForSelector('.VPSidebar .group')
30
+ const cliLinks = await page.evaluate(() => {
31
+ const groups = Array.from(document.querySelectorAll('.VPSidebar .group'))
32
+ const cli = groups.find(
33
+ (g) => g.querySelector('.text')?.textContent?.trim() === 'CLI'
34
+ )
35
+ return Array.from(cli?.querySelectorAll('a') ?? []).map(
36
+ (a) => new URL((a as HTMLAnchorElement).href).pathname
37
+ )
38
+ })
39
+ expect(cliLinks).toEqual([
40
+ '/dirsql/cli/',
41
+ '/dirsql/cli/server.html',
42
+ '/dirsql/cli/init.html',
43
+ '/dirsql/cli/config.html',
44
+ '/dirsql/cli/http-api.html'
45
+ ])
46
+ })
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import config from '../../.vitepress/config'
3
+
4
+ type SidebarItem = { text?: string; link?: string; items?: SidebarItem[] }
5
+
6
+ describe('vitepress config', () => {
7
+ it('has the expected site title and base path', () => {
8
+ expect(config.title).toBe('dirsql')
9
+ expect(config.base).toBe('/dirsql/')
10
+ })
11
+
12
+ // Regression guard: CLI docs live in their own `/cli/` section, not
13
+ // interleaved with the SDK how-to guides (see #179).
14
+ it('keeps CLI pages out of the How-to Guides group', () => {
15
+ const sidebar = config.themeConfig!.sidebar as Record<string, SidebarItem[]>
16
+ const howTo = sidebar['/'].find((group) => group.text === 'How-to Guides')
17
+ const links = (howTo!.items ?? []).map((item) => item.link)
18
+ expect(links).not.toContain('/guide/cli')
19
+ expect(links).not.toContain('/guide/init')
20
+ expect(links).not.toContain('/guide/config')
21
+ })
22
+
23
+ // Regression guard: the sidebar must never *replace* itself when entering a
24
+ // section. A path-scoped key (e.g. `/cli/`) swaps the whole tree out, which
25
+ // deletes the other sections and reads as the nav breaking (see #301). There
26
+ // must be exactly one global `/` sidebar.
27
+ it('has a single global sidebar with no replacing path-scoped keys', () => {
28
+ const sidebar = config.themeConfig!.sidebar as Record<string, SidebarItem[]>
29
+ expect(Object.keys(sidebar)).toEqual(['/'])
30
+ })
31
+
32
+ // The CLI section is self-contained: its group carries all CLI subpages,
33
+ // alongside (never instead of) the other sections (see #301).
34
+ it('shows every CLI subpage in a self-contained CLI group', () => {
35
+ const sidebar = config.themeConfig!.sidebar as Record<string, SidebarItem[]>
36
+ const groupTexts = sidebar['/'].map((group) => group.text)
37
+ expect(groupTexts).toEqual(['Tutorials', 'How-to Guides', 'CLI', 'Reference'])
38
+
39
+ const cli = sidebar['/'].find((group) => group.text === 'CLI')
40
+ const links = (cli!.items ?? []).map((item) => item.link)
41
+ expect(links).toEqual([
42
+ '/cli/',
43
+ '/cli/server',
44
+ '/cli/init',
45
+ '/cli/config',
46
+ '/cli/http-api'
47
+ ])
48
+ })
49
+ })
@@ -0,0 +1,6 @@
1
+ {
2
+ "command": "uv run python -m pytest tests/e2e/ -x -q",
3
+ "ran_at": 1782763249,
4
+ "exit_code": 0,
5
+ "commit": "8a3051871b21e04c0c2c2cc11a57e4c0a9b94866"
6
+ }
@@ -0,0 +1,13 @@
1
+ """Fixture: a native Python config that itself sets ``config=``.
2
+
3
+ `dirsql interpret` must reject this -- a config file loaded by interpret
4
+ cannot delegate to another config file (nested config loading). The
5
+ referenced TOML is valid, so the rejection comes from the loader, not from
6
+ a TOML read error.
7
+ """
8
+
9
+ import os
10
+
11
+ from dirsql import DirSQL
12
+
13
+ app = DirSQL(config=os.path.join(os.path.dirname(__file__), "nested.dirsql.toml"))
@@ -0,0 +1,26 @@
1
+ """Fixture: a native Python config that omits ``root``.
2
+
3
+ `dirsql interpret` should resolve the root to the helper process's current
4
+ working directory (the cwd the orchestrator was launched from) rather than
5
+ erroring. Mirrors ``dirsql.config.py`` but drops the ``root=`` argument.
6
+ """
7
+
8
+ import json
9
+
10
+ from dirsql import DirSQL, Table
11
+
12
+
13
+ def _extract(path):
14
+ with open(path, encoding="utf-8") as f:
15
+ return [json.load(f)]
16
+
17
+
18
+ app = DirSQL(
19
+ tables=[
20
+ Table(
21
+ ddl="CREATE TABLE papers (title TEXT)",
22
+ glob="**/meta.json",
23
+ extract=_extract,
24
+ )
25
+ ],
26
+ )
@@ -0,0 +1,6 @@
1
+ # Valid config referenced by `dirsql.config_nested.py`. Its validity is the
2
+ # point: the interpret loader must reject the *nesting*, not fail on a bad
3
+ # TOML read.
4
+ [[table]]
5
+ ddl = "CREATE TABLE papers (title TEXT)"
6
+ glob = "**/meta.json"
@@ -239,7 +239,7 @@ def extract_meta(path):
239
239
 
240
240
  # Python must export a module-level `app`.
241
241
  app = DirSQL(
242
- root="papers", # required see "Set a root" below
242
+ root="papers", # optional; defaults to the current directory
243
243
  tables=[
244
244
  Table(
245
245
  ddl="CREATE TABLE papers (title TEXT, _path TEXT)",
@@ -269,7 +269,7 @@ import { readFileSync } from "node:fs";
269
269
  import { DirSQL } from "dirsql";
270
270
 
271
271
  export default new DirSQL({
272
- root: "papers", // required see "Set a root" below
272
+ root: "papers", // optional; defaults to the current directory
273
273
  tables: [
274
274
  {
275
275
  ddl: "CREATE TABLE papers (title TEXT, _path TEXT)",
@@ -285,7 +285,7 @@ const { readFileSync } = require("node:fs");
285
285
  const { DirSQL } = require("dirsql");
286
286
 
287
287
  module.exports = new DirSQL({
288
- root: "papers", // required see "Set a root" below
288
+ root: "papers", // optional; defaults to the current directory
289
289
  tables: [
290
290
  {
291
291
  ddl: "CREATE TABLE papers (title TEXT, _path TEXT)",
@@ -308,10 +308,15 @@ These apply to both the Python and JavaScript forms above.
308
308
  package) uses `module.exports = new DirSQL(...)`. Only the extension
309
309
  matters — the file can be named anything; `dirsql.config.{py,mjs,cjs}` is the
310
310
  suggested convention, not a requirement.
311
- - **Set a `root`.** Unlike TOML configs (which default the scan root to the
312
- config file's directory), native-language configs require an explicit `root`.
313
- Without one the Python launcher errors and the JavaScript launcher silently
314
- indexes nothing.
311
+ - **`root` defaults to the current directory.** A native-language config with
312
+ no `root` indexes the process's current working directory the directory you
313
+ ran `dirsql` from. (This differs from TOML configs, which default the scan
314
+ root to the config file's own directory.) Pass `root` explicitly to index
315
+ somewhere else.
316
+ - **No nested `config=`.** A native-language config builds its `DirSQL` from
317
+ `tables` and an optional `root`; it must not itself set `config=` to delegate
318
+ to another config file. `dirsql interpret` rejects such a config (a nested
319
+ config can't be represented in the handshake and would recurse).
315
320
  - **Install the launcher on your `PATH`.** To run your `extract`, the server
316
321
  spawns `dirsql interpret`, so the matching `dirsql` launcher must be installed
317
322
  and on your `PATH` — a global `pip`/`uv` install for `.py`, or `npm` for
@@ -20,7 +20,7 @@ Everything you need to run `dirsql` as a CLI lives in this section:
20
20
  - **[Configuration File](./config.md)** — the `.dirsql.toml` format and the
21
21
  `.py` / `.js` native-language alternative. Custom tables
22
22
  are defined through a config file; without one, the server runs in
23
- [zero-config mode](./server.md#zero-config-mode).
23
+ [zero-config mode](./server.md#defaults).
24
24
  - **[HTTP API](./http-api.md)** — the `POST /query` and `GET /events`
25
25
  endpoints, status codes, and event streaming.
26
26
 
@@ -1,22 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import config from '../../.vitepress/config'
3
-
4
- type SidebarItem = { text?: string; link?: string; items?: SidebarItem[] }
5
-
6
- describe('vitepress config', () => {
7
- it('has the expected site title and base path', () => {
8
- expect(config.title).toBe('dirsql')
9
- expect(config.base).toBe('/dirsql/')
10
- })
11
-
12
- // Regression guard: CLI docs live in their own `/cli/` section, not
13
- // interleaved with the SDK how-to guides (see #179).
14
- it('keeps CLI pages out of the How-to Guides group', () => {
15
- const sidebar = config.themeConfig!.sidebar as Record<string, SidebarItem[]>
16
- const howTo = sidebar['/'].find((group) => group.text === 'How-to Guides')
17
- const links = (howTo!.items ?? []).map((item) => item.link)
18
- expect(links).not.toContain('/guide/cli')
19
- expect(links).not.toContain('/guide/init')
20
- expect(links).not.toContain('/guide/config')
21
- })
22
- })
@@ -1,22 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import config from '../../.vitepress/config'
3
-
4
- type SidebarItem = { text?: string; link?: string; items?: SidebarItem[] }
5
-
6
- describe('vitepress config', () => {
7
- it('has the expected site title and base path', () => {
8
- expect(config.title).toBe('dirsql')
9
- expect(config.base).toBe('/dirsql/')
10
- })
11
-
12
- // Regression guard: CLI docs live in their own `/cli/` section, not
13
- // interleaved with the SDK how-to guides (see #179).
14
- it('keeps CLI pages out of the How-to Guides group', () => {
15
- const sidebar = config.themeConfig!.sidebar as Record<string, SidebarItem[]>
16
- const howTo = sidebar['/'].find((group) => group.text === 'How-to Guides')
17
- const links = (howTo!.items ?? []).map((item) => item.link)
18
- expect(links).not.toContain('/guide/cli')
19
- expect(links).not.toContain('/guide/init')
20
- expect(links).not.toContain('/guide/config')
21
- })
22
- })
@@ -1,6 +0,0 @@
1
- {
2
- "command": "uv run python -m pytest tests/e2e -q",
3
- "ran_at": 1782734459,
4
- "exit_code": 0,
5
- "commit": "7c5cf2a6e06c5dfabc7ccf60d6b888aeabbaf892"
6
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes