beaver-db 2.4.1__tar.gz → 2.4.2__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 (205) hide show
  1. {beaver_db-2.4.1 → beaver_db-2.4.2}/.github/workflows/ci.yml +3 -0
  2. beaver_db-2.4.2/.github/workflows/pages.yml +62 -0
  3. {beaver_db-2.4.1 → beaver_db-2.4.2}/.gitignore +1 -0
  4. {beaver_db-2.4.1 → beaver_db-2.4.2}/CHANGELOG.md +17 -0
  5. {beaver_db-2.4.1 → beaver_db-2.4.2}/PKG-INFO +6 -13
  6. {beaver_db-2.4.1 → beaver_db-2.4.2}/README.md +4 -11
  7. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/__init__.py +1 -1
  8. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/blobs.py +3 -3
  9. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/dicts.py +15 -10
  10. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/docs.py +42 -16
  11. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/lists.py +2 -2
  12. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/locks.py +1 -1
  13. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/manager.py +18 -0
  14. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/queues.py +2 -2
  15. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/vectors.py +2 -2
  16. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/_quarto.yml +12 -5
  17. beaver_db-2.4.2/docs/beaver.scss +29 -0
  18. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/dev-concurrency.md +4 -21
  19. beaver_db-2.4.2/docs/img/beaver.webp +0 -0
  20. {beaver_db-2.4.1 → beaver_db-2.4.2}/makefile +11 -1
  21. {beaver_db-2.4.1 → beaver_db-2.4.2}/pyproject.toml +1 -1
  22. beaver_db-2.4.2/site/build.py +76 -0
  23. beaver_db-2.4.2/site/check_snippets.py +61 -0
  24. beaver_db-2.4.2/site/fonts/OFL-inter.txt +93 -0
  25. beaver_db-2.4.2/site/fonts/OFL-jetbrainsmono.txt +93 -0
  26. beaver_db-2.4.2/site/fonts/OFL-nunito.txt +93 -0
  27. beaver_db-2.4.2/site/fonts/inter-400.woff2 +0 -0
  28. beaver_db-2.4.2/site/fonts/inter-500.woff2 +0 -0
  29. beaver_db-2.4.2/site/fonts/inter-600.woff2 +0 -0
  30. beaver_db-2.4.2/site/fonts/jbmono-400.woff2 +0 -0
  31. beaver_db-2.4.2/site/fonts/jbmono-500.woff2 +0 -0
  32. beaver_db-2.4.2/site/fonts/nunito-800.woff2 +0 -0
  33. beaver_db-2.4.2/site/img/beaver-180.png +0 -0
  34. beaver_db-2.4.2/site/img/beaver-32.png +0 -0
  35. beaver_db-2.4.2/site/img/beaver-96.webp +0 -0
  36. beaver_db-2.4.2/site/img/logo-400.webp +0 -0
  37. beaver_db-2.4.2/site/index.html +209 -0
  38. beaver_db-2.4.2/site/redirects.txt +24 -0
  39. beaver_db-2.4.2/site/site.css +148 -0
  40. beaver_db-2.4.2/site/site.js +69 -0
  41. beaver_db-2.4.2/tests/unit/test_release_2_4_2.py +122 -0
  42. {beaver_db-2.4.1 → beaver_db-2.4.2}/uv.lock +1 -1
  43. beaver_db-2.4.1/docs/index.html +0 -620
  44. {beaver_db-2.4.1 → beaver_db-2.4.2}/.dockerignore +0 -0
  45. {beaver_db-2.4.1 → beaver_db-2.4.2}/.github/workflows/release.yaml +0 -0
  46. {beaver_db-2.4.1 → beaver_db-2.4.2}/.github/workflows/tests.yaml +0 -0
  47. {beaver_db-2.4.1 → beaver_db-2.4.2}/.python-version +0 -0
  48. {beaver_db-2.4.1 → beaver_db-2.4.2}/.vscode/settings.json +0 -0
  49. {beaver_db-2.4.1 → beaver_db-2.4.2}/AGENTS.md +0 -0
  50. {beaver_db-2.4.1 → beaver_db-2.4.2}/LICENSE +0 -0
  51. {beaver_db-2.4.1 → beaver_db-2.4.2}/STATUS.md +0 -0
  52. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/_fracdex.py +0 -0
  53. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/api.py +0 -0
  54. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/bridge.py +0 -0
  55. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/cache.py +0 -0
  56. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/channels.py +0 -0
  57. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/cli/__init__.py +0 -0
  58. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/cli/discovery.py +0 -0
  59. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/cli/main.py +0 -0
  60. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/client.py +0 -0
  61. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/core.py +0 -0
  62. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/errors.py +0 -0
  63. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/events.py +0 -0
  64. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/graphs.py +0 -0
  65. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/indexing.py +0 -0
  66. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/interfaces.py +0 -0
  67. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/logs.py +0 -0
  68. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/migrate.py +0 -0
  69. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/queries.py +0 -0
  70. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/security.py +0 -0
  71. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/server.py +0 -0
  72. {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/sketches.py +0 -0
  73. {beaver_db-2.4.1 → beaver_db-2.4.2}/design.md +0 -0
  74. {beaver_db-2.4.1 → beaver_db-2.4.2}/dockerfile +0 -0
  75. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/.gitignore +0 -0
  76. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/dev-architecture.md +0 -0
  77. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/dev-contributing.md +0 -0
  78. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/dev-search.md +0 -0
  79. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-blobs.md +0 -0
  80. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-channels.md +0 -0
  81. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-collections.md +0 -0
  82. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-deployment.md +0 -0
  83. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-dicts.md +0 -0
  84. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-lists.md +0 -0
  85. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-locks.md +0 -0
  86. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-logs.md +0 -0
  87. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-queues.md +0 -0
  88. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-sketches.md +0 -0
  89. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide.md +0 -0
  90. /beaver_db-2.4.1/docs/intro.md → /beaver_db-2.4.2/docs/index.md +0 -0
  91. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/logo.png +0 -0
  92. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/migration-1x-to-2x.md +0 -0
  93. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/migration-rc3-to-rc4-lists.md +0 -0
  94. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/quickstart.md +0 -0
  95. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/superpowers/plans/2026-05-24-list-fracdex-ordering.md +0 -0
  96. {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/superpowers/specs/2026-05-24-list-fracdex-ordering-design.md +0 -0
  97. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/async_pubsub.py +0 -0
  98. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/blobs.py +0 -0
  99. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/cache.py +0 -0
  100. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/fts.py +0 -0
  101. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/fuzzy.py +0 -0
  102. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/general_test.py +0 -0
  103. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/graph.py +0 -0
  104. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/kvstore.py +0 -0
  105. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/list.py +0 -0
  106. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/locks.py +0 -0
  107. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/logs.py +0 -0
  108. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/pqueue.py +0 -0
  109. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/producer_consumer.py +0 -0
  110. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/publisher.py +0 -0
  111. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/pubsub.py +0 -0
  112. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/rerank.py +0 -0
  113. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/stress_vectors.py +0 -0
  114. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/subscriber.py +0 -0
  115. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/textual_chat.css +0 -0
  116. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/textual_chat.py +0 -0
  117. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/type_hints.py +0 -0
  118. {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/vector.py +0 -0
  119. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/15-enhance-cli-with-admin-commands-shell-piping-and-interactivity.md +0 -0
  120. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/18-enhanced-dump-and-load-for-etl.md +0 -0
  121. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/19-add-comprehensive-unit-integration-and-concurrency-test-suite.md +0 -0
  122. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/24-refactor-vectorindex-to-be-a-swappable-strategy-and-add-persistent-lsh-implementation.md +0 -0
  123. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/27-batched-operations-api-batched.md +0 -0
  124. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/28-add-hnsw-vector-strategy-beaver-dbhnsw.md +0 -0
  125. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/36-implement-sid-consumers-dynamic-cli-universal-client-server.md +0 -0
  126. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/37-v2-release-phase-0-audit-and-stabilize.md +0 -0
  127. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/38-v2-release-phase-1-storage-engine.md +0 -0
  128. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/39-v2-release-phase-2-wrappers.md +0 -0
  129. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/40-batched-reentry-deadlock-under-sync-portal-on-large-sequential-writes.md +0 -0
  130. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/41-2x-silently-opens-a-1x-database-and-goes-split-brain.md +0 -0
  131. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/42-uniform-field-indexing-across-collections.md +0 -0
  132. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/43-singleton-silently-ignores-differing-kwargs-on-a-cache-hit.md +0 -0
  133. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/44-filters-have-no-cli-or-wire-encoding-so-explain-cannot-be-exposed.md +0 -0
  134. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/1-refactor-vector-store-to-use-faiss.md +0 -0
  135. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/10-expose-dblock-functionality-on-all-high-level-data-managers.md +0 -0
  136. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/12-add-dump-method-for-json-export-to-all-data-managers.md +0 -0
  137. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/13-adopt-pydantic-deprecate-beavermodel-and-refactor-document-to-be-generic.md +0 -0
  138. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/14-deprecate-fire-based-cli-and-build-a-feature-rich-typer-and-rich-cli.md +0 -0
  139. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/16-add-clear-method-for-all-data-structures.md +0 -0
  140. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/17-replace-atomic-operations-with-custom-beaver-lock.md +0 -0
  141. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/2-comprehensive-async-wrappers.md +0 -0
  142. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/21-implement-dbcache-property-with-wal-invalidation-dummy-fallback-and-performance-stats.md +0 -0
  143. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/22-add-context-manager-support-and-robust-process-wide-cleanup-to-beaverdb.md +0 -0
  144. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/23-global-event-listeners-api.md +0 -0
  145. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/25-refactor-core-to-async-first-architecture-with-sync-bridge.md +0 -0
  146. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/30-add-probabilistic-sketches-approximate-sets-counters.md +0 -0
  147. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/32-secure-dictionaries-encryption-and-hashing.md +0 -0
  148. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/34-composable-graph-algorithms-query-builder-graphrag.md +0 -0
  149. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/35-refactor-core-to-shared-interface-definition-sid-architecture.md +0 -0
  150. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/5-add-dblock-for-inter-process-synchronization.md +0 -0
  151. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/6-drop-in-replacement-for-beaver-rest-server-client.md +0 -0
  152. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/7-replace-faiss-with-simpler-linear-numpy-vectorial-search.md +0 -0
  153. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/8-first-class-synchronization-primitive.md +0 -0
  154. {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/9-type-safe-wrappers-based-on-pydantic-compatible-models.md +0 -0
  155. {beaver_db-2.4.1 → beaver_db-2.4.2}/logo.png +0 -0
  156. {beaver_db-2.4.1 → beaver_db-2.4.2}/plans/2026-08-01-field-indexing-slices-1-2.md +0 -0
  157. {beaver_db-2.4.1 → beaver_db-2.4.2}/pytest.ini +0 -0
  158. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/benchmark_lsh.py +0 -0
  159. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/concurrency/conftest.py +0 -0
  160. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/concurrency/test_concurrency.py +0 -0
  161. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/conftest.py +0 -0
  162. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/integration/__init__.py +0 -0
  163. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/integration/test_server_subprocess.py +0 -0
  164. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/integration/test_sync.py +0 -0
  165. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/perftest.py +0 -0
  166. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/test_docs_search_filters.py +0 -0
  167. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_api_expose.py +0 -0
  168. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_batched.py +0 -0
  169. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_blobs.py +0 -0
  170. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_bridge.py +0 -0
  171. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_channels.py +0 -0
  172. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_cli.py +0 -0
  173. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_client_local_only.py +0 -0
  174. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_client_roundtrip.py +0 -0
  175. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_client_roundtrip_lists.py +0 -0
  176. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_client_roundtrip_logs.py +0 -0
  177. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_client_roundtrip_queues.py +0 -0
  178. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_connect.py +0 -0
  179. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_db_version.py +0 -0
  180. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_dicts.py +0 -0
  181. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_dicts_decorated.py +0 -0
  182. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_docs.py +0 -0
  183. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_errors.py +0 -0
  184. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_events.py +0 -0
  185. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_field_indexing.py +0 -0
  186. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_fracdex.py +0 -0
  187. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_graphs.py +0 -0
  188. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_lists.py +0 -0
  189. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_lists_decorated.py +0 -0
  190. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_lists_fracdex.py +0 -0
  191. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_locks.py +0 -0
  192. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_logs.py +0 -0
  193. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_logs_decorated.py +0 -0
  194. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_lsh.py +0 -0
  195. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_manager_events.py +0 -0
  196. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_migrate.py +0 -0
  197. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_queues.py +0 -0
  198. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_queues_decorated.py +0 -0
  199. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_security.py +0 -0
  200. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_server_auth.py +0 -0
  201. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_server_errors.py +0 -0
  202. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_server_routes.py +0 -0
  203. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_sketchs.py +0 -0
  204. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_transactions.py +0 -0
  205. {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_vectors.py +0 -0
@@ -25,3 +25,6 @@ jobs:
25
25
 
26
26
  - name: Run CI (format-check + unit tests)
27
27
  run: make ci
28
+
29
+ - name: Check the snippets on the product page
30
+ run: uv run python site/check_snippets.py
@@ -0,0 +1,62 @@
1
+ name: pages
2
+ # beaver.syalia.com: the product page in site/ at the root, the Quarto docs
3
+ # under /docs/, and a redirect page for each old docs URL. One artifact,
4
+ # assembled by site/build.py. Pull requests build it; main deploys it.
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ paths: ["site/**", "docs/**", "pyproject.toml", ".github/workflows/pages.yml"]
9
+ pull_request:
10
+ branches: [main]
11
+ paths: ["site/**", "docs/**", "pyproject.toml", ".github/workflows/pages.yml"]
12
+ workflow_dispatch:
13
+ permissions:
14
+ contents: read
15
+ pages: write
16
+ id-token: write
17
+ concurrency:
18
+ group: pages-${{ github.ref }}
19
+ cancel-in-progress: true
20
+ jobs:
21
+ build:
22
+ runs-on: ubuntu-latest
23
+ outputs:
24
+ build_type: ${{ steps.pages.outputs.build_type }}
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.12"
30
+ - uses: quarto-dev/quarto-actions/setup@v2
31
+ with:
32
+ version: 1.9.37
33
+ - name: Render the docs
34
+ run: quarto render docs
35
+ - name: Assemble the site
36
+ run: python site/build.py public
37
+ - uses: actions/upload-pages-artifact@v3
38
+ with:
39
+ path: public
40
+ # Deploying needs the repository's Pages source set to "GitHub Actions".
41
+ # While it is still the gh-pages branch, the deploy job is skipped and
42
+ # the old site stays up; re-run this workflow after switching.
43
+ - id: pages
44
+ name: Read the Pages source
45
+ env:
46
+ GH_TOKEN: ${{ github.token }}
47
+ run: |
48
+ build_type=$(gh api "repos/${{ github.repository }}/pages" --jq .build_type || echo none)
49
+ echo "build_type=$build_type" >> "$GITHUB_OUTPUT"
50
+ if [ "$build_type" != workflow ]; then
51
+ echo "::notice::Pages source is '$build_type', not 'workflow': the deploy job will be skipped."
52
+ fi
53
+ deploy:
54
+ needs: build
55
+ if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' && needs.build.outputs.build_type == 'workflow'
56
+ runs-on: ubuntu-latest
57
+ environment:
58
+ name: github-pages
59
+ url: ${{ steps.deploy.outputs.page_url }}
60
+ steps:
61
+ - id: deploy
62
+ uses: actions/deploy-pages@v4
@@ -14,3 +14,4 @@ wheels/
14
14
  .coverage
15
15
  _site
16
16
  benchmark
17
+ public/
@@ -2,6 +2,23 @@
2
2
 
3
3
  All notable changes to beaver-db will be recorded here.
4
4
 
5
+ ## 2.4.2 — 2026-10-30
6
+
7
+ ### Fixed
8
+
9
+ - **`db.lock()` could admit two waiters at once (#38).** The queue was ordered by a timestamp taken before the waiter's insert, so a waiter that stamped first and inserted second sorted ahead of the holder. The queue is now ordered by `rowid`, the order of arrival. A read-increment-write loop under the lock, 24 increments from 8 threads over two objects on one file, gave a duplicate in 2 of 30 runs on 2.4.1 and in 0 of 100 on 2.4.2.
10
+ - **Indexing documents in several batches was quadratic (#40).** Each document's delete scanned the whole FTS5 table. It now finds the rows through a `MATCH` on the id. Three batches of 5,000 short documents took 0.3 s, 25.7 s and 50.4 s on 2.4.1, and about 0.3 s each on 2.4.2. Ids with no tokens, such as `___`, still take the scan.
11
+ - **An expired `dict` key was not removed when read.** The delete ran inside the transaction that the `KeyError` rolled back, so the row stayed until it was overwritten.
12
+
13
+ ### Changed
14
+
15
+ - **Reads no longer write.** `dict.get`, `list.get`, `queue.peek`, `docs.get`, `vectors.get`, `blob.get` and `blob.fetch` took the manager's internal lock, which writes a row to acquire and another to release. They now take only a transaction. `dict.get` went from about 200 to about 700 calls per second through `BeaverDB`.
16
+ - **Several processes writing to one manager wait less.** The internal lock polls every 5 ms instead of every 100 ms. Two processes writing to one `dict` went from 22 to 156 writes per second, and four from 21 to 34.
17
+
18
+ ### Corrected
19
+
20
+ - The 2.4.0 entry said Starlette and uvicorn cancel a request task whenever a client disconnects. In our measurement 12,800 client aborts against a fast plain handler caused no cancellation, while a streaming response was cancelled on every disconnect. The fix stands, and what started the 2026-08-10 outage is not known.
21
+
5
22
  ## 2.4.1 — 2026-08-10
6
23
 
7
24
  ### Fixed
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: beaver-db
3
- Version: 2.4.1
3
+ Version: 2.4.2
4
4
  Summary: Fast, async-native, embedded, and multi-modal DB based on SQLite for AI-powered applications.
5
5
  License-File: LICENSE
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -61,13 +61,6 @@ Install the core library:
61
61
  pip install beaver-db
62
62
  ```
63
63
 
64
- > **Note:** `beaver-db 2.0` is currently in release-candidate cycle. The
65
- > CLI (`beaver` command), REST API server (`beaver serve`), `BeaverClient`
66
- > remote, and the published Docker image are all **landing in 2.0 final**
67
- > (target: June 2026). Today the library is consumed as a Python API only.
68
- > Track progress at `vault/Atlas/Architecture/2026-05-15-beaver-v2-release-plan.md`
69
- > in the workspace, or watch the `2.0` issues on GitHub.
70
-
71
64
  ## Quickstart
72
65
 
73
66
  Get up and running in 30 seconds. This example showcases a dictionary, a list, and full-text search in a single script.
@@ -113,14 +106,14 @@ db.close()
113
106
  * [**Persistent Priority Queue**](https://syalia.com/beaver/guide-queues.html): A high-performance, persistent priority queue perfect for task orchestration across multiple processes.
114
107
  * [**Probabilistic Sketches:**]() Track cardinality and membership for millions of items in constant space using HyperLogLog and Bloom Filters.
115
108
  * [**Document Collections**](https://syalia.com/beaver/guide-collections.html): Store rich documents combining a vector embedding and Pydantic-based metadata.
116
- * [**Vector Search**](https://syalia.com/beaver/guide-collections.html%23vector-search): Fast, multi-process-safe linear vector search using an in-memory `numpy`-based index.
117
- * [**Full-Text & Fuzzy Search**](https://syalia.com/beaver/guide-collections.html%23full-text-fuzzy-search): Automatically index and search through document metadata using SQLite's FTS5 engine, with optional fuzzy search for typo-tolerant matching.
118
- * [**Knowledge Graph**](https://syalia.com/beaver/guide-collections.html%23knowledge-graph): Create directed, labeled relationships between documents and traverse the graph to find neighbors or perform multi-hop walks.
109
+ * [**Vector Search**](https://syalia.com/beaver/guide-collections.html#vector-search-semantic): Fast, multi-process-safe linear vector search using an in-memory `numpy`-based index.
110
+ * [**Full-Text & Fuzzy Search**](https://syalia.com/beaver/guide-collections.html#full-text-search-fts): Automatically index and search through document metadata using SQLite's FTS5 engine, with optional fuzzy search for typo-tolerant matching.
111
+ * [**Knowledge Graph**](https://syalia.com/beaver/guide-collections.html#graph-relationships): Create directed, labeled relationships between documents and traverse the graph to find neighbors or perform multi-hop walks.
119
112
  * [**Pub/Sub System**](https://syalia.com/beaver/guide-channels.html): A powerful, thread and process-safe publish-subscribe system for real-time messaging with a fan-out architecture.
120
113
  * [**Time-Indexed Logs**](https://syalia.com/beaver/guide-logs.html): A specialized data structure for structured, time-series logs. Query historical data by time range or create a live, aggregated view.
121
114
  * [**Event-Driven Callbacks**](https://syalia.com/beaver/guide-channels.html): Listen for database changes in real-time. Subscribe to events on specific managers to trigger workflows or update UIs.
122
115
  * [**Inter-Process Locking**](https://syalia.com/beaver/guide-locks.html): Robust, deadlock-proof locks. Use `db.lock('task_name')` to coordinate arbitrary scripts, or `with db.list('my_list') as l:` to perform atomic, multi-step operations.
123
- * [**Pydantic Support**](https://syalia.com/beaver/dev-architecture.html%23type-safe-models): Optionally associate `pydantic.BaseModel`s with any data structure for automatic, recursive data validation and (de)serialization.
116
+ * [**Pydantic Support**](https://syalia.com/beaver/guide.html#type-safe-models-with-pydantic): Optionally associate `pydantic.BaseModel`s with any data structure for automatic, recursive data validation and (de)serialization.
124
117
  * **Data Export**: Dump most data structures to a portable JSON file with a single `.dump()` command. (Full backup/restore symmetry — `.load()` across all managers, plus the `beaver` CLI and REST server — lands in `2.0` final; tracked in issues #15, #18, #36.)
125
118
 
126
119
  ## Documentation
@@ -34,13 +34,6 @@ Install the core library:
34
34
  pip install beaver-db
35
35
  ```
36
36
 
37
- > **Note:** `beaver-db 2.0` is currently in release-candidate cycle. The
38
- > CLI (`beaver` command), REST API server (`beaver serve`), `BeaverClient`
39
- > remote, and the published Docker image are all **landing in 2.0 final**
40
- > (target: June 2026). Today the library is consumed as a Python API only.
41
- > Track progress at `vault/Atlas/Architecture/2026-05-15-beaver-v2-release-plan.md`
42
- > in the workspace, or watch the `2.0` issues on GitHub.
43
-
44
37
  ## Quickstart
45
38
 
46
39
  Get up and running in 30 seconds. This example showcases a dictionary, a list, and full-text search in a single script.
@@ -86,14 +79,14 @@ db.close()
86
79
  * [**Persistent Priority Queue**](https://syalia.com/beaver/guide-queues.html): A high-performance, persistent priority queue perfect for task orchestration across multiple processes.
87
80
  * [**Probabilistic Sketches:**]() Track cardinality and membership for millions of items in constant space using HyperLogLog and Bloom Filters.
88
81
  * [**Document Collections**](https://syalia.com/beaver/guide-collections.html): Store rich documents combining a vector embedding and Pydantic-based metadata.
89
- * [**Vector Search**](https://syalia.com/beaver/guide-collections.html%23vector-search): Fast, multi-process-safe linear vector search using an in-memory `numpy`-based index.
90
- * [**Full-Text & Fuzzy Search**](https://syalia.com/beaver/guide-collections.html%23full-text-fuzzy-search): Automatically index and search through document metadata using SQLite's FTS5 engine, with optional fuzzy search for typo-tolerant matching.
91
- * [**Knowledge Graph**](https://syalia.com/beaver/guide-collections.html%23knowledge-graph): Create directed, labeled relationships between documents and traverse the graph to find neighbors or perform multi-hop walks.
82
+ * [**Vector Search**](https://syalia.com/beaver/guide-collections.html#vector-search-semantic): Fast, multi-process-safe linear vector search using an in-memory `numpy`-based index.
83
+ * [**Full-Text & Fuzzy Search**](https://syalia.com/beaver/guide-collections.html#full-text-search-fts): Automatically index and search through document metadata using SQLite's FTS5 engine, with optional fuzzy search for typo-tolerant matching.
84
+ * [**Knowledge Graph**](https://syalia.com/beaver/guide-collections.html#graph-relationships): Create directed, labeled relationships between documents and traverse the graph to find neighbors or perform multi-hop walks.
92
85
  * [**Pub/Sub System**](https://syalia.com/beaver/guide-channels.html): A powerful, thread and process-safe publish-subscribe system for real-time messaging with a fan-out architecture.
93
86
  * [**Time-Indexed Logs**](https://syalia.com/beaver/guide-logs.html): A specialized data structure for structured, time-series logs. Query historical data by time range or create a live, aggregated view.
94
87
  * [**Event-Driven Callbacks**](https://syalia.com/beaver/guide-channels.html): Listen for database changes in real-time. Subscribe to events on specific managers to trigger workflows or update UIs.
95
88
  * [**Inter-Process Locking**](https://syalia.com/beaver/guide-locks.html): Robust, deadlock-proof locks. Use `db.lock('task_name')` to coordinate arbitrary scripts, or `with db.list('my_list') as l:` to perform atomic, multi-step operations.
96
- * [**Pydantic Support**](https://syalia.com/beaver/dev-architecture.html%23type-safe-models): Optionally associate `pydantic.BaseModel`s with any data structure for automatic, recursive data validation and (de)serialization.
89
+ * [**Pydantic Support**](https://syalia.com/beaver/guide.html#type-safe-models-with-pydantic): Optionally associate `pydantic.BaseModel`s with any data structure for automatic, recursive data validation and (de)serialization.
97
90
  * **Data Export**: Dump most data structures to a portable JSON file with a single `.dump()` command. (Full backup/restore symmetry — `.load()` across all managers, plus the `beaver` CLI and REST server — lands in `2.0` final; tracked in issues #15, #18, #36.)
98
91
 
99
92
  ## Documentation
@@ -11,7 +11,7 @@ from .queries import q
11
11
  from .security import Secret
12
12
  from .errors import BeaverRemoteError, LocalOnlyError
13
13
 
14
- __version__ = "2.4.1"
14
+ __version__ = "2.4.2"
15
15
 
16
16
 
17
17
  def connect(source: str, *, sync: bool = True, api_key: str | None = None):
@@ -7,7 +7,7 @@ from typing import (
7
7
 
8
8
  from pydantic import BaseModel
9
9
 
10
- from .manager import AsyncBeaverBase, atomic, emits
10
+ from .manager import AsyncBeaverBase, atomic, read_atomic, emits
11
11
  from .interfaces import IAsyncBeaverBlob, BlobItem
12
12
 
13
13
 
@@ -75,7 +75,7 @@ class AsyncBeaverBlob[T: BaseModel](AsyncBeaverBase[T], IAsyncBeaverBlob[T]):
75
75
  (self._name, key, data, meta_json),
76
76
  )
77
77
 
78
- @atomic
78
+ @read_atomic
79
79
  async def fetch(self, key: str) -> BlobItem:
80
80
  """
81
81
  Retrieves the full BlobItem (data + metadata).
@@ -93,7 +93,7 @@ class AsyncBeaverBlob[T: BaseModel](AsyncBeaverBase[T], IAsyncBeaverBlob[T]):
93
93
  meta = json.loads(row["metadata"]) if row["metadata"] else None
94
94
  return BlobItem(key=key, data=row["data"], metadata=meta)
95
95
 
96
- @atomic
96
+ @read_atomic
97
97
  async def get(self, key: str) -> bytes:
98
98
  """
99
99
  Retrieves just the binary data for a key.
@@ -210,17 +210,17 @@ class AsyncBeaverDict[T: BaseModel](AsyncBeaverBase[T], IAsyncBeaverDict[T]):
210
210
  @expose(
211
211
  path="/{key}", method="GET", cli_name="get", cli_help="Get the value for a key."
212
212
  )
213
- @atomic
214
213
  async def get(self, key: str) -> T:
215
214
  """Retrieves a value for a key. Raises KeyError if missing or expired."""
216
215
  if self._secret_arg and not self._cipher:
217
216
  await self._setup_security(self._secret_arg)
218
217
 
219
- cursor = await self.connection.execute(
220
- "SELECT value, expires_at FROM __beaver_dicts__ WHERE dict_name = ? AND key = ?",
221
- (self._name, key),
222
- )
223
- result = await cursor.fetchone()
218
+ async with self._db.transaction():
219
+ cursor = await self.connection.execute(
220
+ "SELECT value, expires_at FROM __beaver_dicts__ WHERE dict_name = ? AND key = ?",
221
+ (self._name, key),
222
+ )
223
+ result = await cursor.fetchone()
224
224
 
225
225
  if result is None:
226
226
  raise KeyError(f"Key '{key}' not found in dictionary '{self._name}'")
@@ -228,16 +228,21 @@ class AsyncBeaverDict[T: BaseModel](AsyncBeaverBase[T], IAsyncBeaverDict[T]):
228
228
  raw_value, expires_at = result["value"], result["expires_at"]
229
229
 
230
230
  if expires_at is not None and time.time() > expires_at:
231
- await self.connection.execute(
232
- "DELETE FROM __beaver_dicts__ WHERE dict_name = ? AND key = ?",
233
- (self._name, key),
234
- )
231
+ # Outside the read's transaction: the KeyError would roll this back.
232
+ await self._delete_expired(key)
235
233
  raise KeyError(
236
234
  f"Key '{key}' not found in dictionary '{self._name}' (expired)"
237
235
  )
238
236
 
239
237
  return self._deserialize(raw_value)
240
238
 
239
+ @atomic
240
+ async def _delete_expired(self, key: str):
241
+ await self.connection.execute(
242
+ "DELETE FROM __beaver_dicts__ WHERE dict_name = ? AND key = ? AND expires_at < ?",
243
+ (self._name, key, time.time()),
244
+ )
245
+
241
246
  @expose(path="/{key}", method="DELETE", cli_name="delete", cli_help="Delete a key.")
242
247
  @emits("del", payload=lambda key, *args, **kwargs: dict(key=key))
243
248
  @atomic
@@ -13,7 +13,7 @@ from pydantic import BaseModel
13
13
 
14
14
  from . import indexing
15
15
  from .queries import Filter
16
- from .manager import AsyncBeaverBase, atomic, emits
16
+ from .manager import AsyncBeaverBase, atomic, read_atomic, emits
17
17
  from .interfaces import Document, ScoredDocument, IDocumentQuery
18
18
 
19
19
 
@@ -106,6 +106,41 @@ class DocumentQuery[T: BaseModel](IDocumentQuery[T]):
106
106
  yield doc
107
107
 
108
108
 
109
+ def _fts_id_filter(item_id: str) -> str | None:
110
+ """The FTS5 query that finds an id's rows, or None if it has no tokens."""
111
+ if not any(c.isalnum() for c in item_id):
112
+ return None
113
+ return 'item_id:"' + item_id.replace('"', '""') + '"'
114
+
115
+
116
+ async def _delete_fts_rows(conn, collection: str, item_ids: list[str]) -> None:
117
+ """Delete the FTS rows of these ids.
118
+
119
+ FTS5 cannot use an index for `item_id = ?`, so that filter alone scans the whole
120
+ table for every id. MATCH finds the candidates through the index, and the
121
+ equality then drops other ids that tokenize alike. An id with no tokens cannot
122
+ be found by MATCH and takes the scan.
123
+ """
124
+ matched, scanned = [], []
125
+ for item_id in item_ids:
126
+ query = _fts_id_filter(item_id)
127
+ if query is None:
128
+ scanned.append((collection, item_id))
129
+ else:
130
+ matched.append((query, collection, item_id))
131
+ await conn.executemany(
132
+ """
133
+ DELETE FROM __beaver_fts_index__
134
+ WHERE __beaver_fts_index__ MATCH ? AND collection = ? AND item_id = ?
135
+ """,
136
+ matched,
137
+ )
138
+ await conn.executemany(
139
+ "DELETE FROM __beaver_fts_index__ WHERE collection = ? AND item_id = ?",
140
+ scanned,
141
+ )
142
+
143
+
109
144
  class AsyncDocumentsBatch[T: BaseModel]:
110
145
  """Async context manager for buffered bulk document indexing.
111
146
 
@@ -144,7 +179,7 @@ class AsyncDocumentsBatch[T: BaseModel]:
144
179
  return
145
180
 
146
181
  doc_rows: list[tuple[str, str, str]] = []
147
- fts_delete_ids: list[tuple[str, str]] = []
182
+ fts_delete_ids: list[str] = []
148
183
  fts_rows: list[tuple[str, str, str, str]] = []
149
184
  trigram_delete_ids: list[tuple[str, str]] = []
150
185
  trigram_rows: list[tuple[str, str, str]] = []
@@ -158,7 +193,7 @@ class AsyncDocumentsBatch[T: BaseModel]:
158
193
 
159
194
  # Always clear stale FTS / trigram rows for this id (mirrors the
160
195
  # non-batched path so re-indexing the same id is consistent).
161
- fts_delete_ids.append((self._manager._name, doc.id))
196
+ fts_delete_ids.append(doc.id)
162
197
  trigram_delete_ids.append((self._manager._name, doc.id))
163
198
 
164
199
  flat = list(_flatten_document(doc.body))
@@ -186,10 +221,7 @@ class AsyncDocumentsBatch[T: BaseModel]:
186
221
  """,
187
222
  doc_rows,
188
223
  )
189
- await conn.executemany(
190
- "DELETE FROM __beaver_fts_index__ WHERE collection = ? AND item_id = ?",
191
- fts_delete_ids,
192
- )
224
+ await _delete_fts_rows(conn, self._manager._name, fts_delete_ids)
193
225
  if fts_rows:
194
226
  await conn.executemany(
195
227
  """
@@ -270,10 +302,7 @@ class AsyncBeaverDocuments[T: BaseModel](AsyncBeaverBase[T]):
270
302
  )
271
303
 
272
304
  # 2. FTS Update (Flatten -> Delete Old -> Insert New)
273
- await self.connection.execute(
274
- "DELETE FROM __beaver_fts_index__ WHERE collection = ? AND item_id = ?",
275
- (self._name, doc.id),
276
- )
305
+ await _delete_fts_rows(self.connection, self._name, [doc.id])
277
306
 
278
307
  fts_rows = []
279
308
  for field_path, content in _flatten_document(doc.body):
@@ -323,7 +352,7 @@ class AsyncBeaverDocuments[T: BaseModel](AsyncBeaverBase[T]):
323
352
  [(self._name, item_id, t) for t in trigrams],
324
353
  )
325
354
 
326
- @atomic
355
+ @read_atomic
327
356
  async def get(self, id: str) -> Document[T]:
328
357
  """Retrieves a document by ID."""
329
358
  cursor = await self.connection.execute(
@@ -369,10 +398,7 @@ class AsyncBeaverDocuments[T: BaseModel](AsyncBeaverBase[T]):
369
398
  "DELETE FROM __beaver_documents__ WHERE collection = ? AND item_id = ?",
370
399
  (self._name, doc_id),
371
400
  )
372
- await self.connection.execute(
373
- "DELETE FROM __beaver_fts_index__ WHERE collection = ? AND item_id = ?",
374
- (self._name, doc_id),
375
- )
401
+ await _delete_fts_rows(self.connection, self._name, [doc_id])
376
402
  await self.connection.execute(
377
403
  "DELETE FROM __beaver_trigrams__ WHERE collection = ? AND item_id = ?",
378
404
  (self._name, doc_id),
@@ -8,7 +8,7 @@ from datetime import datetime, timezone
8
8
  from pydantic import BaseModel
9
9
 
10
10
  from .api import expose, local_only
11
- from .manager import AsyncBeaverBase, atomic, emits
11
+ from .manager import AsyncBeaverBase, atomic, read_atomic, emits
12
12
  from .interfaces import IAsyncBeaverList
13
13
  from ._fracdex import key_between
14
14
 
@@ -201,7 +201,7 @@ class AsyncBeaverList[T: BaseModel](AsyncBeaverBase[T], IAsyncBeaverList[T]):
201
201
  cli_name="get",
202
202
  cli_help="Retrieve an item at an index.",
203
203
  )
204
- @atomic
204
+ @read_atomic
205
205
  async def get(self, index: Union[int, slice]) -> T | list[T]:
206
206
  """
207
207
  Retrieves an item or slice from the list.
@@ -102,7 +102,7 @@ class AsyncBeaverLock(IAsyncBeaverLock):
102
102
  """
103
103
  SELECT waiter_id FROM __beaver_lock_waiters__
104
104
  WHERE lock_name = ?
105
- ORDER BY requested_at ASC, rowid ASC
105
+ ORDER BY rowid ASC
106
106
  LIMIT 1
107
107
  """,
108
108
  (self._lock_name,),
@@ -59,6 +59,7 @@ class AsyncBeaverBase[T: BaseModel]:
59
59
  internal_lock_name,
60
60
  timeout=5.0, # Short timeout for internal operations
61
61
  lock_ttl=5.0, # Short TTL to clear crashes
62
+ poll_interval=0.005, # Held for milliseconds, so wait about as long
62
63
  )
63
64
 
64
65
  @property
@@ -173,6 +174,23 @@ def atomic(func):
173
174
  return wrapper
174
175
 
175
176
 
177
+ def read_atomic(func):
178
+ """
179
+ A decorator to run a read in a database transaction.
180
+
181
+ The transaction keeps it from seeing another task's uncommitted writes on the
182
+ shared connection. It takes no manager lock: that lock writes a row to acquire
183
+ and another to release, which would make every read a write.
184
+ """
185
+
186
+ @functools.wraps(func)
187
+ async def wrapper(self: AsyncBeaverBase, *args, **kwargs):
188
+ async with self._db.transaction():
189
+ return await func(self, *args, **kwargs)
190
+
191
+ return wrapper
192
+
193
+
176
194
  def emits(event: str | None = None, payload: Callable | None = None):
177
195
  """
178
196
  A decorator to emit an event after a manager method completes.
@@ -9,7 +9,7 @@ from typing import (
9
9
  from pydantic import BaseModel
10
10
 
11
11
  from .api import expose, local_only
12
- from .manager import AsyncBeaverBase, atomic, emits
12
+ from .manager import AsyncBeaverBase, atomic, read_atomic, emits
13
13
  from .interfaces import QueueItem, IAsyncBeaverQueue
14
14
 
15
15
 
@@ -76,7 +76,7 @@ class AsyncBeaverQueue[T: BaseModel](AsyncBeaverBase[T], IAsyncBeaverQueue[T]):
76
76
  @expose(
77
77
  path="/peek", method="GET", cli_name="peek", cli_help="Peek at the next item."
78
78
  )
79
- @atomic
79
+ @read_atomic
80
80
  async def peek(self) -> QueueItem[T] | None:
81
81
  """
82
82
  Retrieves the first item of the queue without removing it.
@@ -9,7 +9,7 @@ from typing import (
9
9
 
10
10
  from pydantic import BaseModel
11
11
 
12
- from .manager import AsyncBeaverBase, atomic, emits
12
+ from .manager import AsyncBeaverBase, atomic, read_atomic, emits
13
13
  from .interfaces import IAsyncBeaverVectors, VectorItem
14
14
  from .queries import Filter
15
15
 
@@ -169,7 +169,7 @@ class AsyncBeaverVectors[T: BaseModel](AsyncBeaverBase[T], IAsyncBeaverVectors[T
169
169
  (self._name, bucket_id, id),
170
170
  )
171
171
 
172
- @atomic
172
+ @read_atomic
173
173
  async def get(self, id: str) -> VectorItem[T]:
174
174
  cursor = await self.connection.execute(
175
175
  "SELECT vector, metadata FROM __beaver_vectors__ WHERE collection = ? AND item_id = ?",
@@ -2,23 +2,29 @@
2
2
  project:
3
3
  type: website
4
4
  output-dir: _site
5
+ # Internal design notes stay in the repo, not on the published docs.
6
+ render:
7
+ - "*.md"
8
+ - "!superpowers/"
5
9
 
6
10
  # Website-level metadata
7
11
  website:
8
12
  title: "BeaverDB"
13
+ site-url: https://beaver.syalia.com/docs/
9
14
  # author: "SYALIA S.R.L."
10
15
  # license: "CC BY-NC-SA 4.0"
11
16
 
12
17
 
13
18
  # --- Navigation Bar ---
14
19
  navbar:
15
- logo: "logo.png"
20
+ logo: "img/beaver.webp"
16
21
  pinned: true
17
22
  search: true
18
23
  left:
19
- - href: index.html
24
+ # The product page sits one level up, at the site root.
25
+ - href: ../index.html
20
26
  text: Home
21
- - href: intro.md
27
+ - href: index.md
22
28
  text: Introduction
23
29
  - href: quickstart.md
24
30
  text: Quickstart
@@ -77,13 +83,14 @@ website:
77
83
  format:
78
84
  html:
79
85
  theme:
80
- light: cosmo
86
+ light: [cosmo, beaver.scss]
81
87
  # dark:
82
88
  toc: true
83
89
  toc-depth: 3
84
90
  code-fold: true
85
91
  code-copy: true
86
- mainfont: "Inter"
92
+ # Plain math keeps the docs free of CDN scripts; the docs only write $T$.
93
+ html-math-method: plain
87
94
 
88
95
  # # --- PDF Output Format (Optional) ---
89
96
  # # This format can still be used to generate a PDF of individual pages
@@ -0,0 +1,29 @@
1
+ /*-- scss:defaults --*/
2
+ // Beaver's colours from logo.png on top of cosmo, so the docs match the
3
+ // product page. No web font import: the fonts come from the site itself.
4
+ $web-font-path: false;
5
+ $navy: #1a2757;
6
+ $fur: #cc722d;
7
+ $paper: #fbf6ee;
8
+ $primary: $navy;
9
+ $body-bg: $paper;
10
+ $body-color: $navy;
11
+ $link-color: #a85a1f;
12
+ $navbar-bg: #fffdf9;
13
+ $navbar-fg: $navy;
14
+ $font-family-sans-serif: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
15
+ $font-family-monospace: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
16
+ $headings-font-family: "Nunito", "Inter", system-ui, sans-serif;
17
+ $headings-font-weight: 800;
18
+
19
+ /*-- scss:rules --*/
20
+ // Paths are relative to docs/: Quarto copies the files from site/fonts/
21
+ // into the rendered docs, so they work wherever the docs are served.
22
+ @font-face { font-family: "Nunito"; font-weight: 800; font-display: swap; src: url("../site/fonts/nunito-800.woff2") format("woff2"); }
23
+ @font-face { font-family: "Inter"; font-weight: 400; font-display: swap; src: url("../site/fonts/inter-400.woff2") format("woff2"); }
24
+ @font-face { font-family: "Inter"; font-weight: 600; font-display: swap; src: url("../site/fonts/inter-600.woff2") format("woff2"); }
25
+ @font-face { font-family: "JetBrains Mono"; font-weight: 400; font-display: swap; src: url("../site/fonts/jbmono-400.woff2") format("woff2"); }
26
+
27
+ .navbar { border-bottom: 2px solid $navy; }
28
+ .navbar-brand .navbar-title { font-family: "Nunito", "Inter", sans-serif; font-weight: 800; }
29
+ .navbar-logo { border-radius: 8px; }
@@ -22,30 +22,13 @@ In **WAL mode**:
22
22
 
23
23
  This allows BeaverDB to handle high-throughput scenarios (e.g., a logger writing events while an API server queries them) without lock contention errors.
24
24
 
25
- ## Thread Safety (`threading.local`)
25
+ ## Thread Safety (one connection)
26
26
 
27
- SQLite connections cannot be shared across threads. If two threads try to write to the same `sqlite3.Connection` object, the application will crash or corrupt memory.
27
+ `AsyncBeaverDB` opens **one** aiosqlite connection, and aiosqlite runs every statement on one worker thread. `BeaverDB`, the synchronous facade, runs that engine on a reactor thread and sends each call there, so any number of threads can share one instance.
28
28
 
29
- BeaverDB manages this automatically using **Thread-Local Storage**.
29
+ Every statement in a process therefore runs in turn: a slow query delays all the others, including reads of unrelated data. A second `BeaverDB` on the same file opens its own connection, and SQLite's file locking mediates between them.
30
30
 
31
- ```python
32
- # Internal Logic
33
- class BeaverDB:
34
- def __init__(self):
35
- self._thread_local = threading.local()
36
-
37
- @property
38
- def connection(self):
39
- # If this thread has never asked for a connection, open one.
40
- if not hasattr(self._thread_local, "conn"):
41
- self._thread_local.conn = sqlite3.connect(...)
42
-
43
- return self._thread_local.conn
44
- ```
45
-
46
- * When Thread A calls `db.dict("x")`, it gets a connection dedicated to Thread A.
47
- * When Thread B calls `db.dict("x")`, it gets a different connection dedicated to Thread B.
48
- * Both connections talk to the same file, mediated by SQLite's file locking.
31
+ Reads that take a transaction (`dict.get`, `list.get`, `docs.get` and others) do so to avoid seeing another task's uncommitted writes on the shared connection. They do not take the manager's lock, whose acquire and release each write a row.
49
32
 
50
33
  ## The in-process write lock (`Transaction`)
51
34
 
Binary file
@@ -6,7 +6,17 @@ sync:
6
6
 
7
7
  .PHONY: docs
8
8
  docs:
9
- cd docs && quarto publish gh-pages
9
+ quarto preview docs
10
+
11
+ # The Pages artifact, built as .github/workflows/pages.yml builds it.
12
+ .PHONY: site
13
+ site:
14
+ quarto render docs
15
+ uv run python site/build.py public
16
+
17
+ .PHONY: check-snippets
18
+ check-snippets:
19
+ uv run python site/check_snippets.py
10
20
 
11
21
  .PHONY: bugfix
12
22
  bugfix:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "beaver-db"
3
- version = "2.4.1"
3
+ version = "2.4.2"
4
4
  description = "Fast, async-native, embedded, and multi-modal DB based on SQLite for AI-powered applications."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"