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.
- {beaver_db-2.4.1 → beaver_db-2.4.2}/.github/workflows/ci.yml +3 -0
- beaver_db-2.4.2/.github/workflows/pages.yml +62 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/.gitignore +1 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/CHANGELOG.md +17 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/PKG-INFO +6 -13
- {beaver_db-2.4.1 → beaver_db-2.4.2}/README.md +4 -11
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/__init__.py +1 -1
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/blobs.py +3 -3
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/dicts.py +15 -10
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/docs.py +42 -16
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/lists.py +2 -2
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/locks.py +1 -1
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/manager.py +18 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/queues.py +2 -2
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/vectors.py +2 -2
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/_quarto.yml +12 -5
- beaver_db-2.4.2/docs/beaver.scss +29 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/dev-concurrency.md +4 -21
- beaver_db-2.4.2/docs/img/beaver.webp +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/makefile +11 -1
- {beaver_db-2.4.1 → beaver_db-2.4.2}/pyproject.toml +1 -1
- beaver_db-2.4.2/site/build.py +76 -0
- beaver_db-2.4.2/site/check_snippets.py +61 -0
- beaver_db-2.4.2/site/fonts/OFL-inter.txt +93 -0
- beaver_db-2.4.2/site/fonts/OFL-jetbrainsmono.txt +93 -0
- beaver_db-2.4.2/site/fonts/OFL-nunito.txt +93 -0
- beaver_db-2.4.2/site/fonts/inter-400.woff2 +0 -0
- beaver_db-2.4.2/site/fonts/inter-500.woff2 +0 -0
- beaver_db-2.4.2/site/fonts/inter-600.woff2 +0 -0
- beaver_db-2.4.2/site/fonts/jbmono-400.woff2 +0 -0
- beaver_db-2.4.2/site/fonts/jbmono-500.woff2 +0 -0
- beaver_db-2.4.2/site/fonts/nunito-800.woff2 +0 -0
- beaver_db-2.4.2/site/img/beaver-180.png +0 -0
- beaver_db-2.4.2/site/img/beaver-32.png +0 -0
- beaver_db-2.4.2/site/img/beaver-96.webp +0 -0
- beaver_db-2.4.2/site/img/logo-400.webp +0 -0
- beaver_db-2.4.2/site/index.html +209 -0
- beaver_db-2.4.2/site/redirects.txt +24 -0
- beaver_db-2.4.2/site/site.css +148 -0
- beaver_db-2.4.2/site/site.js +69 -0
- beaver_db-2.4.2/tests/unit/test_release_2_4_2.py +122 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/uv.lock +1 -1
- beaver_db-2.4.1/docs/index.html +0 -620
- {beaver_db-2.4.1 → beaver_db-2.4.2}/.dockerignore +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/.github/workflows/release.yaml +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/.github/workflows/tests.yaml +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/.python-version +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/.vscode/settings.json +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/AGENTS.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/LICENSE +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/STATUS.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/_fracdex.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/api.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/bridge.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/cache.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/channels.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/cli/__init__.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/cli/discovery.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/cli/main.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/client.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/core.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/errors.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/events.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/graphs.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/indexing.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/interfaces.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/logs.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/migrate.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/queries.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/security.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/server.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/beaver/sketches.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/design.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/dockerfile +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/.gitignore +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/dev-architecture.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/dev-contributing.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/dev-search.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-blobs.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-channels.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-collections.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-deployment.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-dicts.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-lists.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-locks.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-logs.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-queues.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide-sketches.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/guide.md +0 -0
- /beaver_db-2.4.1/docs/intro.md → /beaver_db-2.4.2/docs/index.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/logo.png +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/migration-1x-to-2x.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/migration-rc3-to-rc4-lists.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/quickstart.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/superpowers/plans/2026-05-24-list-fracdex-ordering.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/docs/superpowers/specs/2026-05-24-list-fracdex-ordering-design.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/async_pubsub.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/blobs.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/cache.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/fts.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/fuzzy.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/general_test.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/graph.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/kvstore.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/list.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/locks.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/logs.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/pqueue.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/producer_consumer.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/publisher.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/pubsub.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/rerank.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/stress_vectors.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/subscriber.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/textual_chat.css +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/textual_chat.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/type_hints.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/examples/vector.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/15-enhance-cli-with-admin-commands-shell-piping-and-interactivity.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/18-enhanced-dump-and-load-for-etl.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/19-add-comprehensive-unit-integration-and-concurrency-test-suite.md +0 -0
- {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
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/27-batched-operations-api-batched.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/28-add-hnsw-vector-strategy-beaver-dbhnsw.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/36-implement-sid-consumers-dynamic-cli-universal-client-server.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/37-v2-release-phase-0-audit-and-stabilize.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/38-v2-release-phase-1-storage-engine.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/39-v2-release-phase-2-wrappers.md +0 -0
- {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
- {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
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/42-uniform-field-indexing-across-collections.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/43-singleton-silently-ignores-differing-kwargs-on-a-cache-hit.md +0 -0
- {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
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/1-refactor-vector-store-to-use-faiss.md +0 -0
- {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
- {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
- {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
- {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
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/16-add-clear-method-for-all-data-structures.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/17-replace-atomic-operations-with-custom-beaver-lock.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/2-comprehensive-async-wrappers.md +0 -0
- {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
- {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
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/23-global-event-listeners-api.md +0 -0
- {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
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/30-add-probabilistic-sketches-approximate-sets-counters.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/32-secure-dictionaries-encryption-and-hashing.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/34-composable-graph-algorithms-query-builder-graphrag.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/35-refactor-core-to-shared-interface-definition-sid-architecture.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/5-add-dblock-for-inter-process-synchronization.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/6-drop-in-replacement-for-beaver-rest-server-client.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/7-replace-faiss-with-simpler-linear-numpy-vectorial-search.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/8-first-class-synchronization-primitive.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/issues/closed/9-type-safe-wrappers-based-on-pydantic-compatible-models.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/logo.png +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/plans/2026-08-01-field-indexing-slices-1-2.md +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/pytest.ini +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/benchmark_lsh.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/concurrency/conftest.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/concurrency/test_concurrency.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/conftest.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/integration/__init__.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/integration/test_server_subprocess.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/integration/test_sync.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/perftest.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/test_docs_search_filters.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_api_expose.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_batched.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_blobs.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_bridge.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_channels.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_cli.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_client_local_only.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_client_roundtrip.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_client_roundtrip_lists.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_client_roundtrip_logs.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_client_roundtrip_queues.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_connect.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_db_version.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_dicts.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_dicts_decorated.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_docs.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_errors.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_events.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_field_indexing.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_fracdex.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_graphs.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_lists.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_lists_decorated.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_lists_fracdex.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_locks.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_logs.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_logs_decorated.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_lsh.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_manager_events.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_migrate.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_queues.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_queues_decorated.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_security.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_server_auth.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_server_errors.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_server_routes.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_sketchs.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_transactions.py +0 -0
- {beaver_db-2.4.1 → beaver_db-2.4.2}/tests/unit/test_vectors.py +0 -0
|
@@ -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
|
|
@@ -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.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: beaver-db
|
|
3
|
-
Version: 2.4.
|
|
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
|
|
117
|
-
* [**Full-Text & Fuzzy Search**](https://syalia.com/beaver/guide-collections.html
|
|
118
|
-
* [**Knowledge Graph**](https://syalia.com/beaver/guide-collections.html
|
|
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/
|
|
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
|
|
90
|
-
* [**Full-Text & Fuzzy Search**](https://syalia.com/beaver/guide-collections.html
|
|
91
|
-
* [**Knowledge Graph**](https://syalia.com/beaver/guide-collections.html
|
|
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/
|
|
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
|
|
@@ -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
|
-
@
|
|
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
|
-
@
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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[
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
-
@
|
|
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.
|
|
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
|
-
@
|
|
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.
|
|
@@ -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
|
-
@
|
|
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
|
-
@
|
|
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: "
|
|
20
|
+
logo: "img/beaver.webp"
|
|
16
21
|
pinned: true
|
|
17
22
|
search: true
|
|
18
23
|
left:
|
|
19
|
-
|
|
24
|
+
# The product page sits one level up, at the site root.
|
|
25
|
+
- href: ../index.html
|
|
20
26
|
text: Home
|
|
21
|
-
- href:
|
|
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
|
-
|
|
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 (
|
|
25
|
+
## Thread Safety (one connection)
|
|
26
26
|
|
|
27
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|