dirsql 0.4.85__tar.gz → 0.4.87__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 (359) hide show
  1. {dirsql-0.4.85 → dirsql-0.4.87}/Cargo.lock +3 -2
  2. {dirsql-0.4.85 → dirsql-0.4.87}/PKG-INFO +1 -1
  3. {dirsql-0.4.85 → dirsql-0.4.87}/docs/reference/cli.md +18 -0
  4. {dirsql-0.4.85/packages/rust → dirsql-0.4.87}/docs/reference/http-api.md +4 -1
  5. {dirsql-0.4.85/packages/rust → dirsql-0.4.87}/docs/reference/path-tables.md +3 -1
  6. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/Cargo.toml +1 -1
  7. dirsql-0.4.87/packages/python/changelog.d/2026-10-05-unbalanced-brackets-literal.md +1 -0
  8. {dirsql-0.4.85/packages/rust → dirsql-0.4.87/packages/python}/docs/reference/cli.md +18 -0
  9. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/reference/http-api.md +4 -1
  10. {dirsql-0.4.85 → dirsql-0.4.87/packages/python}/docs/reference/path-tables.md +3 -1
  11. dirsql-0.4.87/packages/python/e2e-attestations/bot-1399-server-cors.json +7 -0
  12. dirsql-0.4.87/packages/python/e2e-attestations/bot-1401-watch-scope.json +7 -0
  13. dirsql-0.4.87/packages/python/e2e-attestations/claude-1268-unbalanced-brackets.json +7 -0
  14. dirsql-0.4.87/packages/python/migrations.d/2026-10-05-unbalanced-brackets-literal.md +29 -0
  15. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/Cargo.toml +3 -1
  16. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/README.md +1 -1
  17. {dirsql-0.4.85/packages/python → dirsql-0.4.87/packages/rust}/docs/reference/cli.md +18 -0
  18. {dirsql-0.4.85 → dirsql-0.4.87/packages/rust}/docs/reference/http-api.md +4 -1
  19. {dirsql-0.4.85/packages/python → dirsql-0.4.87/packages/rust}/docs/reference/path-tables.md +3 -1
  20. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/brace.rs +14 -8
  21. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/cli/mod.rs +30 -1
  22. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/cli/run.rs +73 -4
  23. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/cli/server.rs +11 -1
  24. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/lib.rs +40 -44
  25. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/matcher.rs +4 -2
  26. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/parsed_vtab.rs +1 -1
  27. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/scanner.rs +128 -23
  28. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/vtab.rs +1 -1
  29. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/vtab_scaffold.rs +1 -1
  30. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/watcher.rs +285 -12
  31. {dirsql-0.4.85 → dirsql-0.4.87}/Cargo.toml +0 -0
  32. {dirsql-0.4.85 → dirsql-0.4.87}/README.md +0 -0
  33. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/__init__.py +0 -0
  34. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/_async.py +0 -0
  35. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/_dirsql.pyi +0 -0
  36. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/cli/__init__.py +0 -0
  37. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/cli/discover_plugins/__init__.py +0 -0
  38. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/cli/discover_plugins/discovered_fragments.py +0 -0
  39. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/cli/discover_plugins/discovery_disabled.py +0 -0
  40. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/cli/discover_plugins/fragment_path.py +0 -0
  41. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/cli/discover_plugins/with_discovered_plugins.py +0 -0
  42. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/cli/main.py +0 -0
  43. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/cli/resolve_config_extensions.py +0 -0
  44. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/plan_entry_path.py +0 -0
  45. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/py.typed +0 -0
  46. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/read_config.py +0 -0
  47. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/resolve_configs_extension_specs.py +0 -0
  48. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/resolve_extension.py +0 -0
  49. {dirsql-0.4.85 → dirsql-0.4.87}/dirsql/resolve_package.py +0 -0
  50. {dirsql-0.4.85 → dirsql-0.4.87}/docs/.claude/CLAUDE.md +0 -0
  51. {dirsql-0.4.85 → dirsql-0.4.87}/docs/.vitepress/config.ts +0 -0
  52. {dirsql-0.4.85 → dirsql-0.4.87}/docs/.vitepress/theme/index.ts +0 -0
  53. {dirsql-0.4.85 → dirsql-0.4.87}/docs/.vitepress/theme/lang.ts +0 -0
  54. {dirsql-0.4.85 → dirsql-0.4.87}/docs/AGENTS.md +0 -0
  55. {dirsql-0.4.85 → dirsql-0.4.87}/docs/context/.gitattributes +0 -0
  56. {dirsql-0.4.85 → dirsql-0.4.87}/docs/context/recipes.md +0 -0
  57. {dirsql-0.4.85 → dirsql-0.4.87}/docs/context/troubleshooting.md +0 -0
  58. {dirsql-0.4.85 → dirsql-0.4.87}/docs/context/usage.md +0 -0
  59. {dirsql-0.4.85 → dirsql-0.4.87}/docs/explanation.md +0 -0
  60. {dirsql-0.4.85 → dirsql-0.4.87}/docs/getting-started.md +0 -0
  61. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/agent-skill.md +0 -0
  62. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/columns-from-paths.md +0 -0
  63. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/define-tables.md +0 -0
  64. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/embed.md +0 -0
  65. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/extract-from-contents.md +0 -0
  66. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/load-extension.md +0 -0
  67. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/parse-files-into-columns.md +0 -0
  68. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/persist.md +0 -0
  69. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/query-json.md +0 -0
  70. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/query-without-config.md +0 -0
  71. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/react-to-changes.md +0 -0
  72. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/search-by-meaning.md +0 -0
  73. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/search-indexes.md +0 -0
  74. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/skip-files.md +0 -0
  75. {dirsql-0.4.85 → dirsql-0.4.87}/docs/howto/write-a-plugin.md +0 -0
  76. {dirsql-0.4.85 → dirsql-0.4.87}/docs/index.md +0 -0
  77. {dirsql-0.4.85 → dirsql-0.4.87}/docs/package.json +0 -0
  78. {dirsql-0.4.85 → dirsql-0.4.87}/docs/plugins.md +0 -0
  79. {dirsql-0.4.85 → dirsql-0.4.87}/docs/pnpm-lock.yaml +0 -0
  80. {dirsql-0.4.85 → dirsql-0.4.87}/docs/pnpm-workspace.yaml +0 -0
  81. {dirsql-0.4.85 → dirsql-0.4.87}/docs/reference/columns.md +0 -0
  82. {dirsql-0.4.85 → dirsql-0.4.87}/docs/reference/config.md +0 -0
  83. {dirsql-0.4.85 → dirsql-0.4.87}/docs/reference/hooks.md +0 -0
  84. {dirsql-0.4.85 → dirsql-0.4.87}/docs/reference/sdk.md +0 -0
  85. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/CHANGELOG.md +0 -0
  86. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/MIGRATIONS.md +0 -0
  87. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/README.md +0 -0
  88. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  89. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-07-13-plugin-discovery.md +0 -0
  90. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  91. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-07-13-repeatable-config.md +0 -0
  92. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  93. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-07-20-drop-implicit-files-table.md +0 -0
  94. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-07-21-remove-glob-captures.md +0 -0
  95. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-07-24-hookless-table-config-error.md +0 -0
  96. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  97. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-07-29-restore-python-310.md +0 -0
  98. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-08-02-release-profile.md +0 -0
  99. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-08-02-scan-failures.md +0 -0
  100. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-08-03-discovery-ext-resolution.md +0 -0
  101. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-08-03-manylinux-dynamic-binary.md +0 -0
  102. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-08-03-python-no-ignore.md +0 -0
  103. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-08-04-cast-lints.md +0 -0
  104. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-08-04-cli-in-process.md +0 -0
  105. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-08-20-curtaincall-dev-dependency.md +0 -0
  106. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-08-20-declared-table-name.md +0 -0
  107. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-09-04-config-paths-from-argv-module.md +0 -0
  108. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-09-05-config-extension-loader-split.md +0 -0
  109. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-09-05-resolve-config-extensions-split.md +0 -0
  110. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-09-05-resolve-extension-split.md +0 -0
  111. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-09-09-launcher-config-path-scan.md +0 -0
  112. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-09-17-extension-resolution-via-core.md +0 -0
  113. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-09-18-export-scan-failure.md +0 -0
  114. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-09-18-flatten-row-event-in-core.md +0 -0
  115. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-09-18-programmatic-extension-plan.md +0 -0
  116. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
  117. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-09-24-query-rows-in-projection-order.md +0 -0
  118. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-02-readme-docs-host.md +0 -0
  119. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-03-one-glob-rule-for-config-globs.md +0 -0
  120. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-03-path-table-directory-one-level.md +0 -0
  121. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-04-path-table-dotfiles-hidden.md +0 -0
  122. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-05-brace-groups-follow-bash.md +0 -0
  123. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-05-cli-context.md +0 -0
  124. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-05-gitignore-requires-repo.md +0 -0
  125. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-05-path-table-node-modules-named.md +0 -0
  126. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-05-path-table-symlinks.md +0 -0
  127. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-05-path-table-trailing-slash.md +0 -0
  128. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-06-config-globs-anchor-at-the-config.md +0 -0
  129. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-06-one-glob-compiler.md +0 -0
  130. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-06-plugin-no-default-records.md +0 -0
  131. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-06-watch-broadcast.md +0 -0
  132. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/2026-10-06-zero-match-config-table-warning.md +0 -0
  133. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/changelog.d/README.md +0 -0
  134. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/conftest.py +0 -0
  135. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/.claude/CLAUDE.md +0 -0
  136. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/.vitepress/config.ts +0 -0
  137. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/.vitepress/theme/index.ts +0 -0
  138. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/.vitepress/theme/lang.ts +0 -0
  139. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/AGENTS.md +0 -0
  140. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/context/.gitattributes +0 -0
  141. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/context/recipes.md +0 -0
  142. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/context/troubleshooting.md +0 -0
  143. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/context/usage.md +0 -0
  144. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/explanation.md +0 -0
  145. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/getting-started.md +0 -0
  146. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/agent-skill.md +0 -0
  147. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/columns-from-paths.md +0 -0
  148. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/define-tables.md +0 -0
  149. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/embed.md +0 -0
  150. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/extract-from-contents.md +0 -0
  151. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/load-extension.md +0 -0
  152. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/parse-files-into-columns.md +0 -0
  153. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/persist.md +0 -0
  154. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/query-json.md +0 -0
  155. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/query-without-config.md +0 -0
  156. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/react-to-changes.md +0 -0
  157. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/search-by-meaning.md +0 -0
  158. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/search-indexes.md +0 -0
  159. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/skip-files.md +0 -0
  160. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/howto/write-a-plugin.md +0 -0
  161. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/index.md +0 -0
  162. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/package.json +0 -0
  163. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/plugins.md +0 -0
  164. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/pnpm-lock.yaml +0 -0
  165. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/pnpm-workspace.yaml +0 -0
  166. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/reference/columns.md +0 -0
  167. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/reference/config.md +0 -0
  168. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/reference/hooks.md +0 -0
  169. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/docs/reference/sdk.md +0 -0
  170. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/bot-1397-watch-broadcast.json +0 -0
  171. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/bot-1398-inotify-error.json +0 -0
  172. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/bot-1407-child-place-depth.json +0 -0
  173. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/cc-keen-knuth-f11gyq.json +0 -0
  174. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1056-py-ofpf9.json +0 -0
  175. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1066-p1-resolve-extension.json +0 -0
  176. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1078-argv-config-paths-core.json +0 -0
  177. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1079-p2-resolve-config-extensions.json +0 -0
  178. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1080-share-vtab-scaffolding.json +0 -0
  179. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1081-indicatif-progress.json +0 -0
  180. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1096-watch-moved-dir.json +0 -0
  181. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1109-watch-moved-out.json +0 -0
  182. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1119-ext-plan.json +0 -0
  183. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1120-py-ext.json +0 -0
  184. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1122-ext-cleanup.json +0 -0
  185. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1130-codec-crates.json +0 -0
  186. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1131-flatten-row-event.json +0 -0
  187. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1139-sse-flatten.json +0 -0
  188. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1140-scan-failure-export.json +0 -0
  189. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1148-clippy-all-targets.json +0 -0
  190. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1164-sigint-fatal.json +0 -0
  191. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1170-projection-order.json +0 -0
  192. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1172-on-file-path.json +0 -0
  193. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1173-py-projection-order.json +0 -0
  194. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1184-win-rel-paths.json +0 -0
  195. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1185-win-abs-table-names.json +0 -0
  196. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1186-win-test-harness.json +0 -0
  197. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1196-join-index.json +0 -0
  198. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1199-glob-prefix.json +0 -0
  199. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1201-single-walk.json +0 -0
  200. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1204-embed-batch.json +0 -0
  201. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1207-win-argv-quote.json +0 -0
  202. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1208-win-nonverbatim-paths.json +0 -0
  203. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1215-on-file-batch.json +0 -0
  204. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1216-on-file-cleanup.json +0 -0
  205. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1223-config-glob-rule.json +0 -0
  206. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1223-dir-one-level.json +0 -0
  207. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1225-plugin-no-default.json +0 -0
  208. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1226-stale-docs.json +0 -0
  209. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1232-anchor-plumbing.json +0 -0
  210. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1232-config-anchor.json +0 -0
  211. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1232-multi-watch.json +0 -0
  212. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1232-zero-match.json +0 -0
  213. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1238-parallel-walk.json +0 -0
  214. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1241-content-prefetch.json +0 -0
  215. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1243-ingest-perf.json +0 -0
  216. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1249-dotfiles.json +0 -0
  217. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1256-symlinks.json +0 -0
  218. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1257-trailing-slash.json +0 -0
  219. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1258-one-pattern.json +0 -0
  220. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1262-brace-literal.json +0 -0
  221. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1264-context.json +0 -0
  222. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1267-posix-classes.json +0 -0
  223. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1269-utf8-chars.json +0 -0
  224. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1271-node-modules-named.json +0 -0
  225. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1272-ancestor-gitignore.json +0 -0
  226. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1273-walk-prune.json +0 -0
  227. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1285-ingest-fast.json +0 -0
  228. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1286-nested-repo.json +0 -0
  229. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1305-glob-speed.json +0 -0
  230. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1308-glob-speed.json +0 -0
  231. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1310-glob-speed.json +0 -0
  232. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1311-glob-speed.json +0 -0
  233. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1312-glob-speed.json +0 -0
  234. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1318-lazy-stat.json +0 -0
  235. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1320-walk-speed.json +0 -0
  236. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1322-walk-readdir.json +0 -0
  237. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1325-parallel-walk.json +0 -0
  238. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1326-stream-rows.json +0 -0
  239. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1330-python-dirsql-patch.json +0 -0
  240. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1330-python-extensions-patch.json +0 -0
  241. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1330-scanner-effects.json +0 -0
  242. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1330-scanner-gitignore.json +0 -0
  243. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1330-vtab-prefetch-flag.json +0 -0
  244. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1330-vtab-read.json +0 -0
  245. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-1351-dot-dirs.json +0 -0
  246. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-819-oneshot-timeout.json +0 -0
  247. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-827-unquote-doubled-quotes.json +0 -0
  248. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-epic-953-slice2-reedline.json +0 -0
  249. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-epic-953-slice3-format.json +0 -0
  250. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-epic-953-stacked-prs-x59wm3.json +0 -0
  251. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-github-issue-825-e4mhhp.json +0 -0
  252. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-issue-951-0eease.json +0 -0
  253. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-issue-962-0jjrfv.json +0 -0
  254. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-issue-986-red-test-e7sjrb.json +0 -0
  255. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-tackle-1034-cachesplit.json +0 -0
  256. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-tackle-1042-py12.json +0 -0
  257. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-tackle-956-xzen74.json +0 -0
  258. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6-worker-calls.json +0 -0
  259. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-tackle-957-lrm0z6.json +0 -0
  260. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/e2e-attestations/claude-tackle-975-n8mc2h.json +0 -0
  261. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-07-13-drop-cwd-config-auto-detect.md +0 -0
  262. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-07-13-rename-extract-to-on-file.md +0 -0
  263. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-07-13-sdk-default-baked-in-config.md +0 -0
  264. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-07-20-drop-implicit-files-table.md +0 -0
  265. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-07-21-remove-glob-captures.md +0 -0
  266. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-07-24-hookless-table-config-error.md +0 -0
  267. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-07-24-remove-stat-fact-injection.md +0 -0
  268. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-07-29-restore-python-310.md +0 -0
  269. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-08-04-cli-in-process.md +0 -0
  270. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-08-20-declared-table-name.md +0 -0
  271. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-09-20-sigint-ends-a-run-in-progress.md +0 -0
  272. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-09-24-query-rows-in-projection-order.md +0 -0
  273. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-02-readme-docs-host.md +0 -0
  274. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-03-one-glob-rule-for-config-globs.md +0 -0
  275. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-03-path-table-directory-one-level.md +0 -0
  276. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-04-path-table-dotfiles-hidden.md +0 -0
  277. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-05-brace-groups-follow-bash.md +0 -0
  278. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-05-cli-context.md +0 -0
  279. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-05-gitignore-requires-repo.md +0 -0
  280. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-05-path-table-node-modules-named.md +0 -0
  281. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-05-path-table-symlinks.md +0 -0
  282. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-05-path-table-trailing-slash.md +0 -0
  283. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-06-config-globs-anchor-at-the-config.md +0 -0
  284. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-06-one-glob-compiler.md +0 -0
  285. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-06-plugin-no-default-records.md +0 -0
  286. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-06-watch-broadcast.md +0 -0
  287. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/2026-10-06-zero-match-config-table-warning.md +0 -0
  288. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/migrations.d/README.md +0 -0
  289. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/src/lib.rs +0 -0
  290. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/testing-conventions.toml +0 -0
  291. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/tests/__init__.py +0 -0
  292. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/tests/conftest.py +0 -0
  293. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/tests/e2e/__init__.py +0 -0
  294. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/__init__.py +0 -0
  295. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/tests/e2e/fixtures/dirsql_plugin_fixture/dirsql.toml +0 -0
  296. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/tests/e2e/speed_of_light.py +0 -0
  297. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/tests/integration/__init__.py +0 -0
  298. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/tests/integration/binding/__init__.py +0 -0
  299. {dirsql-0.4.85 → dirsql-0.4.87}/packages/python/tests/integration/hermetic/__init__.py +0 -0
  300. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/CHANGELOG.md +0 -0
  301. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/MIGRATIONS.md +0 -0
  302. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/benches/db_bench.rs +0 -0
  303. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/benches/differ_bench.rs +0 -0
  304. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/benches/matcher_bench.rs +0 -0
  305. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/benches/scanner_bench.rs +0 -0
  306. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/context/.gitattributes +0 -0
  307. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/context/recipes.md +0 -0
  308. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/context/troubleshooting.md +0 -0
  309. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/context/usage.md +0 -0
  310. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/explanation.md +0 -0
  311. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/getting-started.md +0 -0
  312. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/agent-skill.md +0 -0
  313. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/columns-from-paths.md +0 -0
  314. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/define-tables.md +0 -0
  315. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/embed.md +0 -0
  316. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/extract-from-contents.md +0 -0
  317. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/load-extension.md +0 -0
  318. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/parse-files-into-columns.md +0 -0
  319. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/persist.md +0 -0
  320. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/query-json.md +0 -0
  321. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/query-without-config.md +0 -0
  322. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/react-to-changes.md +0 -0
  323. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/search-by-meaning.md +0 -0
  324. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/search-indexes.md +0 -0
  325. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/skip-files.md +0 -0
  326. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/howto/write-a-plugin.md +0 -0
  327. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/index.md +0 -0
  328. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/plugins.md +0 -0
  329. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/reference/columns.md +0 -0
  330. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/reference/config.md +0 -0
  331. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/reference/hooks.md +0 -0
  332. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/docs/reference/sdk.md +0 -0
  333. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/bin/dirsql.rs +0 -0
  334. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/cli/context.rs +0 -0
  335. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/cli/execute.rs +0 -0
  336. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/cli/init.rs +0 -0
  337. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/cli/repl.rs +0 -0
  338. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/cli/router.rs +0 -0
  339. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/cli/serialize.rs +0 -0
  340. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/cli/table.rs +0 -0
  341. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/command.rs +0 -0
  342. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/config.rs +0 -0
  343. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/db.rs +0 -0
  344. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/default_config.toml +0 -0
  345. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/differ.rs +0 -0
  346. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/extension_resolution.rs +0 -0
  347. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/functions.rs +0 -0
  348. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/infer.rs +0 -0
  349. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/launcher.rs +0 -0
  350. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/listing.rs +0 -0
  351. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/on_file.rs +0 -0
  352. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/path_table.rs +0 -0
  353. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/persist.rs +0 -0
  354. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/posix_class.rs +0 -0
  355. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/progress.rs +0 -0
  356. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/row_event_flat.rs +0 -0
  357. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/sql_literal.rs +0 -0
  358. {dirsql-0.4.85 → dirsql-0.4.87}/packages/rust/src/tree_walk.rs +0 -0
  359. {dirsql-0.4.85 → dirsql-0.4.87}/pyproject.toml +0 -0
@@ -552,7 +552,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
552
552
 
553
553
  [[package]]
554
554
  name = "dirsql"
555
- version = "0.4.85"
555
+ version = "0.4.87"
556
556
  dependencies = [
557
557
  "assert_cmd",
558
558
  "axum",
@@ -586,6 +586,7 @@ dependencies = [
586
586
  "tokio",
587
587
  "tokio-stream",
588
588
  "toml",
589
+ "tower-http",
589
590
  "typed-path",
590
591
  ]
591
592
 
@@ -602,7 +603,7 @@ dependencies = [
602
603
 
603
604
  [[package]]
604
605
  name = "dirsql-py-ext"
605
- version = "0.4.85"
606
+ version = "0.4.87"
606
607
  dependencies = [
607
608
  "dirsql",
608
609
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dirsql
3
- Version: 0.4.85
3
+ Version: 0.4.87
4
4
  Requires-Dist: bin-shim>=0.1
5
5
  Summary: Ephemeral SQL index over a local directory
6
6
  Keywords: sql,filesystem,directory,sqlite,index
@@ -247,6 +247,7 @@ Config flags are subcommand-local: pass them after `server`
247
247
  | `-c, --config <path>` | none | Path to a [config file](./config.md). **Repeatable** (`-c a -c b`): the configs load and merge in argv order — see [Composing multiple configs](./config.md#composing-multiple-configs). The index root is the **invocation directory** (the current working directory) and governs path-tables; a config's `[[table]]` globs anchor at **that config's own directory** (see [glob anchor](./config.md#glob-anchor)), so `--config /elsewhere/.dirsql.toml` indexes `/elsewhere`, from any working directory. With none given, **no named tables are defined** — query the filesystem with a [path-table](./path-tables.md) (`FROM './'`). A `./.dirsql.toml` on disk is **not** auto-loaded; pass it explicitly. A `-c` naming a file that does not exist is an [error](#degraded-mode). |
248
248
  | `--host <addr>` | `localhost` | Bind address. |
249
249
  | `--port <n>` | `7117` | TCP port to bind. |
250
+ | `--cors-origin <origin>` | off | Let browser pages on `<origin>` (an exact origin such as `http://localhost:3202`, or `*` for any) call `/query` and open `/events`. See [Cross-origin requests](#cross-origin-requests). |
250
251
  | `--persist [<path>]` | off | Keep the SQLite index on disk between runs so a restart only re-parses files that actually changed. Bare `--persist` caches at `<root>/.dirsql/cache.db`; `--persist <path>` caches at `<path>`. Off by default (the index is ephemeral). Also available on [`dirsql query`](#dirsql-query). See [Keep the index across restarts](../howto/persist.md). |
251
252
  | `--no-ignore` | off | Scan files a `.gitignore` would hide. [Path-tables](./path-tables.md#skip-rules) respect `.gitignore` files by default; this flag restores the full walk. The built-in skips (`node_modules`/`.git`) and configured `ignore` patterns still apply. Also available on [`dirsql query`](#dirsql-query). |
252
253
  | `--extension <path>` | none | Load a SQLite extension by literal path, overriding the config's `[[dirsql.extension]]` entries. Repeatable. Format: `<path>` or `<path>::<entrypoint>`. Internal plumbing for the pip/npm launchers, which resolve package-name extensions and pass the resolved paths here — not intended for direct use. When any `--extension` is present, the config file's own extension entries are not loaded. |
@@ -262,6 +263,23 @@ Config flags are subcommand-local: pass them after `server`
262
263
  command in `timeout(1)` (see
263
264
  [Bounding a hook](./hooks.md#bounding-a-hook)).
264
265
 
266
+ ### Cross-origin requests
267
+
268
+ By default the server sends no CORS headers, so a browser blocks a page on
269
+ another origin from reading `/query` responses or opening `/events`. This is
270
+ deliberate: any web page the browser visits can send requests to
271
+ `localhost`, and permissive CORS would let it read the indexed files.
272
+
273
+ `--cors-origin <origin>` opts in. Every response then carries
274
+ `Access-Control-Allow-Origin: <origin>`, and the preflight a JSON
275
+ `POST /query` triggers is answered (methods `GET`, `POST`; header
276
+ `content-type`). The value is sent verbatim: an origin is
277
+ `scheme://host[:port]` with no trailing slash, and `*` allows every origin.
278
+
279
+ ```bash
280
+ dirsql server --cors-origin http://localhost:3202
281
+ ```
282
+
265
283
  ### Configless mode
266
284
 
267
285
  With no `-c/--config`, the server indexes the invocation directory but
@@ -1,7 +1,10 @@
1
1
  # HTTP API
2
2
 
3
3
  The [`dirsql` server](./cli.md#dirsql-server) (default `localhost:7117`)
4
- exposes two endpoints: `POST /query` and `GET /events`.
4
+ exposes two endpoints: `POST /query` and `GET /events`. It sends no CORS
5
+ headers unless started with
6
+ [`--cors-origin`](./cli.md#cross-origin-requests), so a browser page on
7
+ another origin can reach it only after that opt-in.
5
8
 
6
9
  ## `POST /query`
7
10
 
@@ -59,7 +59,9 @@ alternation: `'./docs/{a,b}.md'` is `docs/a.md` and `docs/b.md`, and groups
59
59
  nest. A group holding a `..` range counts: `'./log/{1..3}.txt'`,
60
60
  `'./{a..c}.md'`, `'./{01..10}.md'` (zero-padded) and `'./{1..10..2}.md'`
61
61
  (every second value). Any other brace is literal text, so `'./br/{q}.md'`
62
- names the file `br/{q}.md`, and `\{` is always a literal brace.
62
+ names the file `br/{q}.md`, and `\{` is always a literal brace. A `{`, `}`
63
+ or `[` with no partner is literal too, in a path-table and in a config
64
+ `[[table]] glob`.
63
65
 
64
66
  The scan starts at the last directory named outright before the first glob
65
67
  component, and descends only as deep as the pattern can match --
@@ -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.4.85"
7
+ version = "0.4.87"
8
8
  # abi3 note: the `abi3-py310` pyo3 feature (below) builds ONE stable-ABI
9
9
  # `cp310-abi3` wheel per platform that loads on every CPython >= 3.10
10
10
  # (matching `pyproject.toml`'s `requires-python = ">=3.10"`), instead of
@@ -0,0 +1 @@
1
+ **Fixed** An unmatched `{`, `}` or `[` in a path-table or a config `[[table]] glob` is a literal character, as in bash, instead of a glob compile error.
@@ -247,6 +247,7 @@ Config flags are subcommand-local: pass them after `server`
247
247
  | `-c, --config <path>` | none | Path to a [config file](./config.md). **Repeatable** (`-c a -c b`): the configs load and merge in argv order — see [Composing multiple configs](./config.md#composing-multiple-configs). The index root is the **invocation directory** (the current working directory) and governs path-tables; a config's `[[table]]` globs anchor at **that config's own directory** (see [glob anchor](./config.md#glob-anchor)), so `--config /elsewhere/.dirsql.toml` indexes `/elsewhere`, from any working directory. With none given, **no named tables are defined** — query the filesystem with a [path-table](./path-tables.md) (`FROM './'`). A `./.dirsql.toml` on disk is **not** auto-loaded; pass it explicitly. A `-c` naming a file that does not exist is an [error](#degraded-mode). |
248
248
  | `--host <addr>` | `localhost` | Bind address. |
249
249
  | `--port <n>` | `7117` | TCP port to bind. |
250
+ | `--cors-origin <origin>` | off | Let browser pages on `<origin>` (an exact origin such as `http://localhost:3202`, or `*` for any) call `/query` and open `/events`. See [Cross-origin requests](#cross-origin-requests). |
250
251
  | `--persist [<path>]` | off | Keep the SQLite index on disk between runs so a restart only re-parses files that actually changed. Bare `--persist` caches at `<root>/.dirsql/cache.db`; `--persist <path>` caches at `<path>`. Off by default (the index is ephemeral). Also available on [`dirsql query`](#dirsql-query). See [Keep the index across restarts](../howto/persist.md). |
251
252
  | `--no-ignore` | off | Scan files a `.gitignore` would hide. [Path-tables](./path-tables.md#skip-rules) respect `.gitignore` files by default; this flag restores the full walk. The built-in skips (`node_modules`/`.git`) and configured `ignore` patterns still apply. Also available on [`dirsql query`](#dirsql-query). |
252
253
  | `--extension <path>` | none | Load a SQLite extension by literal path, overriding the config's `[[dirsql.extension]]` entries. Repeatable. Format: `<path>` or `<path>::<entrypoint>`. Internal plumbing for the pip/npm launchers, which resolve package-name extensions and pass the resolved paths here — not intended for direct use. When any `--extension` is present, the config file's own extension entries are not loaded. |
@@ -262,6 +263,23 @@ Config flags are subcommand-local: pass them after `server`
262
263
  command in `timeout(1)` (see
263
264
  [Bounding a hook](./hooks.md#bounding-a-hook)).
264
265
 
266
+ ### Cross-origin requests
267
+
268
+ By default the server sends no CORS headers, so a browser blocks a page on
269
+ another origin from reading `/query` responses or opening `/events`. This is
270
+ deliberate: any web page the browser visits can send requests to
271
+ `localhost`, and permissive CORS would let it read the indexed files.
272
+
273
+ `--cors-origin <origin>` opts in. Every response then carries
274
+ `Access-Control-Allow-Origin: <origin>`, and the preflight a JSON
275
+ `POST /query` triggers is answered (methods `GET`, `POST`; header
276
+ `content-type`). The value is sent verbatim: an origin is
277
+ `scheme://host[:port]` with no trailing slash, and `*` allows every origin.
278
+
279
+ ```bash
280
+ dirsql server --cors-origin http://localhost:3202
281
+ ```
282
+
265
283
  ### Configless mode
266
284
 
267
285
  With no `-c/--config`, the server indexes the invocation directory but
@@ -1,7 +1,10 @@
1
1
  # HTTP API
2
2
 
3
3
  The [`dirsql` server](./cli.md#dirsql-server) (default `localhost:7117`)
4
- exposes two endpoints: `POST /query` and `GET /events`.
4
+ exposes two endpoints: `POST /query` and `GET /events`. It sends no CORS
5
+ headers unless started with
6
+ [`--cors-origin`](./cli.md#cross-origin-requests), so a browser page on
7
+ another origin can reach it only after that opt-in.
5
8
 
6
9
  ## `POST /query`
7
10
 
@@ -59,7 +59,9 @@ alternation: `'./docs/{a,b}.md'` is `docs/a.md` and `docs/b.md`, and groups
59
59
  nest. A group holding a `..` range counts: `'./log/{1..3}.txt'`,
60
60
  `'./{a..c}.md'`, `'./{01..10}.md'` (zero-padded) and `'./{1..10..2}.md'`
61
61
  (every second value). Any other brace is literal text, so `'./br/{q}.md'`
62
- names the file `br/{q}.md`, and `\{` is always a literal brace.
62
+ names the file `br/{q}.md`, and `\{` is always a literal brace. A `{`, `}`
63
+ or `[` with no partner is literal too, in a path-table and in a config
64
+ `[[table]] glob`.
63
65
 
64
66
  The scan starts at the last directory named outright before the first glob
65
67
  component, and descends only as deep as the pattern can match --
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "uv run python -m pytest tests/e2e/fanout_test.py tests/e2e/query_contract_test.py -x -q",
3
+ "ran_at": 1791312715,
4
+ "exit_code": 0,
5
+ "commit": "517f231592489abeec2e15d76c19865b7f651c3b",
6
+ "branch": "bot/1399-server-cors"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "uv run python -m pytest tests/e2e/fanout_test.py tests/e2e/declared_table_name_test.py tests/e2e/query_contract_test.py tests/e2e/on_file_query_test.py -x -q",
3
+ "ran_at": 1791324350,
4
+ "exit_code": 0,
5
+ "commit": "20cb91d9d8f0a1b24c57f426b95aff4dd97e433b",
6
+ "branch": "bot/1401-watch-scope"
7
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "command": "uv run python -m pytest tests/e2e/glob_speed_depth_bound_test.py tests/e2e/glob_speed_recursive_test.py tests/e2e/glob_speed_one_level_test.py tests/e2e/glob_speed_matchers_test.py tests/e2e/glob_speed_dotfiles_test.py tests/e2e/query_contract_test.py tests/e2e/fanout_test.py tests/e2e/capture_collision_test.py -x -q",
3
+ "ran_at": 1791325871,
4
+ "exit_code": 0,
5
+ "commit": "45535f123cfc3ea9cf9460c228196e2630d377db",
6
+ "branch": "claude/1268-unbalanced-brackets"
7
+ }
@@ -0,0 +1,29 @@
1
+ ### Core: unbalanced braces and brackets in a glob are literal
2
+
3
+ #### Summary
4
+
5
+ A `{`, `}` or `[` with no partner in a glob is now a literal character, as in
6
+ bash, on path-tables and on config `[[table]] glob`. It used to fail with a
7
+ glob compile error. Nothing that compiled before changes meaning.
8
+
9
+ #### Required changes
10
+
11
+ _None._
12
+
13
+ #### Deprecations removed
14
+
15
+ _None._
16
+
17
+ #### Behavior changes without code changes
18
+
19
+ - `'./{a.md'`, `'./a}.md'`, `'./[a.md'` and `'./a[b.md'` match files named
20
+ exactly that instead of failing to compile.
21
+
22
+ #### Verification
23
+
24
+ ```bash
25
+ cd "$(mktemp -d)"
26
+ touch '[a.md'
27
+ dirsql query "SELECT path FROM './[a.md'"
28
+ # expected: [{"path":"[a.md"}]
29
+ ```
@@ -6,7 +6,7 @@ description = "Ephemeral SQL index over a local directory"
6
6
  # version stays for the binding crates (publish = false). The literal
7
7
  # intentionally lags the published version: putitoutthere rewrites it to
8
8
  # the planned release version at build time and never commits it back.
9
- version = "0.4.85"
9
+ version = "0.4.87"
10
10
  edition.workspace = true
11
11
  # Literal `license` (not `license.workspace = true`) because
12
12
  # putitoutthere's preflight check reads `[package].license` directly
@@ -50,6 +50,7 @@ cli = [
50
50
  "dep:hex",
51
51
  "dep:reedline",
52
52
  "dep:tokio-stream",
53
+ "dep:tower-http",
53
54
  "tokio/rt-multi-thread",
54
55
  "tokio/macros",
55
56
  "tokio/signal",
@@ -86,6 +87,7 @@ shlex = "1"
86
87
  thiserror.workspace = true
87
88
  tokio.workspace = true
88
89
  tokio-stream = { version = "0.1", features = ["sync"], optional = true }
90
+ tower-http = { version = "0.6", features = ["cors"], optional = true }
89
91
  toml = "0.8"
90
92
  typed-path = "0.12"
91
93
 
@@ -124,7 +124,7 @@ cargo install dirsql --features cli
124
124
  dirsql "SELECT * FROM './'"
125
125
  ```
126
126
 
127
- `dirsql "<sql>"` runs one query and prints the rows as JSON — the default. `dirsql server` starts an HTTP server bound to `localhost:7117` that exposes the SDK over HTTP: `POST /query` for SQL and `GET /events` for a Server-Sent Events change stream. Override the bind with `dirsql server --host`, `--port`, `--config`. See the [CLI reference](https://dirsql.dev/reference/cli).
127
+ `dirsql "<sql>"` runs one query and prints the rows as JSON — the default. `dirsql server` starts an HTTP server bound to `localhost:7117` that exposes the SDK over HTTP: `POST /query` for SQL and `GET /events` for a Server-Sent Events change stream. Override the bind with `dirsql server --host`, `--port`, `--config`; add `--cors-origin <origin>` to let a browser page on another origin call it. See the [CLI reference](https://dirsql.dev/reference/cli).
128
128
 
129
129
  The `cli` feature is **opt-in** -- `cargo add dirsql` pulls no CLI dependencies. `cargo install dirsql` without `--features cli` silently installs nothing (`required-features` skips the bin target with no warning); always include the flag, or use `npx dirsql` / `uvx dirsql` for prebuilt binaries.
130
130
 
@@ -247,6 +247,7 @@ Config flags are subcommand-local: pass them after `server`
247
247
  | `-c, --config <path>` | none | Path to a [config file](./config.md). **Repeatable** (`-c a -c b`): the configs load and merge in argv order — see [Composing multiple configs](./config.md#composing-multiple-configs). The index root is the **invocation directory** (the current working directory) and governs path-tables; a config's `[[table]]` globs anchor at **that config's own directory** (see [glob anchor](./config.md#glob-anchor)), so `--config /elsewhere/.dirsql.toml` indexes `/elsewhere`, from any working directory. With none given, **no named tables are defined** — query the filesystem with a [path-table](./path-tables.md) (`FROM './'`). A `./.dirsql.toml` on disk is **not** auto-loaded; pass it explicitly. A `-c` naming a file that does not exist is an [error](#degraded-mode). |
248
248
  | `--host <addr>` | `localhost` | Bind address. |
249
249
  | `--port <n>` | `7117` | TCP port to bind. |
250
+ | `--cors-origin <origin>` | off | Let browser pages on `<origin>` (an exact origin such as `http://localhost:3202`, or `*` for any) call `/query` and open `/events`. See [Cross-origin requests](#cross-origin-requests). |
250
251
  | `--persist [<path>]` | off | Keep the SQLite index on disk between runs so a restart only re-parses files that actually changed. Bare `--persist` caches at `<root>/.dirsql/cache.db`; `--persist <path>` caches at `<path>`. Off by default (the index is ephemeral). Also available on [`dirsql query`](#dirsql-query). See [Keep the index across restarts](../howto/persist.md). |
251
252
  | `--no-ignore` | off | Scan files a `.gitignore` would hide. [Path-tables](./path-tables.md#skip-rules) respect `.gitignore` files by default; this flag restores the full walk. The built-in skips (`node_modules`/`.git`) and configured `ignore` patterns still apply. Also available on [`dirsql query`](#dirsql-query). |
252
253
  | `--extension <path>` | none | Load a SQLite extension by literal path, overriding the config's `[[dirsql.extension]]` entries. Repeatable. Format: `<path>` or `<path>::<entrypoint>`. Internal plumbing for the pip/npm launchers, which resolve package-name extensions and pass the resolved paths here — not intended for direct use. When any `--extension` is present, the config file's own extension entries are not loaded. |
@@ -262,6 +263,23 @@ Config flags are subcommand-local: pass them after `server`
262
263
  command in `timeout(1)` (see
263
264
  [Bounding a hook](./hooks.md#bounding-a-hook)).
264
265
 
266
+ ### Cross-origin requests
267
+
268
+ By default the server sends no CORS headers, so a browser blocks a page on
269
+ another origin from reading `/query` responses or opening `/events`. This is
270
+ deliberate: any web page the browser visits can send requests to
271
+ `localhost`, and permissive CORS would let it read the indexed files.
272
+
273
+ `--cors-origin <origin>` opts in. Every response then carries
274
+ `Access-Control-Allow-Origin: <origin>`, and the preflight a JSON
275
+ `POST /query` triggers is answered (methods `GET`, `POST`; header
276
+ `content-type`). The value is sent verbatim: an origin is
277
+ `scheme://host[:port]` with no trailing slash, and `*` allows every origin.
278
+
279
+ ```bash
280
+ dirsql server --cors-origin http://localhost:3202
281
+ ```
282
+
265
283
  ### Configless mode
266
284
 
267
285
  With no `-c/--config`, the server indexes the invocation directory but
@@ -1,7 +1,10 @@
1
1
  # HTTP API
2
2
 
3
3
  The [`dirsql` server](./cli.md#dirsql-server) (default `localhost:7117`)
4
- exposes two endpoints: `POST /query` and `GET /events`.
4
+ exposes two endpoints: `POST /query` and `GET /events`. It sends no CORS
5
+ headers unless started with
6
+ [`--cors-origin`](./cli.md#cross-origin-requests), so a browser page on
7
+ another origin can reach it only after that opt-in.
5
8
 
6
9
  ## `POST /query`
7
10
 
@@ -59,7 +59,9 @@ alternation: `'./docs/{a,b}.md'` is `docs/a.md` and `docs/b.md`, and groups
59
59
  nest. A group holding a `..` range counts: `'./log/{1..3}.txt'`,
60
60
  `'./{a..c}.md'`, `'./{01..10}.md'` (zero-padded) and `'./{1..10..2}.md'`
61
61
  (every second value). Any other brace is literal text, so `'./br/{q}.md'`
62
- names the file `br/{q}.md`, and `\{` is always a literal brace.
62
+ names the file `br/{q}.md`, and `\{` is always a literal brace. A `{`, `}`
63
+ or `[` with no partner is literal too, in a path-table and in a config
64
+ `[[table]] glob`.
63
65
 
64
66
  The scan starts at the last directory named outright before the first glob
65
67
  component, and descends only as deep as the pattern can match --
@@ -198,7 +198,7 @@ fn steps(lo: i64, hi: i64, step: i64) -> Option<impl Iterator<Item = i128>> {
198
198
 
199
199
  /// Escape the braces bash left as literal text, so globset matches them
200
200
  /// rather than reading an alternation. A complete `[...]` class is copied as
201
- /// written; an unclosed one ends escaping so globset reports the error.
201
+ /// written; an unclosed `[` is a literal character.
202
202
  fn escape_braces(word: &str) -> String {
203
203
  let chars: Vec<char> = word.chars().collect();
204
204
  let mut out = String::with_capacity(word.len());
@@ -222,10 +222,7 @@ fn escape_braces(word: &str) -> String {
222
222
  out.extend(&chars[i..=end]);
223
223
  resume = end + 1;
224
224
  }
225
- None => {
226
- out.extend(&chars[i..]);
227
- return out;
228
- }
225
+ None => out.push_str("[[]"),
229
226
  },
230
227
  ('{', _) => out.push_str("[{]"),
231
228
  ('}', _) => out.push_str("[}]"),
@@ -411,9 +408,18 @@ mod tests {
411
408
  }
412
409
 
413
410
  #[test]
414
- fn an_unclosed_class_ends_escaping() {
415
- assert_eq!(escape_braces("x[{q}"), "x[{q}");
416
- assert_eq!(escape_braces("x[]{"), "x[]{");
411
+ fn an_unclosed_bracket_is_literal() {
412
+ assert_eq!(escape_braces("x[{q}"), "x[[][{]q[}]");
413
+ assert_eq!(escape_braces("x[]{"), "x[[]][{]");
414
+ assert_eq!(escape_braces("[!a"), "[[]!a");
415
+ assert_eq!(escape_braces("a].md"), "a].md");
416
+ }
417
+
418
+ #[test]
419
+ fn expand_leaves_an_unpartnered_brace_literal() {
420
+ assert_eq!(expand("{a.md"), vec!["[{]a.md"]);
421
+ assert_eq!(expand("a}.md"), vec!["a[}].md"]);
422
+ assert_eq!(expand("{a,b}}.md"), vec!["a[}].md", "b[}].md"]);
417
423
  }
418
424
 
419
425
  #[test]
@@ -26,6 +26,7 @@
26
26
  use std::net::SocketAddr;
27
27
  use std::time::Duration;
28
28
 
29
+ use axum::http::HeaderValue;
29
30
  use tokio::sync::{oneshot, watch};
30
31
  use tokio::task::JoinError;
31
32
  use tokio::task::JoinHandle;
@@ -52,12 +53,15 @@ pub use server::{serve, serve_with_state};
52
53
  pub use crate::DEFAULT_CONFIG_TOML;
53
54
 
54
55
  /// Configure how the server binds. Defaults to `localhost:7117` with a
55
- /// 30-second per-query timeout.
56
+ /// 30-second per-query timeout and no CORS headers.
56
57
  #[derive(Debug, Clone)]
57
58
  pub struct ServerConfig {
58
59
  pub host: String,
59
60
  pub port: u16,
60
61
  pub query_timeout: Duration,
62
+ /// The `Access-Control-Allow-Origin` value sent on every response, or
63
+ /// `None` to send no CORS headers (browsers then block cross-origin reads).
64
+ pub cors_origin: Option<HeaderValue>,
61
65
  }
62
66
 
63
67
  impl ServerConfig {
@@ -68,6 +72,7 @@ impl ServerConfig {
68
72
  host: "localhost".into(),
69
73
  port: 0,
70
74
  query_timeout: Duration::from_secs(30),
75
+ cors_origin: None,
71
76
  }
72
77
  }
73
78
 
@@ -77,6 +82,7 @@ impl ServerConfig {
77
82
  host: host.into(),
78
83
  port,
79
84
  query_timeout: Duration::from_secs(30),
85
+ cors_origin: None,
80
86
  }
81
87
  }
82
88
 
@@ -86,6 +92,13 @@ impl ServerConfig {
86
92
  self.query_timeout = timeout;
87
93
  self
88
94
  }
95
+
96
+ /// Allow browser pages on `origin` (or any origin, for `*`) to read
97
+ /// responses and open `/events`.
98
+ pub fn with_cors_origin(mut self, origin: HeaderValue) -> Self {
99
+ self.cors_origin = Some(origin);
100
+ self
101
+ }
89
102
  }
90
103
 
91
104
  impl Default for ServerConfig {
@@ -181,6 +194,22 @@ mod tests {
181
194
  assert_eq!(cfg.host, "localhost");
182
195
  assert_eq!(cfg.port, 7117);
183
196
  assert_eq!(cfg.query_timeout, Duration::from_secs(30));
197
+ assert_eq!(cfg.cors_origin, None);
198
+ }
199
+
200
+ #[test]
201
+ fn ephemeral_config_binds_port_zero_on_localhost_without_cors() {
202
+ let cfg = ServerConfig::ephemeral();
203
+ assert_eq!(cfg.host, "localhost");
204
+ assert_eq!(cfg.port, 0);
205
+ assert_eq!(cfg.query_timeout, Duration::from_secs(30));
206
+ assert_eq!(cfg.cors_origin, None);
207
+ }
208
+
209
+ #[test]
210
+ fn with_cors_origin_sets_the_allowed_origin() {
211
+ let cfg = ServerConfig::ephemeral().with_cors_origin(HeaderValue::from_static("*"));
212
+ assert_eq!(cfg.cors_origin, Some(HeaderValue::from_static("*")));
184
213
  }
185
214
 
186
215
  #[test]
@@ -26,6 +26,7 @@ use super::{
26
26
  serve_with_state, table,
27
27
  };
28
28
  use crate::{DirSQL, Extension};
29
+ use axum::http::HeaderValue;
29
30
  use clap::{Args, Parser, Subcommand, ValueEnum};
30
31
  use serde_json::Value;
31
32
 
@@ -184,6 +185,12 @@ struct ServerArgs {
184
185
  #[arg(long, default_value_t = 7117)]
185
186
  port: u16,
186
187
 
188
+ /// Send `Access-Control-Allow-Origin: <ORIGIN>` so browser pages on that
189
+ /// origin (`*` for any) can call `/query` and open `/events`. Without it
190
+ /// the server sends no CORS headers.
191
+ #[arg(long = "cors-origin", value_name = "ORIGIN")]
192
+ cors_origin: Option<HeaderValue>,
193
+
187
194
  #[command(flatten)]
188
195
  common: ConfigArgs,
189
196
  }
@@ -330,7 +337,7 @@ pub fn run_cli(argv: Vec<String>) -> i32 {
330
337
  match cli.command.take() {
331
338
  Some(Command::Init(args)) => run_init(args),
332
339
  Some(Command::Query(args)) => run_query(args, &mut std::io::stdout()).await,
333
- Some(Command::Server(args)) => run_server(args).await,
340
+ Some(Command::Server(args)) => run_server(args, wait_for_shutdown()).await,
334
341
  Some(Command::Context) => {
335
342
  print!("{}", super::context::guide());
336
343
  0
@@ -462,11 +469,17 @@ fn run_init(args: InitArgs) -> u8 {
462
469
  }
463
470
  }
464
471
 
465
- async fn run_server(args: ServerArgs) -> u8 {
472
+ async fn run_server(
473
+ args: ServerArgs,
474
+ shutdown: impl std::future::Future<Output = std::io::Result<()>>,
475
+ ) -> u8 {
466
476
  // The server has no `--on-file`: clap rejects it as an unknown flag before
467
477
  // reaching here. Path-tables served over HTTP keep their stat columns.
468
478
  let state = load_state(&args.common, None);
469
- let server_config = ServerConfig::bind(args.host.clone(), args.port);
479
+ let mut server_config = ServerConfig::bind(args.host.clone(), args.port);
480
+ if let Some(origin) = args.cors_origin {
481
+ server_config = server_config.with_cors_origin(origin);
482
+ }
470
483
 
471
484
  let host = args.host.clone();
472
485
  let handle = match serve_with_state(server_config, state).await {
@@ -480,7 +493,7 @@ async fn run_server(args: ServerArgs) -> u8 {
480
493
  // Echo back the user-facing hostname (not the resolved IP SocketAddr).
481
494
  println!("Running at {host}:{}", handle.local_addr().port());
482
495
 
483
- if let Err(err) = wait_for_shutdown().await {
496
+ if let Err(err) = shutdown.await {
484
497
  eprintln!("dirsql: signal handler error: {err}");
485
498
  }
486
499
 
@@ -857,12 +870,68 @@ mod tests {
857
870
  }
858
871
  }
859
872
 
873
+ fn server_args(argv: &[&str]) -> ServerArgs {
874
+ match Cli::parse_from(argv).command {
875
+ Some(Command::Server(args)) => args,
876
+ other => panic!("expected a server subcommand, got {other:?}"),
877
+ }
878
+ }
879
+
880
+ #[tokio::test]
881
+ async fn run_server_exits_zero_after_a_clean_shutdown() {
882
+ let args = server_args(&[
883
+ "dirsql",
884
+ "server",
885
+ "--host",
886
+ "127.0.0.1",
887
+ "--port",
888
+ "0",
889
+ "-c",
890
+ "/nonexistent/.dirsql.toml",
891
+ ]);
892
+ assert_eq!(run_server(args, std::future::ready(Ok(()))).await, 0);
893
+ }
894
+
895
+ #[tokio::test]
896
+ async fn run_server_exits_one_when_the_bind_fails() {
897
+ let args = server_args(&[
898
+ "dirsql",
899
+ "server",
900
+ "--host",
901
+ "192.0.2.1",
902
+ "--port",
903
+ "9",
904
+ "-c",
905
+ "/nonexistent/.dirsql.toml",
906
+ ]);
907
+ assert_eq!(run_server(args, std::future::pending()).await, 1);
908
+ }
909
+
910
+ #[test]
911
+ fn server_subcommand_parses_a_cors_origin() {
912
+ match Cli::parse_from(["dirsql", "server", "--cors-origin", "http://localhost:3202"])
913
+ .command
914
+ {
915
+ Some(Command::Server(args)) => assert_eq!(
916
+ args.cors_origin,
917
+ Some(HeaderValue::from_static("http://localhost:3202"))
918
+ ),
919
+ other => panic!("expected a server subcommand, got {other:?}"),
920
+ }
921
+ }
922
+
923
+ #[test]
924
+ fn server_subcommand_rejects_a_cors_origin_that_is_not_a_header_value() {
925
+ assert!(Cli::try_parse_from(["dirsql", "server", "--cors-origin", "a\nb"]).is_err());
926
+ }
927
+
860
928
  #[test]
861
929
  fn server_subcommand_defaults_host_and_port() {
862
930
  match Cli::parse_from(["dirsql", "server"]).command {
863
931
  Some(Command::Server(args)) => {
864
932
  assert_eq!(args.host, "localhost");
865
933
  assert_eq!(args.port, 7117);
934
+ assert_eq!(args.cors_origin, None);
866
935
  }
867
936
  other => panic!("expected a server subcommand, got {other:?}"),
868
937
  }
@@ -2,9 +2,11 @@
2
2
 
3
3
  use std::sync::Arc;
4
4
 
5
+ use axum::http::{Method, header};
5
6
  use futures::stream::StreamExt;
6
7
  use tokio::net::TcpListener;
7
8
  use tokio::sync::{broadcast, oneshot, watch};
9
+ use tower_http::cors::CorsLayer;
8
10
 
9
11
  use super::router::{AppContext, router};
10
12
  use super::serialize::event_to_json;
@@ -51,7 +53,15 @@ pub async fn serve_with_state(
51
53
  cancel: cancel_rx,
52
54
  query_timeout: config.query_timeout,
53
55
  });
54
- let app = router(shared);
56
+ let mut app = router(shared);
57
+ if let Some(origin) = config.cors_origin {
58
+ app = app.layer(
59
+ CorsLayer::new()
60
+ .allow_origin(origin)
61
+ .allow_methods([Method::GET, Method::POST])
62
+ .allow_headers([header::CONTENT_TYPE]),
63
+ );
64
+ }
55
65
 
56
66
  let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
57
67
  let task = tokio::spawn(async move {