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.
Files changed (324) hide show
  1. package/.circleci/config.yml +405 -0
  2. package/.circleci/scripts/gen_ci.clj +194 -0
  3. package/.cirrus.yml +60 -0
  4. package/.clj-kondo/babashka/sci/config.edn +1 -0
  5. package/.clj-kondo/babashka/sci/sci/core.clj +9 -0
  6. package/.clj-kondo/config.edn +95 -0
  7. package/.dir-locals.el +2 -0
  8. package/.github/FUNDING.yml +3 -0
  9. package/.github/ISSUE_TEMPLATE/1-bug-report.yml +68 -0
  10. package/.github/ISSUE_TEMPLATE/2-feature-request.yml +28 -0
  11. package/.github/ISSUE_TEMPLATE/config.yml +6 -0
  12. package/.github/pull_request_template.md +24 -0
  13. package/.github/workflows/native-image.yml +84 -0
  14. package/LICENSE +203 -0
  15. package/README.md +273 -0
  16. package/bb/deps.edn +9 -0
  17. package/bb/resources/github-fingerprints +3 -0
  18. package/bb/resources/native-image-tests/run-bb-pod-tests.clj +162 -0
  19. package/bb/resources/native-image-tests/run-libdatahike-tests +12 -0
  20. package/bb/resources/native-image-tests/run-native-image-tests +74 -0
  21. package/bb/resources/native-image-tests/run-python-tests +22 -0
  22. package/bb/resources/native-image-tests/testconfig.attr-refs.edn +6 -0
  23. package/bb/resources/native-image-tests/testconfig.edn +5 -0
  24. package/bb/resources/template/.settings/org.eclipse.jdt.apt.core.prefs +2 -0
  25. package/bb/resources/template/.settings/org.eclipse.jdt.core.prefs +9 -0
  26. package/bb/resources/template/.settings/org.eclipse.m2e.core.prefs +4 -0
  27. package/bb/resources/template/pom.xml +22 -0
  28. package/bb/src/tools/build.clj +132 -0
  29. package/bb/src/tools/clj_kondo.clj +32 -0
  30. package/bb/src/tools/deploy.clj +26 -0
  31. package/bb/src/tools/examples.clj +19 -0
  32. package/bb/src/tools/npm.clj +100 -0
  33. package/bb/src/tools/python.clj +14 -0
  34. package/bb/src/tools/release.clj +94 -0
  35. package/bb/src/tools/test.clj +148 -0
  36. package/bb/src/tools/version.clj +47 -0
  37. package/bb.edn +269 -0
  38. package/benchmark/src/benchmark/cli.clj +195 -0
  39. package/benchmark/src/benchmark/compare.clj +157 -0
  40. package/benchmark/src/benchmark/config.clj +316 -0
  41. package/benchmark/src/benchmark/measure.clj +187 -0
  42. package/benchmark/src/benchmark/store.clj +190 -0
  43. package/benchmark/test/benchmark/measure_test.clj +156 -0
  44. package/build.clj +30 -0
  45. package/config.edn +49 -0
  46. package/deps.edn +138 -0
  47. package/dev/sandbox.clj +82 -0
  48. package/dev/sandbox.cljs +127 -0
  49. package/dev/sandbox_benchmarks.clj +27 -0
  50. package/dev/sandbox_client.clj +87 -0
  51. package/dev/sandbox_transact_bench.clj +109 -0
  52. package/dev/user.clj +79 -0
  53. package/doc/README.md +96 -0
  54. package/doc/adl/README.md +6 -0
  55. package/doc/adl/adr-000-adr.org +28 -0
  56. package/doc/adl/adr-001-attribute-references.org +15 -0
  57. package/doc/adl/adr-002-build-tooling.org +54 -0
  58. package/doc/adl/adr-003-db-meta-data.md +52 -0
  59. package/doc/adl/adr-004-github-flow.md +40 -0
  60. package/doc/adl/adr-XYZ-template.md +30 -0
  61. package/doc/adl/index.org +3 -0
  62. package/doc/assets/datahike-logo.svg +3 -0
  63. package/doc/assets/datahiking-invoice.org +85 -0
  64. package/doc/assets/hhtree2.png +0 -0
  65. package/doc/assets/network_topology.svg +624 -0
  66. package/doc/assets/perf.png +0 -0
  67. package/doc/assets/schema_mindmap.mm +132 -0
  68. package/doc/assets/schema_mindmap.svg +970 -0
  69. package/doc/assets/temporal_index.mm +74 -0
  70. package/doc/backend-development.md +78 -0
  71. package/doc/bb-pod.md +89 -0
  72. package/doc/benchmarking.md +360 -0
  73. package/doc/bindings/edn-conversion.md +383 -0
  74. package/doc/cli.md +162 -0
  75. package/doc/cljdoc.edn +27 -0
  76. package/doc/cljs-support.md +133 -0
  77. package/doc/config.md +406 -0
  78. package/doc/contributing.md +114 -0
  79. package/doc/datalog-vs-sql.md +210 -0
  80. package/doc/datomic_differences.md +109 -0
  81. package/doc/development/pull-api-ns.md +186 -0
  82. package/doc/development/pull-frame-state-diagram.jpg +0 -0
  83. package/doc/distributed.md +566 -0
  84. package/doc/entity_spec.md +92 -0
  85. package/doc/gc.md +273 -0
  86. package/doc/java-api.md +808 -0
  87. package/doc/javascript-api.md +421 -0
  88. package/doc/libdatahike.md +86 -0
  89. package/doc/logging_and_error_handling.md +43 -0
  90. package/doc/norms.md +66 -0
  91. package/doc/schema-migration.md +85 -0
  92. package/doc/schema.md +287 -0
  93. package/doc/storage-backends.md +363 -0
  94. package/doc/store-id-refactoring.md +596 -0
  95. package/doc/time_variance.md +325 -0
  96. package/doc/unstructured.md +167 -0
  97. package/doc/versioning.md +261 -0
  98. package/examples/basic/README.md +19 -0
  99. package/examples/basic/deps.edn +6 -0
  100. package/examples/basic/docker-compose.yml +13 -0
  101. package/examples/basic/src/examples/core.clj +60 -0
  102. package/examples/basic/src/examples/schema.clj +155 -0
  103. package/examples/basic/src/examples/store.clj +60 -0
  104. package/examples/basic/src/examples/time_travel.clj +185 -0
  105. package/examples/java/.settings/org.eclipse.core.resources.prefs +3 -0
  106. package/examples/java/.settings/org.eclipse.jdt.apt.core.prefs +2 -0
  107. package/examples/java/.settings/org.eclipse.jdt.core.prefs +9 -0
  108. package/examples/java/.settings/org.eclipse.m2e.core.prefs +4 -0
  109. package/examples/java/README.md +162 -0
  110. package/examples/java/pom.xml +62 -0
  111. package/examples/java/src/main/java/examples/QuickStart.java +115 -0
  112. package/examples/java/src/main/java/examples/SchemaExample.java +148 -0
  113. package/examples/java/src/main/java/examples/TimeTravelExample.java +121 -0
  114. package/flake.lock +27 -0
  115. package/flake.nix +27 -0
  116. package/http-server/datahike/http/middleware.clj +75 -0
  117. package/http-server/datahike/http/server.clj +269 -0
  118. package/java/src/datahike/java/Database.java +274 -0
  119. package/java/src/datahike/java/Datahike.java +281 -0
  120. package/java/src/datahike/java/DatahikeGeneratedTest.java +349 -0
  121. package/java/src/datahike/java/DatahikeTest.java +370 -0
  122. package/java/src/datahike/java/EDN.java +170 -0
  123. package/java/src/datahike/java/IEntity.java +11 -0
  124. package/java/src/datahike/java/Keywords.java +161 -0
  125. package/java/src/datahike/java/SchemaFlexibility.java +52 -0
  126. package/java/src/datahike/java/Util.java +219 -0
  127. package/karma.conf.js +19 -0
  128. package/libdatahike/compile-cpp +7 -0
  129. package/libdatahike/src/datahike/impl/LibDatahikeBase.java +203 -0
  130. package/libdatahike/src/datahike/impl/libdatahike.clj +59 -0
  131. package/libdatahike/src/test_cpp.cpp +61 -0
  132. package/npm-package/PUBLISHING.md +140 -0
  133. package/npm-package/README.md +226 -0
  134. package/npm-package/package.template.json +34 -0
  135. package/npm-package/test-isomorphic.ts +281 -0
  136. package/npm-package/test.js +557 -0
  137. package/npm-package/typescript-test.ts +70 -0
  138. package/package.json +16 -0
  139. package/pydatahike/README.md +569 -0
  140. package/pydatahike/pyproject.toml +91 -0
  141. package/pydatahike/setup.py +42 -0
  142. package/pydatahike/src/datahike/__init__.py +134 -0
  143. package/pydatahike/src/datahike/_native.py +250 -0
  144. package/pydatahike/src/datahike/_version.py +2 -0
  145. package/pydatahike/src/datahike/database.py +722 -0
  146. package/pydatahike/src/datahike/edn.py +311 -0
  147. package/pydatahike/src/datahike/py.typed +0 -0
  148. package/pydatahike/tests/conftest.py +17 -0
  149. package/pydatahike/tests/test_basic.py +170 -0
  150. package/pydatahike/tests/test_database.py +51 -0
  151. package/pydatahike/tests/test_edn_conversion.py +299 -0
  152. package/pydatahike/tests/test_query.py +99 -0
  153. package/pydatahike/tests/test_schema.py +55 -0
  154. package/resources/clj-kondo.exports/io.replikativ/datahike/config.edn +5 -0
  155. package/resources/example_server.edn +4 -0
  156. package/shadow-cljs.edn +56 -0
  157. package/src/data_readers.clj +7 -0
  158. package/src/datahike/api/impl.cljc +176 -0
  159. package/src/datahike/api/specification.cljc +633 -0
  160. package/src/datahike/api/types.cljc +261 -0
  161. package/src/datahike/api.cljc +41 -0
  162. package/src/datahike/array.cljc +99 -0
  163. package/src/datahike/cli.clj +166 -0
  164. package/src/datahike/cljs.cljs +6 -0
  165. package/src/datahike/codegen/cli.clj +406 -0
  166. package/src/datahike/codegen/clj_kondo.clj +291 -0
  167. package/src/datahike/codegen/java.clj +403 -0
  168. package/src/datahike/codegen/naming.cljc +33 -0
  169. package/src/datahike/codegen/native.clj +559 -0
  170. package/src/datahike/codegen/pod.clj +488 -0
  171. package/src/datahike/codegen/python.clj +838 -0
  172. package/src/datahike/codegen/report.clj +55 -0
  173. package/src/datahike/codegen/typescript.clj +262 -0
  174. package/src/datahike/codegen/validation.clj +145 -0
  175. package/src/datahike/config.cljc +294 -0
  176. package/src/datahike/connections.cljc +16 -0
  177. package/src/datahike/connector.cljc +265 -0
  178. package/src/datahike/constants.cljc +142 -0
  179. package/src/datahike/core.cljc +297 -0
  180. package/src/datahike/datom.cljc +459 -0
  181. package/src/datahike/db/interface.cljc +119 -0
  182. package/src/datahike/db/search.cljc +305 -0
  183. package/src/datahike/db/transaction.cljc +937 -0
  184. package/src/datahike/db/utils.cljc +338 -0
  185. package/src/datahike/db.cljc +956 -0
  186. package/src/datahike/experimental/unstructured.cljc +126 -0
  187. package/src/datahike/experimental/versioning.cljc +172 -0
  188. package/src/datahike/externs.js +31 -0
  189. package/src/datahike/gc.cljc +69 -0
  190. package/src/datahike/http/client.clj +188 -0
  191. package/src/datahike/http/writer.clj +79 -0
  192. package/src/datahike/impl/entity.cljc +218 -0
  193. package/src/datahike/index/interface.cljc +93 -0
  194. package/src/datahike/index/persistent_set.cljc +469 -0
  195. package/src/datahike/index/utils.cljc +44 -0
  196. package/src/datahike/index.cljc +32 -0
  197. package/src/datahike/js/api.cljs +172 -0
  198. package/src/datahike/js/api_macros.clj +22 -0
  199. package/src/datahike/js.cljs +163 -0
  200. package/src/datahike/json.cljc +209 -0
  201. package/src/datahike/lru.cljc +146 -0
  202. package/src/datahike/migrate.clj +39 -0
  203. package/src/datahike/norm/norm.clj +245 -0
  204. package/src/datahike/online_gc.cljc +252 -0
  205. package/src/datahike/pod.clj +155 -0
  206. package/src/datahike/pull_api.cljc +325 -0
  207. package/src/datahike/query.cljc +1945 -0
  208. package/src/datahike/query_stats.cljc +88 -0
  209. package/src/datahike/readers.cljc +62 -0
  210. package/src/datahike/remote.cljc +218 -0
  211. package/src/datahike/schema.cljc +228 -0
  212. package/src/datahike/schema_cache.cljc +42 -0
  213. package/src/datahike/spec.cljc +101 -0
  214. package/src/datahike/store.cljc +80 -0
  215. package/src/datahike/tools.cljc +308 -0
  216. package/src/datahike/transit.cljc +80 -0
  217. package/src/datahike/writer.cljc +239 -0
  218. package/src/datahike/writing.cljc +362 -0
  219. package/src/deps.cljs +1 -0
  220. package/src-hitchhiker-tree/datahike/index/hitchhiker_tree/insert.cljc +76 -0
  221. package/src-hitchhiker-tree/datahike/index/hitchhiker_tree/upsert.cljc +128 -0
  222. package/src-hitchhiker-tree/datahike/index/hitchhiker_tree.cljc +213 -0
  223. package/test/datahike/backward_compatibility_test/src/backward_test.clj +37 -0
  224. package/test/datahike/integration_test/config_record_file_test.clj +14 -0
  225. package/test/datahike/integration_test/config_record_test.clj +14 -0
  226. package/test/datahike/integration_test/depr_config_uri_test.clj +15 -0
  227. package/test/datahike/integration_test/return_map_test.clj +62 -0
  228. package/test/datahike/integration_test.cljc +67 -0
  229. package/test/datahike/norm/norm_test.clj +124 -0
  230. package/test/datahike/norm/resources/naming-and-sorting-test/001-a1-example.edn +5 -0
  231. package/test/datahike/norm/resources/naming-and-sorting-test/002-a2-example.edn +5 -0
  232. package/test/datahike/norm/resources/naming-and-sorting-test/003-tx-fn-test.edn +1 -0
  233. package/test/datahike/norm/resources/naming-and-sorting-test/004-tx-data-and-tx-fn-test.edn +5 -0
  234. package/test/datahike/norm/resources/naming-and-sorting-test/01-transact-basic-characters.edn +2 -0
  235. package/test/datahike/norm/resources/naming-and-sorting-test/02 add occupation.edn +5 -0
  236. package/test/datahike/norm/resources/naming-and-sorting-test/checksums.edn +12 -0
  237. package/test/datahike/norm/resources/simple-test/001-a1-example.edn +5 -0
  238. package/test/datahike/norm/resources/simple-test/002-a2-example.edn +5 -0
  239. package/test/datahike/norm/resources/simple-test/checksums.edn +4 -0
  240. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/001-a1-example.edn +5 -0
  241. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/002-a2-example.edn +5 -0
  242. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/003-tx-fn-test.edn +1 -0
  243. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/first/checksums.edn +6 -0
  244. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/second/004-tx-data-and-tx-fn-test.edn +5 -0
  245. package/test/datahike/norm/resources/tx-data-and-tx-fn-test/second/checksums.edn +2 -0
  246. package/test/datahike/norm/resources/tx-fn-test/first/001-a1-example.edn +5 -0
  247. package/test/datahike/norm/resources/tx-fn-test/first/002-a2-example.edn +5 -0
  248. package/test/datahike/norm/resources/tx-fn-test/first/checksums.edn +4 -0
  249. package/test/datahike/norm/resources/tx-fn-test/second/003-tx-fn-test.edn +1 -0
  250. package/test/datahike/norm/resources/tx-fn-test/second/checksums.edn +2 -0
  251. package/test/datahike/test/api_test.cljc +895 -0
  252. package/test/datahike/test/array_test.cljc +40 -0
  253. package/test/datahike/test/attribute_refs/datoms_test.cljc +140 -0
  254. package/test/datahike/test/attribute_refs/db_test.cljc +42 -0
  255. package/test/datahike/test/attribute_refs/differences_test.cljc +515 -0
  256. package/test/datahike/test/attribute_refs/entity_test.cljc +89 -0
  257. package/test/datahike/test/attribute_refs/pull_api_test.cljc +320 -0
  258. package/test/datahike/test/attribute_refs/query_find_specs_test.cljc +59 -0
  259. package/test/datahike/test/attribute_refs/query_fns_test.cljc +130 -0
  260. package/test/datahike/test/attribute_refs/query_interop_test.cljc +47 -0
  261. package/test/datahike/test/attribute_refs/query_not_test.cljc +193 -0
  262. package/test/datahike/test/attribute_refs/query_or_test.cljc +137 -0
  263. package/test/datahike/test/attribute_refs/query_pull_test.cljc +156 -0
  264. package/test/datahike/test/attribute_refs/query_rules_test.cljc +176 -0
  265. package/test/datahike/test/attribute_refs/query_test.cljc +241 -0
  266. package/test/datahike/test/attribute_refs/temporal_search.cljc +22 -0
  267. package/test/datahike/test/attribute_refs/transact_test.cljc +220 -0
  268. package/test/datahike/test/attribute_refs/utils.cljc +128 -0
  269. package/test/datahike/test/cache_test.cljc +38 -0
  270. package/test/datahike/test/components_test.cljc +92 -0
  271. package/test/datahike/test/config_test.cljc +158 -0
  272. package/test/datahike/test/core_test.cljc +105 -0
  273. package/test/datahike/test/datom_test.cljc +44 -0
  274. package/test/datahike/test/db_test.cljc +54 -0
  275. package/test/datahike/test/entity_spec_test.cljc +159 -0
  276. package/test/datahike/test/entity_test.cljc +103 -0
  277. package/test/datahike/test/explode_test.cljc +143 -0
  278. package/test/datahike/test/filter_test.cljc +75 -0
  279. package/test/datahike/test/gc_test.cljc +159 -0
  280. package/test/datahike/test/http/server_test.clj +192 -0
  281. package/test/datahike/test/http/writer_test.clj +86 -0
  282. package/test/datahike/test/ident_test.cljc +32 -0
  283. package/test/datahike/test/index_test.cljc +345 -0
  284. package/test/datahike/test/insert.cljc +125 -0
  285. package/test/datahike/test/java_bindings_test.clj +6 -0
  286. package/test/datahike/test/listen_test.cljc +41 -0
  287. package/test/datahike/test/lookup_refs_test.cljc +266 -0
  288. package/test/datahike/test/lru_test.cljc +27 -0
  289. package/test/datahike/test/migrate_test.clj +297 -0
  290. package/test/datahike/test/model/core.cljc +376 -0
  291. package/test/datahike/test/model/invariant.cljc +142 -0
  292. package/test/datahike/test/model/rng.cljc +82 -0
  293. package/test/datahike/test/model_test.clj +217 -0
  294. package/test/datahike/test/nodejs_test.cljs +262 -0
  295. package/test/datahike/test/online_gc_test.cljc +475 -0
  296. package/test/datahike/test/pod_test.clj +369 -0
  297. package/test/datahike/test/pull_api_test.cljc +474 -0
  298. package/test/datahike/test/purge_test.cljc +144 -0
  299. package/test/datahike/test/query_aggregates_test.cljc +101 -0
  300. package/test/datahike/test/query_find_specs_test.cljc +52 -0
  301. package/test/datahike/test/query_fns_test.cljc +523 -0
  302. package/test/datahike/test/query_interop_test.cljc +47 -0
  303. package/test/datahike/test/query_not_test.cljc +189 -0
  304. package/test/datahike/test/query_or_test.cljc +158 -0
  305. package/test/datahike/test/query_pull_test.cljc +147 -0
  306. package/test/datahike/test/query_rules_test.cljc +248 -0
  307. package/test/datahike/test/query_stats_test.cljc +218 -0
  308. package/test/datahike/test/query_test.cljc +984 -0
  309. package/test/datahike/test/schema_test.cljc +424 -0
  310. package/test/datahike/test/specification_test.cljc +30 -0
  311. package/test/datahike/test/store_test.cljc +78 -0
  312. package/test/datahike/test/stress_test.cljc +57 -0
  313. package/test/datahike/test/time_variance_test.cljc +518 -0
  314. package/test/datahike/test/tools_test.clj +134 -0
  315. package/test/datahike/test/transact_test.cljc +518 -0
  316. package/test/datahike/test/tuples_test.cljc +564 -0
  317. package/test/datahike/test/unstructured_test.cljc +291 -0
  318. package/test/datahike/test/upsert_impl_test.cljc +205 -0
  319. package/test/datahike/test/upsert_test.cljc +363 -0
  320. package/test/datahike/test/utils.cljc +110 -0
  321. package/test/datahike/test/validation_test.cljc +48 -0
  322. package/test/datahike/test/versioning_test.cljc +56 -0
  323. package/test/datahike/test.cljc +66 -0
  324. package/tests.edn +24 -0
@@ -0,0 +1,566 @@
1
+ # Distributed Architecture
2
+
3
+ Datahike's architecture is built on **immutable persistent data structures** that enable efficient distribution and collaboration. The database is fundamentally designed around two complementary approaches:
4
+
5
+ 1. **Distributed Index Space (DIS)**: Share persistent indices across processes—readers access data directly without database connections
6
+ 2. **Remote Procedure Calls (RPC)**: Centralize computation on a server for shared caching and simplified deployment
7
+
8
+ ![Network topology](assets/network_topology.svg)
9
+
10
+ # Distributed Index Space (DIS)
11
+
12
+ **Distributed Index Space is Datahike's key architectural advantage.** It enables massive read scalability and powers collaborative systems by treating database snapshots as immutable values that can be shared like files.
13
+
14
+ ## How it works
15
+
16
+ Datahike builds on **copy-on-write persistent data structures** where changes create new structure sharing most data with previous versions. When you transact to a database:
17
+
18
+ 1. New index nodes are written to the shared [storage backend](storage-backends.md) (S3, JDBC, file, etc.)
19
+ 2. A new root pointer is published atomically
20
+ 3. Readers pick up the new snapshot on next access—no active connections needed
21
+
22
+ This is similar to [Datomic](https://datomic.com), but **Datahike connections are lightweight and require no communication by default**. If you only need to read from a database (e.g., a dataset provided by a third party), you just need read access to the storage—no server setup required.
23
+
24
+ ## Scaling and collaboration
25
+
26
+ The DIS model provides fundamental advantages for distributed systems:
27
+
28
+ - **Massive read scaling**: Add readers without coordination—they access persistent indices directly
29
+ - **Zero connection overhead**: No connection pooling, no network round-trips for reads
30
+ - **Snapshot isolation**: Each reader sees a consistent point-in-time view
31
+ - **Efficient sharding**: Create one database per logical unit (e.g., per customer, per project)—readers can join across databases locally
32
+ - **Offline-first capable**: Readers can cache indices locally and sync differentially when online
33
+
34
+ This architecture enables collaborative systems where multiple processes share access to evolving datasets without centralized coordination. The same design principles that enable DIS (immutability, structural sharing) also support more advanced distribution patterns including CRDT-based merge strategies (see [replikativ](https://github.com/replikativ/replikativ)) and peer-to-peer synchronization (demonstrated with [dat-sync](https://github.com/replikativ/dat-sync)).
35
+
36
+ These capabilities are valuable even in centralized production environments: differential sync reduces bandwidth, immutable snapshots simplify caching and recovery, and the architecture naturally handles network partitions.
37
+
38
+ ## Single writer model
39
+
40
+ Datahike uses a **single-writer, multiple-reader** model—the same architectural choice as Datomic, Datalevin, and XTDB. While multiple readers can access indices concurrently via DIS, write operations are serialized through a single writer process to ensure strong consistency and linearizable transactions.
41
+
42
+ To provide distributed write access, you configure a writer endpoint (HTTP server or Kabel WebSocket). The writer:
43
+ - Serializes all transactions for strong consistency guarantees
44
+ - Publishes new index snapshots to the shared storage backend
45
+ - Allows unlimited readers to access the updated indices via DIS
46
+
47
+ **All readers continue to access data locally** from the distributed storage (shared filesystem, JDBC, S3, etc.) without connecting to the writer—they only contact it to submit transactions. This model is supported by all Datahike clients: JVM, Node.js, browser, CLI, Babashka pod, and libdatahike.
48
+
49
+ The client setup is simple, you just add a `:writer` entry in the configuration
50
+ for your database, e.g.
51
+
52
+ ```clojure
53
+ {:store {:backend :file
54
+ :id #uuid "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
55
+ :path "/shared/filesystem/store"}
56
+ :keep-history? true
57
+ :schema-flexibility :read
58
+ :writer {:backend :datahike-server
59
+ :url "http://localhost:4444"
60
+ :token "securerandompassword"}}
61
+ ```
62
+
63
+ You can now use the normal `datahike.api` as usual and all operations changing a
64
+ database, e.g. `create-database`, `delete-database` and `transact` are sent to
65
+ the server while all other calls are executed locally.
66
+
67
+ ### AWS lambda
68
+
69
+ An example setup to run Datahike distributed in AWS lambda without a server can
70
+ be found [here](https://github.com/viesti/clj-lambda-datahike). It configures a
71
+ singleton lambda for write operations while reader lambdas can be run multiple
72
+ times and scale out. This setup can be upgraded later to use dedicated servers
73
+ through EC2 instances.
74
+
75
+ ### Streaming writer (Kabel)
76
+
77
+ **Beta feature - please try it out and provide feedback.**
78
+
79
+ The Kabel writer provides **real-time reactive updates** via WebSockets, complementing the HTTP server's REST API. Where HTTP server is ideal for conventional REST integrations (including non-Clojure clients), Kabel enables live synchronization where clients receive database updates as they happen, without polling.
80
+
81
+ The stack consists of:
82
+
83
+ - [kabel](https://github.com/replikativ/kabel) - WebSocket transport with middleware support
84
+ - [distributed-scope](https://github.com/replikativ/distributed-scope) - Remote function invocation with Clojure semantics
85
+ - [konserve-sync](https://github.com/replikativ/konserve-sync) - Differential store synchronization (only transmits changed data)
86
+
87
+ This setup is particularly useful for browser clients where storage backends cannot be shared directly, and for applications requiring reactive UIs that update automatically when data changes on the server (see [JavaScript API](javascript-api.md)).
88
+
89
+ #### Server setup
90
+
91
+ The server owns the database and handles all write operations. It uses a file
92
+ backend and broadcasts updates to connected clients via konserve-sync.
93
+
94
+ ```clojure
95
+ (ns my-app.server
96
+ (:require [datahike.api :as d]
97
+ [datahike.kabel.handlers :as handlers]
98
+ [datahike.kabel.fressian-handlers :as fh]
99
+ [kabel.peer :as peer]
100
+ [kabel.http-kit :refer [create-http-kit-handler!]]
101
+ [konserve-sync.core :as sync]
102
+ [is.simm.distributed-scope :refer [remote-middleware invoke-on-peer]]
103
+ [superv.async :refer [S go-try <?]]
104
+ [clojure.core.async :refer [<!!]]))
105
+
106
+ (def server-id #uuid "aaaaaaaa-0000-0000-0000-000000000001")
107
+ (def server-url "ws://localhost:47296")
108
+
109
+ ;; Fressian middleware with Datahike type handlers for serialization
110
+ (defn datahike-fressian-middleware [peer-config]
111
+ (kabel.middleware.fressian/fressian
112
+ (atom fh/read-handlers)
113
+ (atom fh/write-handlers)
114
+ peer-config))
115
+
116
+ ;; Store config factory - maps client store UUID to server-side file store
117
+ ;; Browsers use TieredStore (memory + IndexedDB), but the server uses file backend
118
+ ;; The store-id parameter is the UUID from the client's :store :id field
119
+ (defn store-config-fn [store-id _client-config]
120
+ {:backend :file
121
+ :path (str "/var/data/datahike/" store-id)
122
+ :id store-id})
123
+
124
+ (defn start-server! []
125
+ (let [;; Create kabel server peer with middleware stack:
126
+ ;; - sync/server-middleware: handles konserve-sync replication
127
+ ;; - remote-middleware: handles distributed-scope RPC
128
+ ;; - datahike-fressian-middleware: serializes Datahike types
129
+ server (peer/server-peer
130
+ S
131
+ (create-http-kit-handler! S server-url server-id)
132
+ server-id
133
+ (comp (sync/server-middleware) remote-middleware)
134
+ datahike-fressian-middleware)]
135
+
136
+ ;; Start server and enable remote function invocation
137
+ (<!! (peer/start server))
138
+ (invoke-on-peer server)
139
+
140
+ ;; Register global Datahike handlers for create-database, delete-database, transact
141
+ ;; The :store-config-fn translates client config to server-side store config
142
+ (handlers/register-global-handlers! server {:store-config-fn store-config-fn})
143
+
144
+ server))
145
+ ```
146
+
147
+ #### Browser client setup
148
+
149
+ Browser clients use a TieredStore combining fast in-memory access with
150
+ persistent IndexedDB storage. The KabelWriter sends transactions to the server,
151
+ and konserve-sync replicates updates back to the client's store.
152
+
153
+ **Store IDs**: Store IDs should be UUIDs for distributed coordination. Use a
154
+ fixed UUID when multiple clients need to share the same database, or generate a
155
+ unique UUID with `(random-uuid)` for ephemeral/test databases.
156
+
157
+ ```clojure
158
+ (ns my-app.client
159
+ (:require [cljs.core.async :refer [<! timeout alts!] :refer-macros [go]]
160
+ [datahike.api :as d]
161
+ [datahike.kabel.fressian-handlers :refer [datahike-fressian-middleware]]
162
+ [is.simm.distributed-scope :as ds]
163
+ [kabel.peer :as peer]
164
+ [konserve-sync.core :as sync]
165
+ [superv.async :refer [S] :refer-macros [go-try <?]]))
166
+
167
+ (def server-url "ws://localhost:47296")
168
+ (def server-id #uuid "aaaaaaaa-0000-0000-0000-000000000001")
169
+ (def client-id #uuid "bbbbbbbb-0000-0000-0000-000000000002")
170
+
171
+ (defonce client-peer (atom nil))
172
+
173
+ (defn init-peer! []
174
+ ;; Create client peer with middleware stack (innermost runs first):
175
+ ;; - ds/remote-middleware: handles distributed-scope RPC responses
176
+ ;; - sync/client-middleware: handles konserve-sync replication
177
+ (let [peer-atom (peer/client-peer
178
+ S
179
+ client-id
180
+ (comp ds/remote-middleware (sync/client-middleware))
181
+ datahike-fressian-middleware)]
182
+ ;; Start invocation loop for handling remote calls
183
+ (ds/invoke-on-peer peer-atom)
184
+ (reset! client-peer peer-atom)))
185
+
186
+ (defn example []
187
+ ;; go-try/<? from superv.async propagate errors through async channels
188
+ ;; Use go/<! if you prefer manual error handling
189
+ (go-try S
190
+ ;; Connect to server via distributed-scope
191
+ (<? S (ds/connect-distributed-scope S @client-peer server-url))
192
+
193
+ (let [store-id (random-uuid)
194
+ db-name (str "db-" store-id)
195
+ ;; TieredStore: memory frontend for fast reads, IndexedDB for persistence
196
+ ;; The server uses file backend - store-config-fn handles this translation
197
+ ;; Note: All :id values must match for konserve validation
198
+ config {:store {:backend :tiered
199
+ :frontend-config {:backend :memory :id store-id}
200
+ :backend-config {:backend :indexeddb :name db-name :id store-id}
201
+ :id store-id}
202
+ :writer {:backend :kabel
203
+ :peer-id server-id
204
+ :local-peer @client-peer}
205
+ :schema-flexibility :write
206
+ :keep-history? false}]
207
+
208
+ ;; Create database on server (transmitted via distributed-scope RPC)
209
+ (<? S (d/create-database config))
210
+
211
+ ;; Connect locally - syncs initial state from server via konserve-sync
212
+ ;; TieredStore caches data from IndexedDB into memory before subscribing
213
+ ;; so the sync handshake only requests keys newer than cached timestamps
214
+ (let [conn (<? S (d/connect config {:sync? false}))]
215
+
216
+ ;; Transact schema - sent to server, then synced back to local store
217
+ (<? S (d/transact! conn [{:db/ident :name
218
+ :db/valueType :db.type/string
219
+ :db/cardinality :db.cardinality/one}]))
220
+
221
+ ;; Transact data
222
+ (<? S (d/transact! conn [{:name "Alice"} {:name "Bob"}]))
223
+
224
+ ;; Query locally - no network round-trip needed
225
+ (let [db (d/db conn)
226
+ results (d/q '[:find ?name :where [?e :name ?name]] db)]
227
+ (println "Found:" results)) ;; => #{["Alice"] ["Bob"]}
228
+
229
+ ;; Clean up
230
+ (d/release conn)
231
+ (<? S (d/delete-database config))))))
232
+ ```
233
+
234
+ # Remote Procedure Calls (RPC)
235
+
236
+ In addition to DIS, Datahike supports **remote procedure calls** where all operations (reads and writes) are executed on a server. This approach is complementary to DIS:
237
+
238
+ **Use RPC when:**
239
+ - You want simplified deployment (thin clients, all logic on server)
240
+ - Shared server-side caching benefits multiple clients
241
+ - Clients are resource-constrained (mobile, embedded)
242
+ - You need conventional REST integration with non-Clojure clients
243
+
244
+ **Use DIS when:**
245
+ - Read scalability is critical (unlimited readers without server load)
246
+ - You want offline-capable or low-latency reads
247
+ - Clients need to run custom queries with local functions/closures
248
+ - Network bandwidth or availability is a concern
249
+
250
+ The remote API has the same call signatures as `datahike.api` and is located in `datahike.api.client`. All functionality except `listen!` and `with` is supported. To use it, add `:remote-peer` to your config:
251
+
252
+ ```clojure
253
+ {:store {:backend :memory :id "distributed-datahike"}
254
+ :keep-history? true
255
+ :schema-flexibility :read
256
+ :remote-peer {:backend :datahike-server
257
+ :url "http://localhost:4444"
258
+ :token "securerandompassword"}}
259
+ ```
260
+
261
+ The API will return lightweight remote pointers that follow the same semantics
262
+ as `datahike.api`, but do not support any of Datahike's local functionality,
263
+ i.e. you can only use them with this API.
264
+
265
+ # Hybrid Architecture
266
+
267
+ You can combine DIS and RPC in the same deployment. For example:
268
+ - A set of application servers access a shared database via DIS (direct index access)
269
+ - These servers expose RPC/REST APIs to external clients
270
+ - Internal servers benefit from DIS scalability and local query execution
271
+ - External clients get a simple REST interface without needing Datahike dependencies
272
+
273
+ This pattern is common in production systems where internal services need high-performance data access while external integrations require conventional APIs.
274
+
275
+ # HTTP Server Setup
276
+
277
+ The HTTP server provides a **REST/RPC interface** for conventional integrations with any language or tool that speaks HTTP. Use this when you need request/response semantics rather than reactive updates (for reactive updates, see Kabel above).
278
+
279
+ To build locally, clone the repository and run `bb http-server-uber` to create the jar. Run the server with:
280
+
281
+ ```bash
282
+ java -jar datahike-http-server-VERSION.jar path/to/config.edn
283
+ ```
284
+
285
+ The edn configuration file looks like:
286
+
287
+ ```clojure
288
+ {:port 4444
289
+ :level :debug
290
+ :dev-mode true
291
+ :token "securerandompassword"}
292
+ ```
293
+
294
+ Port sets the `port` to run the HTTP server under, `level` sets the log-level.
295
+ `dev-mode` deactivates authentication during development and if `token` is
296
+ provided then you need to send this token as the HTTP header "token" to
297
+ authenticate.
298
+
299
+ The server exports a swagger interface on the port and can serialize requests in
300
+ `transit-json`, `edn` and `JSON` with
301
+ [jsonista](https://github.com/metosin/jsonista) tagged literals. The server
302
+ exposes all referentially transparent calls (that don't change given their
303
+ arguments) as GET requests and all requests that depend on input information as
304
+ POST requests. All arguments in both cases are sent as a list *in the request
305
+ body*.
306
+
307
+ ### Extended configuration
308
+
309
+ CORS headers can be set, e.g. with adding
310
+ ```clojure
311
+ :access-control-allow-origin [#"http://localhost" #"http://localhost:8080"]
312
+ ```
313
+
314
+ The server also experimentally supports HTTP caching for GET requests, e.g. by adding
315
+ ```clojure
316
+ :cache {:get {:max-age 3600}}
317
+ ```
318
+
319
+ This should be beneficially in case your HTTP client or proxy supports efficient
320
+ caching and you often run the same queries many times on different queries (e.g.
321
+ to retrieve a daily context in an app against a database only changes with low
322
+ frequency.)
323
+
324
+ # JSON Support (HTTP Server)
325
+
326
+ The HTTP server supports JSON with embedded [tagged literals](https://github.com/metosin/jsonista#tagged-json) for language-agnostic integration. This allows non-Clojure clients (JavaScript, Python, etc.) to interact with Datahike using familiar JSON syntax.
327
+
328
+ When sending HTTP requests to the datahike-server, you can use JSON argument arrays in each method body. Include the "token" header if authentication is enabled.
329
+
330
+ `POST` to "/create-database"
331
+ ```javascript
332
+ ["{:schema-flexibility :read}"]
333
+ ```
334
+ Note that here you can pass the configuration as an `edn` string, which is more concise. If you want to speak JSON directly you would pass
335
+ ```
336
+ [{"schema-flexibility": ["!kw", "read"]}]
337
+ ```
338
+
339
+ "!kw" annotates a tagged literal here and encodes that "read" is an `edn`
340
+ keyword.
341
+
342
+ The resulting configuration will look like (with random DB name):
343
+ ```javascript
344
+ cfg = {
345
+ "keep-history?": true,
346
+ "search-cache-size": 10000,
347
+ "index": [
348
+ "!kw",
349
+ "datahike.index/persistent-set"
350
+ ],
351
+ "store": {
352
+ "id": "wiggly-field-vole",
353
+ "backend": [
354
+ "!kw",
355
+ "memory"
356
+ ]
357
+ },
358
+ "store-cache-size": 1000,
359
+ "attribute-refs?": false,
360
+ "writer": {
361
+ "backend": [
362
+ "!kw",
363
+ "self"
364
+ ]
365
+ },
366
+ "crypto-hash?": false,
367
+ "remote-peer": null,
368
+ "schema-flexibility": [
369
+ "!kw",
370
+ "read"
371
+ ],
372
+ "branch": [
373
+ "!kw",
374
+ "db"
375
+ ]
376
+ }
377
+ ```
378
+
379
+ You can now use this cfg to connect to this database:
380
+
381
+ `POST` to "/connect"
382
+ ```javascript
383
+ [cfg]
384
+ ```
385
+
386
+ The result will look like:
387
+
388
+ ```javascript
389
+ conn = ["!datahike/Connection",[[["!kw","memory"],"wiggly-field-vole"],["!kw","db"]]]
390
+ ```
391
+
392
+ Finally let's add some data to the database:
393
+
394
+ `POST` to "/transact"
395
+ ```javascript
396
+ [conn, [{"name": "Peter", "age": 42}]]
397
+ ```
398
+
399
+ The result is a comprehensive transaction record (feel free to ignore the details):
400
+
401
+ ```javascript
402
+ [
403
+ "!datahike/TxReport",
404
+ {
405
+ "db-before": [
406
+ "!datahike/DB",
407
+ {
408
+ "store-id": [
409
+ [
410
+ [
411
+ "!kw",
412
+ "memory"
413
+ ],
414
+ "wiggly-field-vole"
415
+ ],
416
+ [
417
+ "!kw",
418
+ "db"
419
+ ]
420
+ ],
421
+ "commit-id": [
422
+ "!uuid",
423
+ "2c8f71f9-a3c6-4189-ba0c-e183cc29c672"
424
+ ],
425
+ "max-eid": 1,
426
+ "max-tx": 536870913
427
+ }
428
+ ],
429
+ "db-after": [
430
+ "!datahike/DB",
431
+ {
432
+ "store-id": [
433
+ [
434
+ [
435
+ "!kw",
436
+ "memory"
437
+ ],
438
+ "wiggly-field-vole"
439
+ ],
440
+ [
441
+ "!kw",
442
+ "db"
443
+ ]
444
+ ],
445
+ "commit-id": [
446
+ "!uuid",
447
+ "6ebf8979-cdf0-41f4-b615-30ff81830b0c"
448
+ ],
449
+ "max-eid": 2,
450
+ "max-tx": 536870914
451
+ }
452
+ ],
453
+ "tx-data": [
454
+ [
455
+ "!datahike/Datom",
456
+ [
457
+ 536870914,
458
+ [
459
+ "!kw",
460
+ "db/txInstant"
461
+ ],
462
+ [
463
+ "!date",
464
+ "1695952443102"
465
+ ],
466
+ 536870914,
467
+ true
468
+ ]
469
+ ],
470
+ [
471
+ "!datahike/Datom",
472
+ [
473
+ 2,
474
+ [
475
+ "!kw",
476
+ "age"
477
+ ],
478
+ 42,
479
+ 536870914,
480
+ true
481
+ ]
482
+ ],
483
+ [
484
+ "!datahike/Datom",
485
+ [
486
+ 2,
487
+ [
488
+ "!kw",
489
+ "name"
490
+ ],
491
+ "Peter",
492
+ 536870914,
493
+ true
494
+ ]
495
+ ]
496
+ ],
497
+ "tempids": {
498
+ "db/current-tx": 536870914
499
+ },
500
+ "tx-meta": {
501
+ "db/txInstant": [
502
+ "!date",
503
+ "1695952443102"
504
+ ],
505
+ "db/commitId": [
506
+ "!uuid",
507
+ "6ebf8979-cdf0-41f4-b615-30ff81830b0c"
508
+ ]
509
+ }
510
+ }
511
+ ]
512
+ ```
513
+
514
+ Note that you can extract the snapshots of the database `db-before` and `db-after` the commit as well as the facts added to the database as `tx-data`.
515
+
516
+ To retrieve the current database for your connection use
517
+
518
+ `POST` to "/db"
519
+ ```javascript
520
+ [conn]
521
+ ```
522
+
523
+ The result looks like:
524
+
525
+ ```javascript
526
+ db = [
527
+ "!datahike/DB",
528
+ {
529
+ "store-id": [
530
+ [
531
+ [
532
+ "!kw",
533
+ "mem"
534
+ ],
535
+ "127.0.1.1",
536
+ "wiggly-field-vole"
537
+ ],
538
+ [
539
+ "!kw",
540
+ "db"
541
+ ]
542
+ ],
543
+ "commit-id": [
544
+ "!uuid",
545
+ "6ebf8979-cdf0-41f4-b615-30ff81830b0c"
546
+ ],
547
+ "max-eid": 2,
548
+ "max-tx": 536870914
549
+ }
550
+ ]
551
+ ```
552
+
553
+ You can query this database with the query endpoint. We recommend again using a string to denote the query DSL instead of direct JSON encoding unless you want to manipulate the queries in JSON programmatically.
554
+
555
+ `GET` from "/q"
556
+ ```javascript
557
+ ["[:find ?n ?a :where [?e :name ?n] [?e :age ?a]]", db]
558
+ ```
559
+
560
+ The result set is
561
+
562
+ ```javascript
563
+ ["!set",[["Peter",42]]]
564
+ ```
565
+
566
+ You can also pass strings for pull expressions and to pass configurations to `delete-database` and `database-exists`.
@@ -0,0 +1,92 @@
1
+ # Entity Specs
2
+
3
+ Similar to [Datomic's](https://www.datomic.com/) [entity specs](https://docs.datomic.com/on-prem/schema.html#entity-specs) Datahike supports assertion to ensure properties of transacted entities.
4
+
5
+ **⚠️ Important:** Predicate validation (`:db.entity/preds`) only works in Clojure, not ClojureScript. Attribute validation (`:db.entity/attrs`) works in both.
6
+
7
+ In short: you need to transact a `spec` to the database using `:db/ident` as the identifier, with at least either `:db.entity/attrs` as a list of attributes defined in the schema for ensuring required attributes, or `:db.entity/preds` with a list of fully namespaced symbols that refer to predicate functions that you want to assert. The signature of the predicate function should be `[db eid]` with the database value `db` where the transaction has happened and the entity id `eid` to be asserted.
8
+
9
+
10
+ ## Example
11
+
12
+ Imagine you have a database where an account has an email, a holder, and a balance. Each new account should have an `email` and a `holder` as a required attribute and the email should be checked for its standard.
13
+ The next spec should check a new `balance` to not be less than 0.
14
+
15
+
16
+ Let's fire up a REPL:
17
+
18
+ ```clojure
19
+ (require '[datahike.api :as d])
20
+
21
+ ;; setup database with schema and connection
22
+ (def schema [{:db/ident :account/email
23
+ :db/valueType :db.type/string
24
+ :db/unique :db.unique/identity
25
+ :db/cardinality :db.cardinality/one}
26
+ {:db/ident :account/holder
27
+ :db/valueType :db.type/string
28
+ :db/cardinality :db.cardinality/one}
29
+ {:db/ident :account/balance
30
+ :db/valueType :db.type/long
31
+ :db/cardinality :db.cardinality/one}])
32
+
33
+ (def cfg {:store {:backend :memory
34
+ :id #uuid "550e8400-e29b-41d4-a716-446655440000"}
35
+ :schema-flexibility :write
36
+ :keep-history? true
37
+ :initial-tx schema})
38
+
39
+ (d/delete-database cfg)
40
+
41
+ (d/create-database cfg)
42
+
43
+ (def conn (d/connect cfg))
44
+
45
+ ;; define both predicates (must be fully-qualified symbols matching the namespace)
46
+ (defn is-email? [db eid]
47
+ (if-let [email (:account/email (d/entity db eid))]
48
+ (seq (re-find #"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)" email))
49
+ false))
50
+
51
+ (defn positive-balance? [db eid]
52
+ (if-let [balance (-> (d/entity db eid) :account/balance)]
53
+ (< 0 balance)
54
+ false))
55
+
56
+ ;; add the person spec
57
+ ;; Note: 'user/is-email? assumes you're in the 'user namespace (default REPL namespace)
58
+ ;; If running in a different namespace, use that namespace in the symbol
59
+ (d/transact conn {:tx-data [{:db/ident :person/guard
60
+ :db.entity/attrs [:account/email :account/holder]
61
+ :db.entity/preds ['user/is-email?]}]})
62
+
63
+ (def valid-account {:account/email "emma@datahike.io"
64
+ :account/holder "Emma"})
65
+
66
+ ;; add a valid person
67
+ (d/transact conn {:tx-data [(assoc valid-account :db/ensure :person/guard)]})
68
+
69
+ ;; add with missing holder, observe exception
70
+ (d/transact conn {:tx-data [{:account/email "benedikt@datahike.io"
71
+ :db/ensure :person/guard}]})
72
+
73
+ ;; add with invalid email, observe exception
74
+ (d/transact conn {:tx-data [{:account/email "thekla@datahike"
75
+ :account/holder "Thekla"
76
+ :db/ensure :person/guard}]})
77
+
78
+ ;; add the balance spec
79
+ (d/transact conn {:tx-data [{:db/ident :balance/guard
80
+ :db.entity/attrs [:account/balance]
81
+ :db.entity/preds ['user/positive-balance?]}]})
82
+
83
+ ;; add valid balance
84
+ (d/transact conn {:tx-data [{:db/id [:account/email (:account/email valid-account)]
85
+ :account/balance 1000
86
+ :db/ensure :balance/guard}]})
87
+
88
+ ;; add invalid negative balance, observe exception
89
+ (d/transact conn {:tx-data [{:db/id [:account/email (:account/email valid-account)]
90
+ :account/balance -100
91
+ :db/ensure :balance/guard}]})
92
+ ```