datahike-browser-tests 1.0.0
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.
- package/.circleci/config.yml +405 -0
- package/.circleci/scripts/gen_ci.clj +194 -0
- package/.cirrus.yml +60 -0
- package/.clj-kondo/babashka/sci/config.edn +1 -0
- package/.clj-kondo/babashka/sci/sci/core.clj +9 -0
- package/.clj-kondo/config.edn +95 -0
- package/.dir-locals.el +2 -0
- package/.github/FUNDING.yml +3 -0
- package/.github/ISSUE_TEMPLATE/1-bug-report.yml +68 -0
- package/.github/ISSUE_TEMPLATE/2-feature-request.yml +28 -0
- package/.github/ISSUE_TEMPLATE/config.yml +6 -0
- package/.github/pull_request_template.md +24 -0
- package/.github/workflows/native-image.yml +84 -0
- package/LICENSE +203 -0
- package/README.md +273 -0
- package/bb/deps.edn +9 -0
- package/bb/resources/github-fingerprints +3 -0
- package/bb/resources/native-image-tests/run-bb-pod-tests.clj +162 -0
- package/bb/resources/native-image-tests/run-libdatahike-tests +12 -0
- package/bb/resources/native-image-tests/run-native-image-tests +74 -0
- package/bb/resources/native-image-tests/run-python-tests +22 -0
- package/bb/resources/native-image-tests/testconfig.attr-refs.edn +6 -0
- package/bb/resources/native-image-tests/testconfig.edn +5 -0
- package/bb/resources/template/.settings/org.eclipse.jdt.apt.core.prefs +2 -0
- package/bb/resources/template/.settings/org.eclipse.jdt.core.prefs +9 -0
- package/bb/resources/template/.settings/org.eclipse.m2e.core.prefs +4 -0
- package/bb/resources/template/pom.xml +22 -0
- package/bb/src/tools/build.clj +132 -0
- package/bb/src/tools/clj_kondo.clj +32 -0
- package/bb/src/tools/deploy.clj +26 -0
- package/bb/src/tools/examples.clj +19 -0
- package/bb/src/tools/npm.clj +100 -0
- package/bb/src/tools/python.clj +14 -0
- package/bb/src/tools/release.clj +94 -0
- package/bb/src/tools/test.clj +148 -0
- package/bb/src/tools/version.clj +47 -0
- package/bb.edn +269 -0
- package/benchmark/src/benchmark/cli.clj +195 -0
- package/benchmark/src/benchmark/compare.clj +157 -0
- package/benchmark/src/benchmark/config.clj +316 -0
- package/benchmark/src/benchmark/measure.clj +187 -0
- package/benchmark/src/benchmark/store.clj +190 -0
- package/benchmark/test/benchmark/measure_test.clj +156 -0
- package/build.clj +30 -0
- package/config.edn +49 -0
- package/deps.edn +138 -0
- package/dev/sandbox.clj +82 -0
- package/dev/sandbox.cljs +127 -0
- package/dev/sandbox_benchmarks.clj +27 -0
- package/dev/sandbox_client.clj +87 -0
- package/dev/sandbox_transact_bench.clj +109 -0
- package/dev/user.clj +79 -0
- package/doc/README.md +96 -0
- package/doc/adl/README.md +6 -0
- package/doc/adl/adr-000-adr.org +28 -0
- package/doc/adl/adr-001-attribute-references.org +15 -0
- package/doc/adl/adr-002-build-tooling.org +54 -0
- package/doc/adl/adr-003-db-meta-data.md +52 -0
- package/doc/adl/adr-004-github-flow.md +40 -0
- package/doc/adl/adr-XYZ-template.md +30 -0
- package/doc/adl/index.org +3 -0
- package/doc/assets/datahike-logo.svg +3 -0
- package/doc/assets/datahiking-invoice.org +85 -0
- package/doc/assets/hhtree2.png +0 -0
- package/doc/assets/network_topology.svg +624 -0
- package/doc/assets/perf.png +0 -0
- package/doc/assets/schema_mindmap.mm +132 -0
- package/doc/assets/schema_mindmap.svg +970 -0
- package/doc/assets/temporal_index.mm +74 -0
- package/doc/backend-development.md +78 -0
- package/doc/bb-pod.md +89 -0
- package/doc/benchmarking.md +360 -0
- package/doc/bindings/edn-conversion.md +383 -0
- package/doc/cli.md +162 -0
- package/doc/cljdoc.edn +27 -0
- package/doc/cljs-support.md +133 -0
- package/doc/config.md +406 -0
- package/doc/contributing.md +114 -0
- package/doc/datalog-vs-sql.md +210 -0
- package/doc/datomic_differences.md +109 -0
- package/doc/development/pull-api-ns.md +186 -0
- package/doc/development/pull-frame-state-diagram.jpg +0 -0
- package/doc/distributed.md +566 -0
- package/doc/entity_spec.md +92 -0
- package/doc/gc.md +273 -0
- package/doc/java-api.md +808 -0
- package/doc/javascript-api.md +421 -0
- package/doc/libdatahike.md +86 -0
- package/doc/logging_and_error_handling.md +43 -0
- package/doc/norms.md +66 -0
- package/doc/schema-migration.md +85 -0
- package/doc/schema.md +287 -0
- package/doc/storage-backends.md +363 -0
- package/doc/store-id-refactoring.md +596 -0
- package/doc/time_variance.md +325 -0
- package/doc/unstructured.md +167 -0
- package/doc/versioning.md +261 -0
- package/examples/basic/README.md +19 -0
- package/examples/basic/deps.edn +6 -0
- package/examples/basic/docker-compose.yml +13 -0
- package/examples/basic/src/examples/core.clj +60 -0
- package/examples/basic/src/examples/schema.clj +155 -0
- package/examples/basic/src/examples/store.clj +60 -0
- package/examples/basic/src/examples/time_travel.clj +185 -0
- package/examples/java/.settings/org.eclipse.core.resources.prefs +3 -0
- package/examples/java/.settings/org.eclipse.jdt.apt.core.prefs +2 -0
- package/examples/java/.settings/org.eclipse.jdt.core.prefs +9 -0
- package/examples/java/.settings/org.eclipse.m2e.core.prefs +4 -0
- package/examples/java/README.md +162 -0
- package/examples/java/pom.xml +62 -0
- package/examples/java/src/main/java/examples/QuickStart.java +115 -0
- package/examples/java/src/main/java/examples/SchemaExample.java +148 -0
- package/examples/java/src/main/java/examples/TimeTravelExample.java +121 -0
- package/flake.lock +27 -0
- package/flake.nix +27 -0
- package/http-server/datahike/http/middleware.clj +75 -0
- package/http-server/datahike/http/server.clj +269 -0
- package/java/src/datahike/java/Database.java +274 -0
- package/java/src/datahike/java/Datahike.java +281 -0
- package/java/src/datahike/java/DatahikeGeneratedTest.java +349 -0
- package/java/src/datahike/java/DatahikeTest.java +370 -0
- package/java/src/datahike/java/EDN.java +170 -0
- package/java/src/datahike/java/IEntity.java +11 -0
- package/java/src/datahike/java/Keywords.java +161 -0
- package/java/src/datahike/java/SchemaFlexibility.java +52 -0
- package/java/src/datahike/java/Util.java +219 -0
- package/karma.conf.js +19 -0
- package/libdatahike/compile-cpp +7 -0
- package/libdatahike/src/datahike/impl/LibDatahikeBase.java +203 -0
- package/libdatahike/src/datahike/impl/libdatahike.clj +59 -0
- package/libdatahike/src/test_cpp.cpp +61 -0
- package/npm-package/PUBLISHING.md +140 -0
- package/npm-package/README.md +226 -0
- package/npm-package/package.template.json +34 -0
- package/npm-package/test-isomorphic.ts +281 -0
- package/npm-package/test.js +557 -0
- package/npm-package/typescript-test.ts +70 -0
- package/package.json +16 -0
- package/pydatahike/README.md +569 -0
- package/pydatahike/pyproject.toml +91 -0
- package/pydatahike/setup.py +42 -0
- package/pydatahike/src/datahike/__init__.py +134 -0
- package/pydatahike/src/datahike/_native.py +250 -0
- package/pydatahike/src/datahike/_version.py +2 -0
- package/pydatahike/src/datahike/database.py +722 -0
- package/pydatahike/src/datahike/edn.py +311 -0
- package/pydatahike/src/datahike/py.typed +0 -0
- package/pydatahike/tests/conftest.py +17 -0
- package/pydatahike/tests/test_basic.py +170 -0
- package/pydatahike/tests/test_database.py +51 -0
- package/pydatahike/tests/test_edn_conversion.py +299 -0
- package/pydatahike/tests/test_query.py +99 -0
- package/pydatahike/tests/test_schema.py +55 -0
- package/resources/clj-kondo.exports/io.replikativ/datahike/config.edn +5 -0
- package/resources/example_server.edn +4 -0
- package/shadow-cljs.edn +56 -0
- package/src/data_readers.clj +7 -0
- package/src/datahike/api/impl.cljc +176 -0
- package/src/datahike/api/specification.cljc +633 -0
- package/src/datahike/api/types.cljc +261 -0
- package/src/datahike/api.cljc +41 -0
- package/src/datahike/array.cljc +99 -0
- package/src/datahike/cli.clj +166 -0
- package/src/datahike/cljs.cljs +6 -0
- package/src/datahike/codegen/cli.clj +406 -0
- package/src/datahike/codegen/clj_kondo.clj +291 -0
- package/src/datahike/codegen/java.clj +403 -0
- package/src/datahike/codegen/naming.cljc +33 -0
- package/src/datahike/codegen/native.clj +559 -0
- package/src/datahike/codegen/pod.clj +488 -0
- package/src/datahike/codegen/python.clj +838 -0
- package/src/datahike/codegen/report.clj +55 -0
- package/src/datahike/codegen/typescript.clj +262 -0
- package/src/datahike/codegen/validation.clj +145 -0
- package/src/datahike/config.cljc +294 -0
- package/src/datahike/connections.cljc +16 -0
- package/src/datahike/connector.cljc +265 -0
- package/src/datahike/constants.cljc +142 -0
- package/src/datahike/core.cljc +297 -0
- package/src/datahike/datom.cljc +459 -0
- package/src/datahike/db/interface.cljc +119 -0
- package/src/datahike/db/search.cljc +305 -0
- package/src/datahike/db/transaction.cljc +937 -0
- package/src/datahike/db/utils.cljc +338 -0
- package/src/datahike/db.cljc +956 -0
- package/src/datahike/experimental/unstructured.cljc +126 -0
- package/src/datahike/experimental/versioning.cljc +172 -0
- package/src/datahike/externs.js +31 -0
- package/src/datahike/gc.cljc +69 -0
- package/src/datahike/http/client.clj +188 -0
- package/src/datahike/http/writer.clj +79 -0
- package/src/datahike/impl/entity.cljc +218 -0
- package/src/datahike/index/interface.cljc +93 -0
- package/src/datahike/index/persistent_set.cljc +469 -0
- package/src/datahike/index/utils.cljc +44 -0
- package/src/datahike/index.cljc +32 -0
- package/src/datahike/js/api.cljs +172 -0
- package/src/datahike/js/api_macros.clj +22 -0
- package/src/datahike/js.cljs +163 -0
- package/src/datahike/json.cljc +209 -0
- package/src/datahike/lru.cljc +146 -0
- package/src/datahike/migrate.clj +39 -0
- package/src/datahike/norm/norm.clj +245 -0
- package/src/datahike/online_gc.cljc +252 -0
- package/src/datahike/pod.clj +155 -0
- package/src/datahike/pull_api.cljc +325 -0
- package/src/datahike/query.cljc +1945 -0
- package/src/datahike/query_stats.cljc +88 -0
- package/src/datahike/readers.cljc +62 -0
- package/src/datahike/remote.cljc +218 -0
- package/src/datahike/schema.cljc +228 -0
- package/src/datahike/schema_cache.cljc +42 -0
- package/src/datahike/spec.cljc +101 -0
- package/src/datahike/store.cljc +80 -0
- package/src/datahike/tools.cljc +308 -0
- package/src/datahike/transit.cljc +80 -0
- package/src/datahike/writer.cljc +239 -0
- package/src/datahike/writing.cljc +362 -0
- package/src/deps.cljs +1 -0
- package/src-hitchhiker-tree/datahike/index/hitchhiker_tree/insert.cljc +76 -0
- package/src-hitchhiker-tree/datahike/index/hitchhiker_tree/upsert.cljc +128 -0
- package/src-hitchhiker-tree/datahike/index/hitchhiker_tree.cljc +213 -0
- package/test/datahike/backward_compatibility_test/src/backward_test.clj +37 -0
- package/test/datahike/integration_test/config_record_file_test.clj +14 -0
- package/test/datahike/integration_test/config_record_test.clj +14 -0
- package/test/datahike/integration_test/depr_config_uri_test.clj +15 -0
- package/test/datahike/integration_test/return_map_test.clj +62 -0
- package/test/datahike/integration_test.cljc +67 -0
- package/test/datahike/norm/norm_test.clj +124 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/001-a1-example.edn +5 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/002-a2-example.edn +5 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/003-tx-fn-test.edn +1 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/004-tx-data-and-tx-fn-test.edn +5 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/01-transact-basic-characters.edn +2 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/02 add occupation.edn +5 -0
- package/test/datahike/norm/resources/naming-and-sorting-test/checksums.edn +12 -0
- package/test/datahike/norm/resources/simple-test/001-a1-example.edn +5 -0
- package/test/datahike/norm/resources/simple-test/002-a2-example.edn +5 -0
- package/test/datahike/norm/resources/simple-test/checksums.edn +4 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/001-a1-example.edn +5 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/002-a2-example.edn +5 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/003-tx-fn-test.edn +1 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/checksums.edn +6 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/second/004-tx-data-and-tx-fn-test.edn +5 -0
- package/test/datahike/norm/resources/tx-data-and-tx-fn-test/second/checksums.edn +2 -0
- package/test/datahike/norm/resources/tx-fn-test/first/001-a1-example.edn +5 -0
- package/test/datahike/norm/resources/tx-fn-test/first/002-a2-example.edn +5 -0
- package/test/datahike/norm/resources/tx-fn-test/first/checksums.edn +4 -0
- package/test/datahike/norm/resources/tx-fn-test/second/003-tx-fn-test.edn +1 -0
- package/test/datahike/norm/resources/tx-fn-test/second/checksums.edn +2 -0
- package/test/datahike/test/api_test.cljc +895 -0
- package/test/datahike/test/array_test.cljc +40 -0
- package/test/datahike/test/attribute_refs/datoms_test.cljc +140 -0
- package/test/datahike/test/attribute_refs/db_test.cljc +42 -0
- package/test/datahike/test/attribute_refs/differences_test.cljc +515 -0
- package/test/datahike/test/attribute_refs/entity_test.cljc +89 -0
- package/test/datahike/test/attribute_refs/pull_api_test.cljc +320 -0
- package/test/datahike/test/attribute_refs/query_find_specs_test.cljc +59 -0
- package/test/datahike/test/attribute_refs/query_fns_test.cljc +130 -0
- package/test/datahike/test/attribute_refs/query_interop_test.cljc +47 -0
- package/test/datahike/test/attribute_refs/query_not_test.cljc +193 -0
- package/test/datahike/test/attribute_refs/query_or_test.cljc +137 -0
- package/test/datahike/test/attribute_refs/query_pull_test.cljc +156 -0
- package/test/datahike/test/attribute_refs/query_rules_test.cljc +176 -0
- package/test/datahike/test/attribute_refs/query_test.cljc +241 -0
- package/test/datahike/test/attribute_refs/temporal_search.cljc +22 -0
- package/test/datahike/test/attribute_refs/transact_test.cljc +220 -0
- package/test/datahike/test/attribute_refs/utils.cljc +128 -0
- package/test/datahike/test/cache_test.cljc +38 -0
- package/test/datahike/test/components_test.cljc +92 -0
- package/test/datahike/test/config_test.cljc +158 -0
- package/test/datahike/test/core_test.cljc +105 -0
- package/test/datahike/test/datom_test.cljc +44 -0
- package/test/datahike/test/db_test.cljc +54 -0
- package/test/datahike/test/entity_spec_test.cljc +159 -0
- package/test/datahike/test/entity_test.cljc +103 -0
- package/test/datahike/test/explode_test.cljc +143 -0
- package/test/datahike/test/filter_test.cljc +75 -0
- package/test/datahike/test/gc_test.cljc +159 -0
- package/test/datahike/test/http/server_test.clj +192 -0
- package/test/datahike/test/http/writer_test.clj +86 -0
- package/test/datahike/test/ident_test.cljc +32 -0
- package/test/datahike/test/index_test.cljc +345 -0
- package/test/datahike/test/insert.cljc +125 -0
- package/test/datahike/test/java_bindings_test.clj +6 -0
- package/test/datahike/test/listen_test.cljc +41 -0
- package/test/datahike/test/lookup_refs_test.cljc +266 -0
- package/test/datahike/test/lru_test.cljc +27 -0
- package/test/datahike/test/migrate_test.clj +297 -0
- package/test/datahike/test/model/core.cljc +376 -0
- package/test/datahike/test/model/invariant.cljc +142 -0
- package/test/datahike/test/model/rng.cljc +82 -0
- package/test/datahike/test/model_test.clj +217 -0
- package/test/datahike/test/nodejs_test.cljs +262 -0
- package/test/datahike/test/online_gc_test.cljc +475 -0
- package/test/datahike/test/pod_test.clj +369 -0
- package/test/datahike/test/pull_api_test.cljc +474 -0
- package/test/datahike/test/purge_test.cljc +144 -0
- package/test/datahike/test/query_aggregates_test.cljc +101 -0
- package/test/datahike/test/query_find_specs_test.cljc +52 -0
- package/test/datahike/test/query_fns_test.cljc +523 -0
- package/test/datahike/test/query_interop_test.cljc +47 -0
- package/test/datahike/test/query_not_test.cljc +189 -0
- package/test/datahike/test/query_or_test.cljc +158 -0
- package/test/datahike/test/query_pull_test.cljc +147 -0
- package/test/datahike/test/query_rules_test.cljc +248 -0
- package/test/datahike/test/query_stats_test.cljc +218 -0
- package/test/datahike/test/query_test.cljc +984 -0
- package/test/datahike/test/schema_test.cljc +424 -0
- package/test/datahike/test/specification_test.cljc +30 -0
- package/test/datahike/test/store_test.cljc +78 -0
- package/test/datahike/test/stress_test.cljc +57 -0
- package/test/datahike/test/time_variance_test.cljc +518 -0
- package/test/datahike/test/tools_test.clj +134 -0
- package/test/datahike/test/transact_test.cljc +518 -0
- package/test/datahike/test/tuples_test.cljc +564 -0
- package/test/datahike/test/unstructured_test.cljc +291 -0
- package/test/datahike/test/upsert_impl_test.cljc +205 -0
- package/test/datahike/test/upsert_test.cljc +363 -0
- package/test/datahike/test/utils.cljc +110 -0
- package/test/datahike/test/validation_test.cljc +48 -0
- package/test/datahike/test/versioning_test.cljc +56 -0
- package/test/datahike/test.cljc +66 -0
- package/tests.edn +24 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
# EDN Conversion Rules for Language Bindings
|
|
2
|
+
|
|
3
|
+
This document describes the universal rules for converting native data structures (Python dicts, JavaScript objects, Java Maps) to EDN (Extensible Data Notation) across all Datahike language bindings.
|
|
4
|
+
|
|
5
|
+
## Design Goal
|
|
6
|
+
|
|
7
|
+
Provide a **consistent, explicit, and predictable** mapping between native language data structures and EDN that works the same way across Python, JavaScript, and Java bindings.
|
|
8
|
+
|
|
9
|
+
## The Universal Rule
|
|
10
|
+
|
|
11
|
+
> **Keys are always keywordized. Values starting with `:` become keywords, everything else remains literal.**
|
|
12
|
+
|
|
13
|
+
### Key Conversion
|
|
14
|
+
|
|
15
|
+
All map/dict/object keys are automatically converted to EDN keywords:
|
|
16
|
+
|
|
17
|
+
| Input | EDN Output | Notes |
|
|
18
|
+
|-------|------------|-------|
|
|
19
|
+
| `"name"` | `:name` | Simple key |
|
|
20
|
+
| `"person/name"` | `:person/name` | Namespaced key |
|
|
21
|
+
| `":db/id"` | `:db/id` | Leading `:` is stripped (convenience) |
|
|
22
|
+
| `"keep-history?"` | `:keep-history?` | Question mark preserved |
|
|
23
|
+
|
|
24
|
+
### Value Conversion
|
|
25
|
+
|
|
26
|
+
Values are converted based on their type and content:
|
|
27
|
+
|
|
28
|
+
| Input Type | Input Value | EDN Output | Rule |
|
|
29
|
+
|------------|-------------|------------|------|
|
|
30
|
+
| String starting with `:` | `":active"` | `:active` | Keyword |
|
|
31
|
+
| String without `:` | `"Alice"` | `"Alice"` | String |
|
|
32
|
+
| String with `\:` prefix | `"\\:literal"` | `":literal"` | Escaped literal |
|
|
33
|
+
| Integer | `42` | `42` | Number |
|
|
34
|
+
| Float | `3.14` | `3.14` | Number |
|
|
35
|
+
| Boolean | `true` / `false` | `true` / `false` | Boolean |
|
|
36
|
+
| Null/None | `null` / `None` | `nil` | Nil |
|
|
37
|
+
| List/Array | `[1, 2, 3]` | `[1 2 3]` | Vector |
|
|
38
|
+
| Nested Map/Dict | `{"a": 1}` | `{:a 1}` | Recursive |
|
|
39
|
+
|
|
40
|
+
## Examples by Language
|
|
41
|
+
|
|
42
|
+
### Python
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
# Configuration
|
|
46
|
+
import uuid
|
|
47
|
+
|
|
48
|
+
config = {
|
|
49
|
+
"store": {
|
|
50
|
+
"backend": ":memory", # Keyword
|
|
51
|
+
"id": str(uuid.uuid4()) # String (memory backend requires UUID)
|
|
52
|
+
},
|
|
53
|
+
"schema-flexibility": ":read", # Keyword
|
|
54
|
+
"keep-history?": True # Boolean
|
|
55
|
+
}
|
|
56
|
+
# → {:store {:backend :memory :id "<uuid-string>"}
|
|
57
|
+
# :schema-flexibility :read
|
|
58
|
+
# :keep-history? true}
|
|
59
|
+
|
|
60
|
+
# Transaction data
|
|
61
|
+
data = [
|
|
62
|
+
{"name": "Alice", "status": ":active"},
|
|
63
|
+
{"name": "Bob", "status": ":inactive"}
|
|
64
|
+
]
|
|
65
|
+
# → [{:name "Alice" :status :active}
|
|
66
|
+
# {:name "Bob" :status :inactive}]
|
|
67
|
+
|
|
68
|
+
# Schema transaction
|
|
69
|
+
schema = [
|
|
70
|
+
{
|
|
71
|
+
"db/ident": ":person/name",
|
|
72
|
+
"db/valueType": ":db.type/string",
|
|
73
|
+
"db/cardinality": ":db.cardinality/one"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
# → [{:db/ident :person/name
|
|
77
|
+
# :db/valueType :db.type/string
|
|
78
|
+
# :db/cardinality :db.cardinality/one}]
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### JavaScript
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
// Configuration
|
|
85
|
+
const crypto = require('crypto');
|
|
86
|
+
const config = {
|
|
87
|
+
store: {
|
|
88
|
+
backend: ':memory', // Keyword
|
|
89
|
+
id: crypto.randomUUID() // String (memory backend requires UUID)
|
|
90
|
+
},
|
|
91
|
+
'schema-flexibility': ':read', // Keyword
|
|
92
|
+
'keep-history?': true // Boolean
|
|
93
|
+
};
|
|
94
|
+
// → {:store {:backend :memory :id "<uuid-string>"}
|
|
95
|
+
// :schema-flexibility :read
|
|
96
|
+
// :keep-history? true}
|
|
97
|
+
|
|
98
|
+
// Transaction data
|
|
99
|
+
const data = [
|
|
100
|
+
{name: 'Alice', status: ':active'},
|
|
101
|
+
{name: 'Bob', status: ':inactive'}
|
|
102
|
+
];
|
|
103
|
+
// → [{:name "Alice" :status :active}
|
|
104
|
+
// {:name "Bob" :status :inactive}]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Java
|
|
108
|
+
|
|
109
|
+
```java
|
|
110
|
+
// Configuration
|
|
111
|
+
import java.util.UUID;
|
|
112
|
+
|
|
113
|
+
Map<String, Object> config = Map.of(
|
|
114
|
+
"store", Map.of(
|
|
115
|
+
"backend", ":memory", // Keyword
|
|
116
|
+
"id", UUID.randomUUID().toString() // String (memory backend requires UUID)
|
|
117
|
+
),
|
|
118
|
+
"schema-flexibility", ":read" // Keyword
|
|
119
|
+
);
|
|
120
|
+
// → {:store {:backend :memory :id "<uuid-string>"}
|
|
121
|
+
// :schema-flexibility :read}
|
|
122
|
+
|
|
123
|
+
// Or use builders (convenience)
|
|
124
|
+
// Note: memory backend requires UUID identifier
|
|
125
|
+
Database db = Database.memory(UUID.randomUUID().toString())
|
|
126
|
+
.schemaFlexibility(SchemaFlexibility.READ)
|
|
127
|
+
.build();
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Edge Cases and Escape Hatches
|
|
131
|
+
|
|
132
|
+
### Literal Colon Strings
|
|
133
|
+
|
|
134
|
+
To include a string that starts with `:` without it becoming a keyword, use backslash escape:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
# Python
|
|
138
|
+
{"literal": "\\:starts-with-colon"}
|
|
139
|
+
# → {:literal ":starts-with-colon"}
|
|
140
|
+
|
|
141
|
+
# JavaScript
|
|
142
|
+
{literal: '\\:starts-with-colon'}
|
|
143
|
+
# → {:literal ":starts-with-colon"}
|
|
144
|
+
|
|
145
|
+
# Java
|
|
146
|
+
Map.of("literal", "\\:starts-with-colon")
|
|
147
|
+
// → {:literal ":starts-with-colon"}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Note:** In Python/Java string literals, you need to escape the backslash itself: `"\\:"` → literal `\:` → EDN `":value"`
|
|
151
|
+
|
|
152
|
+
### Helper Modules for Complex Types
|
|
153
|
+
|
|
154
|
+
All languages provide helper modules for explicit type control:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
# Python
|
|
158
|
+
from datahike import edn
|
|
159
|
+
|
|
160
|
+
{
|
|
161
|
+
"uuid": edn.uuid("550e8400-e29b-41d4-a716-446655440000"),
|
|
162
|
+
"inst": edn.inst("2024-01-01T00:00:00Z"),
|
|
163
|
+
"literal": edn.string(":force-string"),
|
|
164
|
+
"keyword": edn.keyword("name", "person") # :person/name
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
// JavaScript
|
|
170
|
+
import {edn} from 'datahike';
|
|
171
|
+
|
|
172
|
+
{
|
|
173
|
+
uuid: edn.uuid('550e8400-e29b-41d4-a716-446655440000'),
|
|
174
|
+
inst: edn.inst('2024-01-01T00:00:00Z'),
|
|
175
|
+
literal: edn.string(':force-string'),
|
|
176
|
+
keyword: edn.keyword('person', 'name') // :person/name
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```java
|
|
181
|
+
// Java
|
|
182
|
+
import static datahike.edn.EDN.*;
|
|
183
|
+
|
|
184
|
+
Map.of(
|
|
185
|
+
"uuid", uuid("550e8400-e29b-41d4-a716-446655440000"),
|
|
186
|
+
"inst", inst("2024-01-01T00:00:00Z"),
|
|
187
|
+
"literal", string(":force-string"),
|
|
188
|
+
"keyword", keyword("person", "name") // :person/name
|
|
189
|
+
)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### EDN String Escape Hatch
|
|
193
|
+
|
|
194
|
+
For truly complex cases (database functions, unusual syntax), all bindings accept raw EDN strings:
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
# Python
|
|
198
|
+
db.transact('''
|
|
199
|
+
[{:db/id #db/id[:db.part/user]
|
|
200
|
+
:db/fn (fn [db] (inc 1))}]
|
|
201
|
+
''', input_format='edn')
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
// JavaScript
|
|
206
|
+
await d.transact(conn, `
|
|
207
|
+
[{:db/id #db/id[:db.part/user]
|
|
208
|
+
:db/fn (fn [db] (inc 1))}]
|
|
209
|
+
`);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```java
|
|
213
|
+
// Java
|
|
214
|
+
Datahike.transact(conn,
|
|
215
|
+
"[{:db/id #db/id[:db.part/user] " +
|
|
216
|
+
" :db/fn (fn [db] (inc 1))}]");
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Custom Backend Support
|
|
220
|
+
|
|
221
|
+
Custom storage backends may require arbitrary configuration keys that are not known in advance. The map/dict/object-based API handles these naturally:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
# Python - custom S3 backend
|
|
225
|
+
db = Database({
|
|
226
|
+
"store": {
|
|
227
|
+
"backend": ":my-s3",
|
|
228
|
+
"bucket": "my-bucket",
|
|
229
|
+
"region": "us-west-2",
|
|
230
|
+
"encryption": {
|
|
231
|
+
"type": ":aes256",
|
|
232
|
+
"key-id": "secret-key"
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
// JavaScript - custom S3 backend
|
|
240
|
+
const db = new Database({
|
|
241
|
+
store: {
|
|
242
|
+
backend: ':my-s3',
|
|
243
|
+
bucket: 'my-bucket',
|
|
244
|
+
region: 'us-west-2',
|
|
245
|
+
encryption: {
|
|
246
|
+
type: ':aes256',
|
|
247
|
+
'key-id': 'secret-key'
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
```java
|
|
254
|
+
// Java - custom S3 backend (Map API)
|
|
255
|
+
Map<String, Object> config = Map.of(
|
|
256
|
+
"store", Map.of(
|
|
257
|
+
"backend", ":my-s3",
|
|
258
|
+
"bucket", "my-bucket",
|
|
259
|
+
"region", "us-west-2",
|
|
260
|
+
"encryption", Map.of(
|
|
261
|
+
"type", ":aes256",
|
|
262
|
+
"key-id", "secret-key"
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
);
|
|
266
|
+
Database db = new Database(config);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Common Keyword Constants
|
|
270
|
+
|
|
271
|
+
All language bindings provide pre-defined constants for frequently-used Datahike keywords:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
# Python
|
|
275
|
+
from datahike import kw
|
|
276
|
+
|
|
277
|
+
kw.DB_ID # :db/id
|
|
278
|
+
kw.DB_IDENT # :db/ident
|
|
279
|
+
kw.DB_VALUE_TYPE # :db/valueType
|
|
280
|
+
kw.DB_CARDINALITY # :db/cardinality
|
|
281
|
+
kw.STRING # :db.type/string
|
|
282
|
+
kw.LONG # :db.type/long
|
|
283
|
+
kw.REF # :db.type/ref
|
|
284
|
+
kw.ONE # :db.cardinality/one
|
|
285
|
+
kw.MANY # :db.cardinality/many
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
// JavaScript
|
|
290
|
+
import {kw} from 'datahike';
|
|
291
|
+
|
|
292
|
+
kw.DB_ID // :db/id
|
|
293
|
+
kw.DB_IDENT // :db/ident
|
|
294
|
+
// ... etc
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```java
|
|
298
|
+
// Java
|
|
299
|
+
import static datahike.edn.Keywords.*;
|
|
300
|
+
|
|
301
|
+
DB_ID // :db/id
|
|
302
|
+
DB_IDENT // :db/ident
|
|
303
|
+
// ... etc
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Design Rationale
|
|
307
|
+
|
|
308
|
+
### Why `:` prefix for keyword values?
|
|
309
|
+
|
|
310
|
+
**Explicit is better than implicit.** Previous versions used heuristics (e.g., `"backend": "mem"` → `:memory`), but this created ambiguity:
|
|
311
|
+
- Is `"person/name"` a keyword or a string with a slash?
|
|
312
|
+
- Is `"active"` a keyword or a string?
|
|
313
|
+
|
|
314
|
+
The `:` prefix makes the distinction unambiguous and works consistently across all contexts.
|
|
315
|
+
|
|
316
|
+
### Why not separate config rules from data rules?
|
|
317
|
+
|
|
318
|
+
**Consistency reduces cognitive load.** Having one universal rule means:
|
|
319
|
+
- No need to remember context-specific rules
|
|
320
|
+
- Easier to teach and document
|
|
321
|
+
- Predictable behavior everywhere
|
|
322
|
+
|
|
323
|
+
### Why backslash escaping?
|
|
324
|
+
|
|
325
|
+
**Familiar to programmers.** Backslash escaping is used in JSON, regex, shell scripts, and most programming languages. It's a well-understood convention.
|
|
326
|
+
|
|
327
|
+
### Why allow EDN string fallback?
|
|
328
|
+
|
|
329
|
+
**Pragmatic escape hatch.** While the conversion rules handle 99% of cases, edge cases exist (database functions, unusual Clojure syntax). Allowing raw EDN strings ensures users are never blocked.
|
|
330
|
+
|
|
331
|
+
## Migration from Previous Versions
|
|
332
|
+
|
|
333
|
+
### JavaScript (Breaking Change)
|
|
334
|
+
|
|
335
|
+
**Old Syntax (v0.6 and earlier):**
|
|
336
|
+
```javascript
|
|
337
|
+
const crypto = require('crypto');
|
|
338
|
+
const config = {
|
|
339
|
+
store: {
|
|
340
|
+
backend: 'memory', // String → keyword (implicit)
|
|
341
|
+
id: crypto.randomUUID() // Memory backend requires UUID
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**New Syntax (v0.7+):**
|
|
347
|
+
```javascript
|
|
348
|
+
const crypto = require('crypto');
|
|
349
|
+
const config = {
|
|
350
|
+
store: {
|
|
351
|
+
backend: ':memory', // Explicit : required
|
|
352
|
+
id: crypto.randomUUID() // Memory backend requires UUID
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
**Migration:** Add `:` prefix to all values that should be keywords. Note that memory backend requires UUID identifiers.
|
|
358
|
+
|
|
359
|
+
### Java (Enhancement)
|
|
360
|
+
|
|
361
|
+
**Syntax:**
|
|
362
|
+
```java
|
|
363
|
+
import java.util.UUID;
|
|
364
|
+
|
|
365
|
+
Map<String, Object> config = Map.of(
|
|
366
|
+
"store", Map.of(
|
|
367
|
+
"backend", ":memory", // Explicit : for keyword
|
|
368
|
+
"id", UUID.randomUUID().toString() // Memory backend requires UUID
|
|
369
|
+
)
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
// Or use builders (memory backend requires UUID)
|
|
373
|
+
Database.memory(UUID.randomUUID().toString()).build();
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Migration:** Add `:` prefix to all values that should be keywords for consistency with other languages.
|
|
377
|
+
|
|
378
|
+
## See Also
|
|
379
|
+
|
|
380
|
+
- [Python Binding Documentation](python.md)
|
|
381
|
+
- [Java Binding Documentation](java.md)
|
|
382
|
+
- [JavaScript Binding Documentation](javascript.md)
|
|
383
|
+
- [Custom Backends Guide](custom-backends.md)
|
package/doc/cli.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Command line interface
|
|
2
|
+
|
|
3
|
+
**Status: Beta** - The CLI is functional and tested, but the command structure may change as we refine the interface.
|
|
4
|
+
|
|
5
|
+
We provide the `dthk` native executable to access Datahike databases from
|
|
6
|
+
the command line.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
1. Download the precompiled binary for your platform from the [Datahike releases page](https://github.com/replikativ/datahike/releases)
|
|
11
|
+
2. Unzip the archive
|
|
12
|
+
3. Add the `dthk` executable to your PATH
|
|
13
|
+
|
|
14
|
+
Supported platforms:
|
|
15
|
+
- Linux (amd64, aarch64)
|
|
16
|
+
- macOS (aarch64/Apple Silicon)
|
|
17
|
+
|
|
18
|
+
For other platforms, build from source using GraalVM native-image (see project documentation).
|
|
19
|
+
|
|
20
|
+
## Commands
|
|
21
|
+
|
|
22
|
+
The CLI provides flat commands that mirror the Clojure API functions. To see all available commands, run:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
$ dthk --help
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Common commands by category:
|
|
29
|
+
- **Database Operations**: `create-database`, `delete-database`, `database-exists`
|
|
30
|
+
- **Transaction Operations**: `transact`, `load-entities`, `db-with`, `with`
|
|
31
|
+
- **Query Operations**: `query`, `pull`, `pull-many`, `entity`, `datoms`, `seek-datoms`, `index-range`, `is-filtered`, `query-stats`
|
|
32
|
+
- **Schema Operations**: `schema`, `reverse-schema`
|
|
33
|
+
- **Diagnostics**: `metrics`
|
|
34
|
+
- **Maintenance**: `gc-storage`
|
|
35
|
+
|
|
36
|
+
Prefix syntax for database access:
|
|
37
|
+
- `db:config.edn` - Dereferences a connection to get the current database value
|
|
38
|
+
- `conn:config.edn` - Creates a connection for transacting
|
|
39
|
+
- `edn:file.edn` - Reads EDN data from a file
|
|
40
|
+
- `json:file.json` - Reads JSON data from a file
|
|
41
|
+
- `asof:timestamp:config.edn` - Creates a database snapshot as-of timestamp
|
|
42
|
+
- `since:timestamp:config.edn` - Creates a database view since timestamp
|
|
43
|
+
- `history:config.edn` - Returns the history database
|
|
44
|
+
|
|
45
|
+
## Example usage
|
|
46
|
+
|
|
47
|
+
To access a database you need to provide the usual configuration for Datahike.
|
|
48
|
+
Put this into a file `myconfig.edn`.
|
|
49
|
+
|
|
50
|
+
```clojure
|
|
51
|
+
{:store {:backend :file
|
|
52
|
+
:path "/home/USERNAME/dh-shared-db"
|
|
53
|
+
:id #uuid "550e8400-e29b-41d4-a716-446655440000"
|
|
54
|
+
:config {:in-place? true}}
|
|
55
|
+
:keep-history? true
|
|
56
|
+
:schema-flexibility :read}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Now you can invoke some of our core API functions on the database. Let us add a
|
|
60
|
+
fact to the database (be careful to use single ' if you do not want your shell
|
|
61
|
+
to substitute parts of your Datalog ;) ):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
$ dthk transact conn:myconfig.edn '[[:db/add -1 :name "Linus"]]'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
And retrieve it:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
$ dthk query '[:find ?n . :where [?e :name ?n]]' db:myconfig.edn
|
|
71
|
+
"Linus" # prints the name
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Note that the `conn:<file>` argument to `transact` comes before the transaction
|
|
75
|
+
value(s), whereas the `db:<file>` argument to `q` comes after the query
|
|
76
|
+
value, mirroring the Clojure API. As an added benefit, this also allows passing
|
|
77
|
+
multiple db configuration files prefixed with `db:` for joining over arbitrary
|
|
78
|
+
many databases or data files with "edn:" or "json:". Everything non-prefixed is
|
|
79
|
+
read in as `edn` and passed to the query engine as well:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
$ dthk query '[:find ?e . :in $ ?name :where [?e :name ?name]]' db:myconfig.edn '"Linus"'
|
|
83
|
+
123
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
When passing strings as EDN, make sure to enclose double quotes as part of the
|
|
87
|
+
command-line arg value. Otherwise it will be parsed as a symbol.
|
|
88
|
+
|
|
89
|
+
Provided the filestore is configured with `{:in-place? true}` you can even write
|
|
90
|
+
to the same database without a dedicated daemon from different shells:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# In the first shell
|
|
94
|
+
$ dthk transact conn:myconfig.edn '[[:db/add -1 :name "Alice"]]'
|
|
95
|
+
|
|
96
|
+
# In a second shell simultaneously
|
|
97
|
+
$ dthk transact conn:myconfig.edn '[[:db/add -2 :name "Bob"]]'
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
To check that everything has been added and no write operations have overwritten
|
|
101
|
+
each other:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
$ dthk query '[:find (count ?e) . :in $ :where [?e :name ?n]]' db:myconfig.edn
|
|
105
|
+
2 # check :)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
# Memory model
|
|
109
|
+
|
|
110
|
+
The persistent semantics of Datahike work more like `git` and less like similar
|
|
111
|
+
mutable databases such as SQLite or Datalevin. In particular you can always read
|
|
112
|
+
and retain snapshots (copies) of the database for free, no matter what else is
|
|
113
|
+
happening in the system. The current version is tested with memory and file
|
|
114
|
+
storage, but hopefully many other backends will also work with the
|
|
115
|
+
`native-image`.
|
|
116
|
+
|
|
117
|
+
In principle this shared memory access should even work while having a JVM
|
|
118
|
+
server, e.g. datahike-server, serving the same database. Note that all reads can
|
|
119
|
+
happen in parallel, only the writers experience congestion around exclusive file
|
|
120
|
+
locks here. This access pattern does not provide highest throughput, but is
|
|
121
|
+
extremely flexible and easy to start with.
|
|
122
|
+
|
|
123
|
+
## Forking and pulling
|
|
124
|
+
|
|
125
|
+
Forking is easy, it is enough to copy the folder of the store (even if the
|
|
126
|
+
database is currently being written to). The only thing you need to take care of
|
|
127
|
+
is to copy the DB root first and place it into the target directory last. The root
|
|
128
|
+
file is a konserve internal storage file with a UUID name like `0594e3b6-9635-5c99-8142-412accf3023b.ksv`
|
|
129
|
+
(the actual UUID will match your database's `:id` configuration). Then you can use e.g.
|
|
130
|
+
`rsync` (or `git`) to copy all other (immutable) files into your new folder. In
|
|
131
|
+
the end you copy the root file in there as well, making sure that all files it
|
|
132
|
+
is referencing are reachable. Note that this will ensure that you only copy new
|
|
133
|
+
data each time.
|
|
134
|
+
|
|
135
|
+
## Merging
|
|
136
|
+
|
|
137
|
+
Now here comes the cool part. You do not need anything more for merging than
|
|
138
|
+
Datalog itself. You can use a query like this to extract all new facts that are
|
|
139
|
+
in `db1` but not in `db2` like this:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
dthk query '[:find ?e ?a ?v ?t :in $ $2 :where [$ ?e ?a ?v ?t] (not [$2 ?e ?a ?v ?t])]' db:config1.edn db:config2.edn
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Since we cannot update transaction metadata, we should filter out
|
|
146
|
+
`:db/txInstant`s. We can also use a trick to add `:db/add` to each element in
|
|
147
|
+
the results, yielding valid transactions that we can then feed into `db2`.
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
dthk query '[:find ?db-add ?e ?a ?v ?t :in $ $2 ?db-add :where [$ ?e ?a ?v ?t] [(not= :db/txInstant ?a)] (not [$2 ?e ?a ?v ?t])]' db:config1.edn db:config2.edn ":db/add" | dthk transact db:config2.edn
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Note that this very simple strategy assumes that the entity ids that have been
|
|
155
|
+
added to `db1` do not overlap with potentially new ones added to `db2`. You can
|
|
156
|
+
encode conflict resolution strategies and id mappings with Datalog as well and
|
|
157
|
+
we are exploring several such strategies at the moment. This strategy is fairly
|
|
158
|
+
universal, as [CRDTs can be expressed in pure
|
|
159
|
+
Datalog](https://speakerdeck.com/ept/data-structures-as-queries-expressing-crdts-using-datalog).
|
|
160
|
+
While it is not the most efficient way to merge, we plan to provide fast paths
|
|
161
|
+
for common patterns in Datalog. Feel free to contact us if you are interested in
|
|
162
|
+
complex merging strategies or have related cool ideas.
|
package/doc/cljdoc.edn
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{:cljdoc.doc/tree [["Readme" {:file "README.md"}]
|
|
2
|
+
["Getting Started" {}
|
|
3
|
+
["Why Datalog?" {:file "doc/datalog-vs-sql.md"}]
|
|
4
|
+
["Configuration" {:file "doc/config.md"}]
|
|
5
|
+
["Storage Backends" {:file "doc/storage-backends.md"}]
|
|
6
|
+
["Schema" {:file "doc/schema.md"}]]
|
|
7
|
+
["Core Features" {}
|
|
8
|
+
["Time Variance" {:file "doc/time_variance.md"}]
|
|
9
|
+
["Versioning (Beta)" {:file "doc/versioning.md"}]
|
|
10
|
+
["Distributed Architecture" {:file "doc/distributed.md"}]
|
|
11
|
+
["Garbage Collection" {:file "doc/gc.md"}]
|
|
12
|
+
["Norms (Database Migrations)" {:file "doc/norms.md"}]]
|
|
13
|
+
["Platform Support" {}
|
|
14
|
+
["ClojureScript Support" {:file "doc/cljs-support.md"}]
|
|
15
|
+
["JavaScript API" {:file "doc/javascript-api.md"}]
|
|
16
|
+
["Babashka Pod" {:file "doc/bb-pod.md"}]
|
|
17
|
+
["Command Line Interface" {:file "doc/cli.md"}]
|
|
18
|
+
["libdatahike (C/C++)" {:file "doc/libdatahike.md"}]]
|
|
19
|
+
["Advanced" {}
|
|
20
|
+
["Entity Specs" {:file "doc/entity_spec.md"}]
|
|
21
|
+
["Logging and Error Handling" {:file "doc/logging_and_error_handling.md"}]
|
|
22
|
+
["Unstructured Input (Experimental)" {:file "doc/unstructured.md"}]
|
|
23
|
+
["Backend Development" {:file "doc/backend-development.md"}]
|
|
24
|
+
["Benchmarking" {:file "doc/benchmarking.md"}]]
|
|
25
|
+
["Reference" {}
|
|
26
|
+
["Differences to Datomic" {:file "doc/datomic_differences.md"}]
|
|
27
|
+
["Contributing" {:file "doc/contributing.md"}]]]}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# ClojureScript Support
|
|
2
|
+
|
|
3
|
+
**This feature is in beta. Please try it out and provide feedback.**
|
|
4
|
+
|
|
5
|
+
Datahike runs in ClojureScript on both Node.js and browser environments. The
|
|
6
|
+
core query engine, pull API, and entity API work identically to Clojure. The
|
|
7
|
+
main differences are in storage backends and async operation.
|
|
8
|
+
|
|
9
|
+
## Async Storage Model
|
|
10
|
+
|
|
11
|
+
ClojureScript environments often require async I/O (IndexedDB, network). To
|
|
12
|
+
support this, Datahike's persistent-sorted-set indices use *partial
|
|
13
|
+
continuation-passing style* (partial-cps): storage operations return channels
|
|
14
|
+
instead of values, while in-memory operations remain synchronous.
|
|
15
|
+
|
|
16
|
+
This is transparent when using `datahike.api` - async backends return channels
|
|
17
|
+
from `create-database`, `connect`, `transact!`, etc.
|
|
18
|
+
|
|
19
|
+
## Node.js
|
|
20
|
+
|
|
21
|
+
Node.js supports the file backend via
|
|
22
|
+
[konserve-node-filestore](https://github.com/replikativ/konserve):
|
|
23
|
+
|
|
24
|
+
```clojure
|
|
25
|
+
(ns my-app.core
|
|
26
|
+
(:require [datahike.api :as d]
|
|
27
|
+
[datahike.nodejs] ;; Registers :file backend
|
|
28
|
+
[cljs.core.async :refer [<!] :refer-macros [go]]))
|
|
29
|
+
|
|
30
|
+
(go
|
|
31
|
+
(let [config {:store {:backend :file
|
|
32
|
+
:path "./my-database"
|
|
33
|
+
:id #uuid "550e8400-e29b-41d4-a716-446655440000"}
|
|
34
|
+
:schema-flexibility :write}]
|
|
35
|
+
(<! (d/create-database config))
|
|
36
|
+
(let [conn (<! (d/connect config))]
|
|
37
|
+
(<! (d/transact! conn [{:name "Alice"}]))
|
|
38
|
+
(println (d/q '[:find ?n :where [?e :name ?n]] (d/db conn))))))
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Browser
|
|
42
|
+
|
|
43
|
+
Browsers cannot use file storage directly. Datahike provides two browser-compatible backends:
|
|
44
|
+
|
|
45
|
+
### Memory backend
|
|
46
|
+
|
|
47
|
+
Fast but not persistent across page reloads:
|
|
48
|
+
|
|
49
|
+
```clojure
|
|
50
|
+
{:store {:backend :memory :id #uuid "550e8400-e29b-41d4-a716-446655440000"}}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### IndexedDB backend
|
|
54
|
+
|
|
55
|
+
Persistent browser storage via
|
|
56
|
+
[konserve-indexeddb](https://github.com/replikativ/konserve):
|
|
57
|
+
|
|
58
|
+
```clojure
|
|
59
|
+
{:store {:backend :indexeddb :id #uuid "550e8400-e29b-41d4-a716-446655440000"}}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
IndexedDB is async, so all operations return channels.
|
|
63
|
+
|
|
64
|
+
### TieredStore
|
|
65
|
+
|
|
66
|
+
For optimal performance, use TieredStore which combines a fast memory frontend
|
|
67
|
+
with a persistent IndexedDB backend:
|
|
68
|
+
|
|
69
|
+
```clojure
|
|
70
|
+
{:store {:backend :tiered
|
|
71
|
+
:id #uuid "550e8400-e29b-41d4-a716-446655440000"
|
|
72
|
+
:frontend-config {:backend :memory
|
|
73
|
+
:id #uuid "550e8400-e29b-41d4-a716-446655440000"}
|
|
74
|
+
:backend-config {:backend :indexeddb
|
|
75
|
+
:id #uuid "550e8400-e29b-41d4-a716-446655440000"}}}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Writes go to both stores (write-through). Reads come from memory. On
|
|
79
|
+
reconnection, TieredStore syncs cached data from IndexedDB into memory before
|
|
80
|
+
the sync handshake, so only keys newer than cached timestamps are transferred.
|
|
81
|
+
|
|
82
|
+
## Distributed Setup with KabelWriter
|
|
83
|
+
|
|
84
|
+
For browser applications that need a persistent backend, use the KabelWriter to
|
|
85
|
+
connect to a JVM server. The server owns the database (using file storage) and
|
|
86
|
+
streams updates to browser clients via WebSockets. See [Streaming writer
|
|
87
|
+
(Kabel)](distributed.md#streaming-writer-kabel) for setup instructions.
|
|
88
|
+
|
|
89
|
+
This architecture means:
|
|
90
|
+
- Transactions are sent to the server via RPC
|
|
91
|
+
- The server writes to its file store
|
|
92
|
+
- Updates are replicated to the client's TieredStore via konserve-sync
|
|
93
|
+
- Queries run locally against the synchronized in-memory store
|
|
94
|
+
|
|
95
|
+
## JavaScript API
|
|
96
|
+
|
|
97
|
+
For JavaScript/TypeScript applications, Datahike provides a Promise-based API.
|
|
98
|
+
See [JavaScript API](javascript-api.md) for documentation.
|
|
99
|
+
|
|
100
|
+
**Installation:**
|
|
101
|
+
```bash
|
|
102
|
+
npm install datahike@next
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Example:**
|
|
106
|
+
```javascript
|
|
107
|
+
const d = require('datahike');
|
|
108
|
+
const crypto = require('crypto');
|
|
109
|
+
|
|
110
|
+
const config = {
|
|
111
|
+
store: {
|
|
112
|
+
backend: ':memory',
|
|
113
|
+
id: crypto.randomUUID()
|
|
114
|
+
},
|
|
115
|
+
'schema-flexibility': ':read' // Allow schemaless data (use kebab-case)
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
await d.createDatabase(config);
|
|
119
|
+
const conn = await d.connect(config);
|
|
120
|
+
await d.transact(conn, [{ name: 'Alice' }]);
|
|
121
|
+
const db = await d.db(conn); // db() is async for async backends
|
|
122
|
+
const results = await d.q('[:find ?n :where [?e :name ?n]]', db);
|
|
123
|
+
console.log(results);
|
|
124
|
+
// => [['Alice']]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Limitations
|
|
128
|
+
|
|
129
|
+
- **History queries**: Work but may be slower due to async index traversal
|
|
130
|
+
- **Large datasets**: Browser memory constraints apply; consider server-side
|
|
131
|
+
queries for large result sets
|
|
132
|
+
- **Concurrent tabs**: Each tab has its own connection; use KabelWriter for
|
|
133
|
+
cross-tab consistency
|