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,121 @@
1
+ package examples;
2
+
3
+ import datahike.java.Datahike;
4
+ import datahike.java.Database;
5
+ import datahike.java.SchemaFlexibility;
6
+
7
+ import java.time.Instant;
8
+ import java.util.*;
9
+
10
+ /**
11
+ * Example demonstrating time-travel queries.
12
+ *
13
+ * Shows:
14
+ * - Querying historical database states
15
+ * - Using asOf for point-in-time queries
16
+ * - Using since for change tracking
17
+ * - Querying full history with all versions
18
+ */
19
+ public class TimeTravelExample {
20
+ public static void main(String[] args) throws InterruptedException {
21
+ System.out.println("=== Time Travel Example ===\n");
22
+
23
+ // Configure database with history enabled
24
+ Map<String, Object> config = Database.memory(UUID.randomUUID())
25
+ .keepHistory(true)
26
+ .schemaFlexibility(SchemaFlexibility.READ)
27
+ .build();
28
+
29
+ Datahike.createDatabase(config);
30
+ Object conn = Datahike.connect(config);
31
+
32
+ // 1. Initial state - add Alice
33
+ System.out.println("1. Initial state - adding Alice...");
34
+ Datahike.transact(conn, List.of(
35
+ Map.of("name", "Alice", "status", "active")
36
+ ));
37
+
38
+ Date t1 = new Date();
39
+ System.out.println("Timestamp T1: " + t1);
40
+
41
+ Thread.sleep(100); // Small delay for distinct timestamps
42
+
43
+ // 2. Add Bob
44
+ System.out.println("\n2. Adding Bob...");
45
+ Datahike.transact(conn, List.of(
46
+ Map.of("name", "Bob", "status", "active")
47
+ ));
48
+
49
+ Date t2 = new Date();
50
+ System.out.println("Timestamp T2: " + t2);
51
+
52
+ Thread.sleep(100);
53
+
54
+ // 3. Update Alice's status
55
+ System.out.println("\n3. Updating Alice's status...");
56
+ Set<?> aliceResult = (Set<?>) Datahike.q(
57
+ "[:find ?e :where [?e :name \"Alice\"]]",
58
+ Datahike.deref(conn)
59
+ );
60
+
61
+ Object aliceId = ((List<?>) aliceResult.iterator().next()).get(0);
62
+ Datahike.transact(conn, List.of(
63
+ Map.of(":db/id", aliceId, "status", "inactive")
64
+ ));
65
+
66
+ Date t3 = new Date();
67
+ System.out.println("Timestamp T3: " + t3);
68
+
69
+ // 4. Query current state
70
+ System.out.println("\n4. Current state:");
71
+ Set<?> currentState = (Set<?>) Datahike.q(
72
+ "[:find ?name ?status :where [?e :name ?name] [?e :status ?status]]",
73
+ Datahike.deref(conn)
74
+ );
75
+ currentState.forEach(System.out::println);
76
+
77
+ // 5. Query as of T1 (only Alice existed)
78
+ System.out.println("\n5. State as of T1 (only Alice):");
79
+ Object dbAtT1 = Datahike.asOf(Datahike.deref(conn), t1);
80
+ Set<?> stateAtT1 = (Set<?>) Datahike.q(
81
+ "[:find ?name ?status :where [?e :name ?name] [?e :status ?status]]",
82
+ dbAtT1
83
+ );
84
+ stateAtT1.forEach(System.out::println);
85
+
86
+ // 6. Query as of T2 (both existed, Alice was active)
87
+ System.out.println("\n6. State as of T2 (both, Alice active):");
88
+ Object dbAtT2 = Datahike.asOf(Datahike.deref(conn), t2);
89
+ Set<?> stateAtT2 = (Set<?>) Datahike.q(
90
+ "[:find ?name ?status :where [?e :name ?name] [?e :status ?status]]",
91
+ dbAtT2
92
+ );
93
+ stateAtT2.forEach(System.out::println);
94
+
95
+ // 7. Query changes since T2
96
+ System.out.println("\n7. Changes since T2:");
97
+ Object dbSinceT2 = Datahike.since(Datahike.deref(conn), t2);
98
+ Set<?> changesSinceT2 = (Set<?>) Datahike.q(
99
+ "[:find ?name ?status :where [?e :name ?name] [?e :status ?status]]",
100
+ dbSinceT2
101
+ );
102
+ System.out.println("Changed entities:");
103
+ changesSinceT2.forEach(System.out::println);
104
+
105
+ // 8. Query full history (includes all versions)
106
+ System.out.println("\n8. Full history of Alice's status:");
107
+ Object historyDb = Datahike.history(Datahike.deref(conn));
108
+ Set<?> aliceHistory = (Set<?>) Datahike.q(
109
+ "[:find ?status :where [?e :name \"Alice\"] [?e :status ?status]]",
110
+ historyDb
111
+ );
112
+ System.out.println("All status values for Alice:");
113
+ aliceHistory.forEach(System.out::println);
114
+
115
+ // Cleanup
116
+ Datahike.deleteDatabase(config);
117
+
118
+ System.out.println("\n=== Example completed successfully! ===");
119
+ System.exit(0);
120
+ }
121
+ }
package/flake.lock ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "nodes": {
3
+ "nixpkgs": {
4
+ "locked": {
5
+ "lastModified": 1752950548,
6
+ "narHash": "sha256-NS6BLD0lxOrnCiEOcvQCDVPXafX1/ek1dfJHX1nUIzc=",
7
+ "owner": "nixos",
8
+ "repo": "nixpkgs",
9
+ "rev": "c87b95e25065c028d31a94f06a62927d18763fdf",
10
+ "type": "github"
11
+ },
12
+ "original": {
13
+ "owner": "nixos",
14
+ "ref": "nixos-unstable",
15
+ "repo": "nixpkgs",
16
+ "type": "github"
17
+ }
18
+ },
19
+ "root": {
20
+ "inputs": {
21
+ "nixpkgs": "nixpkgs"
22
+ }
23
+ }
24
+ },
25
+ "root": "root",
26
+ "version": 7
27
+ }
package/flake.nix ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ inputs = {
3
+ nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
4
+ };
5
+
6
+ outputs = { self, nixpkgs }:
7
+ let
8
+ lib = nixpkgs.lib;
9
+ allSystems = [
10
+ "aarch64-darwin"
11
+ "aarch64-linux"
12
+ "x86_64-linux"
13
+ ];
14
+ pkgsFor = lib.genAttrs allSystems (system: import nixpkgs { inherit system; config.allowUnfree = true; });
15
+ overEachSystem = f: lib.genAttrs allSystems (system: f { inherit system; pkgs = pkgsFor.${system}; });
16
+ in {
17
+ formatter = overEachSystem ({ system, pkgs }: pkgs.nixfmt-rcf-style);
18
+ devShells = overEachSystem ({ system, pkgs }: {
19
+ default = pkgs.mkShellNoCC {
20
+ packages = [
21
+ pkgs.clojure
22
+ pkgs.babashka
23
+ ];
24
+ };
25
+ });
26
+ };
27
+ }
@@ -0,0 +1,75 @@
1
+ (ns datahike.http.middleware
2
+ (:require
3
+ [buddy.auth :refer [authenticated?]]
4
+ [buddy.auth.backends :as buddy-auth-backends]
5
+ [buddy.auth.middleware :as buddy-auth-middleware]
6
+ [clojure.edn :as edn]
7
+ [clojure.walk :as cw]
8
+ [datahike.json :as json]
9
+ [datahike.readers :refer [edn-readers]]
10
+ [muuntaja.core :as m]
11
+ [taoensso.timbre :refer [info trace]])
12
+ (:import
13
+ [clojure.lang ExceptionInfo]))
14
+
15
+ (defn auth
16
+ "Middleware used in routes that require authentication. If request is not
17
+ authenticated a 401 not authorized response will be returned.
18
+ Dev mode always authenticates."
19
+ [config handler]
20
+ (fn [request]
21
+ (if (or (:dev-mode config) (authenticated? request))
22
+ (handler request)
23
+ {:status 401 :error "Not authorized"})))
24
+
25
+ (defn token-auth
26
+ "Middleware used on routes requiring token authentication"
27
+ [config handler]
28
+ (buddy-auth-middleware/wrap-authentication
29
+ handler
30
+ (buddy-auth-backends/token {:token-name "token"
31
+ :authfn (fn [_ token]
32
+ (let [valid-token? (not (nil? token))
33
+ correct-auth? (= (:token config) token)]
34
+ (when (and correct-auth? valid-token?)
35
+ "authenticated-user")))})))
36
+
37
+ ;; TOOD map more errors
38
+ (defn cause->status-code [cause]
39
+ 400)
40
+
41
+ (defn encode-plain-value [muuntaja-with-opts]
42
+ (fn [handler]
43
+ (fn [request]
44
+ (let [format (:content-type request)
45
+ encoder (m/encoder muuntaja-with-opts format)
46
+ response (handler request)
47
+ ret (if (not (instance? java.io.ByteArrayInputStream (:body response)))
48
+ (update response :body #(encoder %))
49
+ response)]
50
+ ret))))
51
+
52
+ (defn patch-swagger-json [handler]
53
+ (fn [request]
54
+ (let [response (handler request)]
55
+ (if (get-in response [:body :swagger])
56
+ (cw/postwalk (fn [n]
57
+ (if (set? n) (vec n) n))
58
+ response)
59
+ response))))
60
+
61
+ (defn support-embedded-edn-in-json [handler]
62
+ (fn [request]
63
+ (let [{:keys [content-type body-params uri]} request]
64
+ (if (= content-type "application/json")
65
+ (if (.endsWith ^String uri "transact")
66
+ (let [[conn tx-data] body-params
67
+ new-body-params [conn (json/xf-data-for-tx tx-data @conn)]]
68
+ (trace "transact transformation" new-body-params)
69
+ (handler (assoc request :body-params new-body-params)))
70
+ (let [[f & r] body-params
71
+ new-body-params (vec (concat [(if (string? f) (edn/read-string {:readers edn-readers} f) f)] r))]
72
+ (trace "old-body-params" body-params)
73
+ (trace "new-body-params" new-body-params)
74
+ (handler (assoc request :body-params new-body-params))))
75
+ (handler request)))))
@@ -0,0 +1,269 @@
1
+ (ns datahike.http.server
2
+ "HTTP server implementation for Datahike."
3
+ (:gen-class)
4
+ (:refer-clojure :exclude [read-string filter])
5
+ (:require
6
+ [clojure.string :as str]
7
+ [clojure.edn :as edn]
8
+ [clojure.core.async :as async]
9
+ [datahike.connections :refer [*connections*]]
10
+ [datahike.api.specification :refer [api-specification ->url]]
11
+ [datahike.api.types :as types]
12
+ [datahike.http.middleware :as middleware]
13
+ [datahike.readers :refer [edn-readers]]
14
+ [datahike.transit :as transit]
15
+ [datahike.json :as json]
16
+ [datahike.api :refer :all :as api]
17
+ [datahike.writing]
18
+ [datahike.writer]
19
+ [reitit.ring :as ring]
20
+ [reitit.coercion.malli]
21
+ [malli.util :as mu]
22
+ [reitit.swagger :as swagger]
23
+ [reitit.swagger-ui :as swagger-ui]
24
+ [reitit.ring.coercion :as coercion]
25
+ [reitit.ring.middleware.muuntaja :as muuntaja]
26
+ [reitit.ring.middleware.exception :as exception]
27
+ [reitit.ring.middleware.multipart :as multipart]
28
+ [reitit.ring.middleware.parameters :as parameters]
29
+ [ring.middleware.cors :refer [wrap-cors]]
30
+ [muuntaja.core :as m]
31
+ [datahike.tools :refer [datahike-version]]
32
+ [datahike.impl.entity :as de]
33
+ [taoensso.timbre :as log]
34
+ [ring.adapter.jetty :refer [run-jetty]])
35
+ (:import [datahike.datom Datom]))
36
+
37
+ (defn generic-handler [config f]
38
+ (fn [request]
39
+ (try
40
+ (let [{{body :body} :parameters
41
+ :keys [headers params method]} request
42
+ _ (log/trace "request body" f body)
43
+ ;; TODO move this to client
44
+ ret-body
45
+ (cond (= f #'api/create-database)
46
+ ;; remove remote-peer and re-add
47
+ (assoc
48
+ (apply f (dissoc (first body) :remote-peer) (rest body))
49
+ :remote-peer (:remote-peer (first body)))
50
+
51
+ (= f #'api/delete-database)
52
+ (apply f (dissoc (first body) :remote-peer) (rest body))
53
+
54
+ :else
55
+ (apply f body))]
56
+ (log/trace "return body" ret-body)
57
+ (merge
58
+ {:status 200
59
+ :body
60
+ (when-not (headers "no-return-value")
61
+ ret-body)}
62
+ (when (and (= method :get)
63
+ (get params "args-id")
64
+ (get-in config [:cache :get :max-age]))
65
+ {:headers {"Cache-Control" (str (when-not (:token config) "public, ")
66
+ "max-age=" (get-in config [:cache :get :max-age]))}})))
67
+ (catch Exception e
68
+ {:status 500
69
+ :body {:msg (ex-message e)
70
+ :ex-data (ex-data e)}}))))
71
+
72
+ (declare create-routes)
73
+
74
+ (defn extract-first-sentence [doc]
75
+ (str (first (str/split doc #"\.\s")) "."))
76
+
77
+ (defn has-cat-operators?
78
+ "Check if args list contains :cat-specific operators like [:* ...], [:+ ...], [:alt ...], etc.
79
+ These operators are only valid in :cat schemas, not in :tuple schemas."
80
+ [args]
81
+ (some #(and (vector? %) (#{:* :+ :? :alt :altn} (first %))) args))
82
+
83
+ (defn extract-input-schema
84
+ "Extract input schema from malli function schema for HTTP body validation.
85
+ Converts [:=> [:cat Type1 Type2] ret] to [:tuple Type1 Type2]
86
+ or [:function [:=> [:cat T1] ret] [:=> [:cat T1 T2] ret]] to [:or [:tuple T1] [:tuple T1 T2]]
87
+
88
+ The HTTP body is a tuple/vector of arguments that matches the function signature.
89
+ For zero-arity functions, we use [:= []] to match an empty vector.
90
+ For functions with :cat operators ([:* ...], [:alt ...], etc), we use [:sequential :any]
91
+ since tuples can't express these dynamic patterns."
92
+ [schema]
93
+ (cond
94
+ ;; Multi-arity: [:function [:=> [:cat ...] ret] ...]
95
+ (and (vector? schema) (= :function (first schema)))
96
+ (let [input-schemas (for [arity-schema (rest schema)
97
+ :when (and (vector? arity-schema)
98
+ (= :=> (first arity-schema)))
99
+ :let [[_ input-schema _] arity-schema
100
+ args (when (and (vector? input-schema)
101
+ (= :cat (first input-schema)))
102
+ (rest input-schema))]]
103
+ (cond
104
+ (not (seq args)) [:= []] ;; Zero-arity
105
+ (has-cat-operators? args) [:sequential :any] ;; Has :cat operators - can't use tuple
106
+ :else (vec (cons :tuple args))))] ;; Fixed arity
107
+ (if (> (count input-schemas) 1)
108
+ (vec (cons :or input-schemas))
109
+ (first input-schemas)))
110
+
111
+ ;; Single arity: [:=> [:cat Type1 Type2] ret]
112
+ (and (vector? schema) (= :=> (first schema)))
113
+ (let [[_ input-schema _] schema]
114
+ (if (and (vector? input-schema) (= :cat (first input-schema)))
115
+ (let [args (rest input-schema)]
116
+ (cond
117
+ (not (seq args)) [:= []] ;; Zero-arity
118
+ (has-cat-operators? args) [:sequential :any] ;; Has :cat operators
119
+ :else (vec (cons :tuple args)))) ;; Fixed arity
120
+ [:sequential :any]))
121
+
122
+ ;; Fallback
123
+ :else [:sequential :any]))
124
+
125
+ ;; This code expands and evals the server route construction given the
126
+ ;; API specification.
127
+ (eval
128
+ `(defn ~'create-routes [~'config]
129
+ ~(vec
130
+ (for [[n {:keys [args doc supports-remote? referentially-transparent?]}] api-specification
131
+ :when supports-remote?]
132
+ `[~(str "/" (->url n))
133
+ {:swagger {:tags ["API"]}
134
+ ~(if referentially-transparent? :get :post)
135
+ {:operationId ~(str n)
136
+ :summary ~(extract-first-sentence doc)
137
+ :description ~doc
138
+ :parameters {:body ~(extract-input-schema args)}
139
+ :handler (generic-handler ~'config ~(resolve n))}}]))))
140
+
141
+ (def muuntaja-with-opts
142
+ (m/create
143
+ (-> m/default-options
144
+ (assoc-in [:formats "application/edn" :decoder-opts]
145
+ {:readers edn-readers})
146
+ (assoc-in [:formats "application/json" :decoder-opts]
147
+ json/mapper-opts)
148
+ (assoc-in [:formats "application/json" :encoder-opts]
149
+ json/mapper-opts)
150
+ (assoc-in [:formats "application/transit+json" :decoder-opts]
151
+ {:handlers transit/read-handlers})
152
+ (assoc-in [:formats "application/transit+json" :encoder-opts]
153
+ {:handlers transit/write-handlers}))))
154
+
155
+ (defn default-route-opts [muuntaja-with-opts]
156
+ {:data {:coercion (reitit.coercion.malli/create
157
+ {:compile mu/closed-schema
158
+ :strip-extra-keys true
159
+ :default-values true
160
+ :options {:registry types/registry}})
161
+ :muuntaja muuntaja-with-opts
162
+ :middleware [swagger/swagger-feature
163
+ parameters/parameters-middleware
164
+ muuntaja/format-negotiate-middleware
165
+ muuntaja/format-response-middleware
166
+ exception/exception-middleware
167
+ muuntaja/format-request-middleware
168
+ (middleware/encode-plain-value muuntaja-with-opts)
169
+ middleware/support-embedded-edn-in-json
170
+ coercion/coerce-response-middleware
171
+ coercion/coerce-request-middleware
172
+ multipart/multipart-middleware
173
+ middleware/patch-swagger-json]}})
174
+
175
+ (defn internal-writer-routes [server-connections]
176
+ [["/delete-database-writer"
177
+ {:post {:parameters {:body [:sequential :any]},
178
+ :summary "Internal endpoint. DO NOT USE!"
179
+ :no-doc true
180
+ :handler (fn [{{:keys [body]} :parameters}]
181
+ (binding [*connections* server-connections]
182
+ (let [cfg (dissoc (first body) :remote-peer :writer)]
183
+ (try
184
+ (try
185
+ (api/release (api/connect cfg) true)
186
+ (catch Exception _))
187
+ {:status 200
188
+ :body (async/<!! (apply datahike.writing/delete-database cfg (rest body)))}
189
+ (catch Exception e
190
+ {:status 500
191
+ :body {:msg (ex-message e)
192
+ :ex-data (ex-data e)}})))))
193
+ :operationId "delete-database"},
194
+ :swagger {:tags ["Internal"]}}]
195
+ ["/create-database-writer"
196
+ {:post {:parameters {:body [:sequential :any]},
197
+ :summary "Internal endpoint. DO NOT USE!"
198
+ :no-doc true
199
+ :handler (fn [{{:keys [body]} :parameters}]
200
+ (let [cfg (dissoc (first body) :remote-peer :writer)]
201
+ (try
202
+ {:status 200
203
+ :body (async/<!! (apply datahike.writing/create-database
204
+ cfg
205
+ (rest body)))}
206
+ (catch Exception e
207
+ {:status 500
208
+ :body {:msg (ex-message e)
209
+ :ex-data (ex-data e)}}))))
210
+ :operationId "create-database"},
211
+ :swagger {:tags ["Internal"]}}]
212
+ ["/transact!-writer"
213
+ {:post {:parameters {:body [:sequential :any]},
214
+ :summary "Internal endpoint. DO NOT USE!"
215
+ :no-doc true
216
+ :handler (fn [{{:keys [body]} :parameters}]
217
+ (binding [*connections* server-connections]
218
+ (try
219
+ (let [conn (api/connect (dissoc (first body) :remote-peer :writer)) ;; TODO maybe release?
220
+ res @(apply datahike.writer/transact! conn (rest body))]
221
+ {:status 200
222
+ :body res})
223
+ (catch Exception e
224
+ {:status 500
225
+ :body {:msg (ex-message e)
226
+ :ex-data (ex-data e)}}))))
227
+ :operationId "transact"},
228
+ :swagger {:tags ["Internal"]}}]])
229
+
230
+ (defn app [config route-opts server-connections]
231
+ (-> (ring/ring-handler
232
+ (ring/router
233
+ (concat
234
+ [["/swagger.json"
235
+ {:get {:no-doc true
236
+ :swagger {:info {:title "Datahike API"
237
+ :description "Transaction and query functions for Datahike.\n\nThe signatures match those of the Clojure API. All functions take their arguments passed as a vector/list in the POST request body."}}
238
+ :handler (swagger/create-swagger-handler)}}]]
239
+ (map (fn [route]
240
+ (let [method (if (:get (second route)) :get :post)]
241
+ (assoc-in route [1 method :middleware]
242
+ [(partial middleware/token-auth config)
243
+ (partial middleware/auth config)])))
244
+ (concat (create-routes config)
245
+ (internal-writer-routes server-connections)))) route-opts)
246
+ (ring/routes
247
+ (swagger-ui/create-swagger-ui-handler
248
+ {:path "/"
249
+ :config {:validatorUrl nil
250
+ :operationsSorter "alpha"}})
251
+ (ring/create-default-handler)))
252
+ (wrap-cors :access-control-allow-origin (or (:access-control-allow-origin config)
253
+ [#"http://localhost" #"http://localhost:8080"])
254
+ :access-control-allow-methods [:get :put :post :delete])))
255
+
256
+ (defn start-server [config]
257
+ (run-jetty (app config (default-route-opts muuntaja-with-opts) (atom {})) config))
258
+
259
+ (defn stop-server [^org.eclipse.jetty.server.Server server]
260
+ (.stop server))
261
+
262
+ (defn -main [& args]
263
+ (let [{:keys [level token] :as config} (edn/read-string (slurp (first args)))]
264
+ (when level (log/set-level! level))
265
+ (when (#{:trace :debug :info nil} level)
266
+ (println "Datahike HTTP Server" datahike-version "- https://datahike.io"))
267
+ (log/info "Config:" (if token (assoc config :token "REDACTED") config))
268
+ (start-server config)
269
+ (log/info "Server started.")))