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,217 @@
1
+ (ns datahike.test.model-test
2
+ "Model-based integration tests for Datahike.
3
+
4
+ This test compares a pure Clojure model of Datahike semantics
5
+ against the actual Datahike implementation. The model tracks
6
+ expected state, and we verify that the real system matches."
7
+ (:require [clojure.test :refer [deftest is testing]]
8
+ [clojure.test.check.clojure-test :refer [defspec]]
9
+ [clojure.test.check.generators :as gen]
10
+ [clojure.test.check.properties :as prop]
11
+ [datahike.api :as d]
12
+ [datahike.test.model.rng :as rng]
13
+ [datahike.test.model.core :as model]
14
+ [datahike.test.model.invariant :as inv])
15
+ (:import [java.util UUID]))
16
+
17
+ ;; =============================================================================
18
+ ;; Test Schema
19
+ ;; =============================================================================
20
+
21
+ (def test-schema
22
+ [{:db/ident :name
23
+ :db/valueType :db.type/string
24
+ :db/cardinality :db.cardinality/one
25
+ :db/index true}
26
+ {:db/ident :label
27
+ :db/valueType :db.type/string
28
+ :db/cardinality :db.cardinality/many
29
+ :db/index true}
30
+ {:db/ident :age
31
+ :db/valueType :db.type/long
32
+ :db/cardinality :db.cardinality/one
33
+ :db/index true}
34
+ {:db/ident :friend
35
+ :db/valueType :db.type/ref
36
+ :db/cardinality :db.cardinality/many}])
37
+
38
+ (def schema-map
39
+ (into {} (map (juxt :db/ident identity)) test-schema))
40
+
41
+ (def user-attrs #{:name :label :age :friend})
42
+
43
+ (def entity-range (vec (range 100 200)))
44
+
45
+ ;; =============================================================================
46
+ ;; Test Helpers
47
+ ;; =============================================================================
48
+
49
+ (defn create-test-db
50
+ "Create a fresh in-memory database with schema.
51
+ Returns map with :conn, :db, :cfg, and :tx-offset.
52
+
53
+ The tx-offset maps model tx-ids to actual tx-ids:
54
+ - Model starts with next-tx = 536870912 (first user tx would be recorded at 536870912)
55
+ - After schema, actual max-tx = 536870913 (schema tx)
56
+ - First user tx will be at 536870914
57
+ - So offset = 536870914 - 536870912 = 2
58
+
59
+ This means: model-tx-id + tx-offset = actual-tx-id
60
+
61
+ opts: optional map to override default config (e.g., {:attribute-refs? false})"
62
+ ([]
63
+ (create-test-db {}))
64
+ ([opts]
65
+ (let [cfg (merge {:store {:backend :memory :id (UUID/randomUUID)}
66
+ :keep-history? true
67
+ :schema-flexibility :write}
68
+ opts)]
69
+ (d/delete-database cfg)
70
+ (d/create-database cfg)
71
+ (let [conn (d/connect cfg)
72
+ _ (d/transact conn test-schema)
73
+ db-after-schema (d/db conn)
74
+ tx-offset (- (:max-tx db-after-schema) 536870911)]
75
+ {:conn conn :db (d/db conn) :cfg cfg :tx-offset tx-offset}))))
76
+
77
+ (defn apply-tx-to-conn
78
+ "Apply transaction to connection, returning updated db."
79
+ [conn tx-ops]
80
+ (try
81
+ (d/transact conn {:tx-data (mapv vec tx-ops)})
82
+ (d/db conn)
83
+ (catch Exception _
84
+ (d/db conn))))
85
+
86
+ ;; =============================================================================
87
+ ;; Properties (test.check)
88
+ ;; =============================================================================
89
+
90
+ (def seed-gen (gen/choose 0 9223372036854775807))
91
+
92
+ (defn index-integrity-prop
93
+ "Property that checks index integrity with given config options."
94
+ [opts]
95
+ (prop/for-all [seed seed-gen]
96
+ (let [rng (rng/create seed)
97
+ model-state (model/create-model schema-map)
98
+ {:keys [conn]} (create-test-db opts)]
99
+ (loop [i 0
100
+ model-state model-state]
101
+ (if (>= i 50)
102
+ true
103
+ (let [tx-ops (model/generate-transaction rng schema-map entity-range model-state 10)
104
+ model-state' (model/apply-tx model-state {:tx-data tx-ops})
105
+ db (apply-tx-to-conn conn tx-ops)
106
+ result (inv/check-all
107
+ (inv/all-index-invariants user-attrs)
108
+ model-state'
109
+ db)]
110
+ (if (:valid? result)
111
+ (recur (inc i) model-state')
112
+ false)))))))
113
+
114
+ (defn historical-consistency-prop
115
+ "Property that checks historical consistency with given config options."
116
+ [opts]
117
+ (prop/for-all [seed seed-gen]
118
+ (let [rng (rng/create seed)
119
+ model-state (model/create-model schema-map)
120
+ {:keys [conn tx-offset]} (create-test-db opts)]
121
+ (loop [i 0
122
+ model-state model-state]
123
+ (if (>= i 20)
124
+ true
125
+ (let [tx-ops (model/generate-transaction rng schema-map entity-range model-state 3)
126
+ model-state' (model/apply-tx model-state {:tx-data tx-ops})
127
+ db (apply-tx-to-conn conn tx-ops)
128
+ model-tx-ids (model/get-transaction-ids model-state')
129
+ actual-tx-ids (map #(+ % tx-offset) (take 3 model-tx-ids))
130
+ hist-result (inv/check-all
131
+ [(inv/historical-consistency
132
+ actual-tx-ids
133
+ tx-offset
134
+ user-attrs)]
135
+ model-state'
136
+ db)]
137
+ (if (:valid? hist-result)
138
+ (recur (inc i) model-state')
139
+ false)))))))
140
+
141
+ ;; With :attribute-refs? true (default)
142
+ (defspec index-integrity-with-attribute-refs 100
143
+ (index-integrity-prop {:attribute-refs? true}))
144
+
145
+ (defspec historical-with-attribute-refs 30
146
+ (historical-consistency-prop {:attribute-refs? true}))
147
+
148
+ ;; Without :attribute-refs?
149
+ (defspec index-integrity-without-attribute-refs 100
150
+ (index-integrity-prop {:attribute-refs? false}))
151
+
152
+ (defspec historical-without-attribute-refs 30
153
+ (historical-consistency-prop {:attribute-refs? false}))
154
+
155
+ ;; =============================================================================
156
+ ;; Unit Tests
157
+ ;; =============================================================================
158
+
159
+ (deftest test-model-accumulate-datom
160
+ (testing "cardinality one replaces"
161
+ (let [s1 (model/accumulate-datom schema-map #{} [100 :name "Alice" true])
162
+ s2 (model/accumulate-datom schema-map s1 [100 :name "Bob" true])]
163
+ (is (= #{[100 :name "Bob"]} s2))))
164
+
165
+ (testing "cardinality many accumulates"
166
+ (let [s1 (model/accumulate-datom schema-map #{} [100 :label "a" true])
167
+ s2 (model/accumulate-datom schema-map s1 [100 :label "b" true])]
168
+ (is (= #{[100 :label "a"] [100 :label "b"]} s2))))
169
+
170
+ (testing "retract removes"
171
+ (let [s1 (model/accumulate-datom schema-map #{} [100 :name "Alice" true])
172
+ s2 (model/accumulate-datom schema-map s1 [100 :name "Alice" false])]
173
+ (is (= #{} s2)))))
174
+
175
+ (deftest test-model-index-computation
176
+ (let [model-state (model/apply-txs
177
+ (model/create-model schema-map)
178
+ [{:tx-data [[:db/add 100 :name "Alice"]
179
+ [:db/add 100 :age 25]]}
180
+ {:tx-data [[:db/add 101 :name "Bob"]
181
+ [:db/add 101 :age 30]]}])]
182
+ (testing "EAVT sorted by [e a v]"
183
+ (let [eavt (model/compute-eavt model-state)]
184
+ (is (= [100 :age 25] (first eavt)))
185
+ (is (= [101 :name "Bob"] (last eavt)))))
186
+
187
+ (testing "AVET contains indexed attrs"
188
+ (let [avet (model/compute-avet model-state)]
189
+ (is (some #{[100 :age 25]} avet))
190
+ (is (some #{[100 :name "Alice"]} avet))))
191
+
192
+ (testing "AEVT sorted by [a e v]"
193
+ (let [aevt (model/compute-aevt model-state)]
194
+ (is (= :age (second (first aevt))))))))
195
+
196
+ (deftest test-rng-reproducibility
197
+ (testing "same seed produces same sequence"
198
+ (let [rng1 (rng/create 12345)
199
+ rng2 (rng/create 12345)
200
+ seq1 (repeatedly 10 #(rng/next-int rng1 100))
201
+ seq2 (repeatedly 10 #(rng/next-int rng2 100))]
202
+ (is (= seq1 seq2))))
203
+
204
+ (testing "different seeds produce different sequences"
205
+ (let [rng1 (rng/create 12345)
206
+ rng2 (rng/create 54321)
207
+ seq1 (repeatedly 10 #(rng/next-int rng1 100))
208
+ seq2 (repeatedly 10 #(rng/next-int rng2 100))]
209
+ (is (not= seq1 seq2))))
210
+
211
+ (testing "fork creates independent stream"
212
+ (let [rng (rng/create 12345)
213
+ child (rng/fork rng)
214
+ parent-val (rng/next-int rng 100)
215
+ child-val (rng/next-int child 100)]
216
+ (is (number? parent-val))
217
+ (is (number? child-val)))))
@@ -0,0 +1,262 @@
1
+ (ns datahike.test.nodejs-test
2
+ (:require [cljs.test :refer [deftest is async] :as t]
3
+ [datahike.api :as d]
4
+ [datahike.online-gc :as online-gc]
5
+ [konserve.core :as k]
6
+ [konserve.node-filestore] ;; Register :file backend for Node.js
7
+ [cljs.core.async :refer [go <!] :include-macros true]
8
+ [cljs.nodejs :as nodejs]))
9
+
10
+ (def fs (nodejs/require "fs"))
11
+ (def path (nodejs/require "path"))
12
+ (def os (nodejs/require "os"))
13
+
14
+ (defn tmp-dir []
15
+ (let [dir (path.join (os.tmpdir) (str "datahike-node-test-" (rand-int 100000)))]
16
+ dir))
17
+
18
+ (deftest roundtrip-test
19
+ (let [dir (tmp-dir)
20
+ store-id (random-uuid)
21
+ cfg {:store {:backend :file :path dir :id store-id}
22
+ :keep-history? true
23
+ :schema-flexibility :write}]
24
+ (async done
25
+ (go
26
+ (try
27
+ ;; Create database
28
+ (let [created (<! (d/create-database cfg))]
29
+ (is created "Database created"))
30
+
31
+ ;; Connect
32
+ (let [conn (d/connect cfg)]
33
+ (is conn "Connection established")
34
+
35
+ ;; Add schema
36
+ (let [schema-tx [{:db/ident :name
37
+ :db/valueType :db.type/string
38
+ :db/cardinality :db.cardinality/one}
39
+ {:db/ident :age
40
+ :db/valueType :db.type/long
41
+ :db/cardinality :db.cardinality/one}]]
42
+ (let [report (<! (d/transact! conn schema-tx))]
43
+ (is (:db-after report) "Schema added")))
44
+
45
+ ;; Transact data
46
+ (let [tx-report (<! (d/transact! conn [{:name "Alice" :age 30}
47
+ {:name "Bob" :age 25}]))]
48
+ (is (:db-after tx-report) "Data transacted")
49
+ (is (pos? (count (:tx-data tx-report))) "Datoms were added"))
50
+
51
+ ;; Verify data via datoms API
52
+ (let [all-datoms (vec (d/datoms @conn :eavt))
53
+ name-datoms (filter #(= :name (:a %)) all-datoms)
54
+ age-datoms (filter #(= :age (:a %)) all-datoms)]
55
+ (is (= 2 (count name-datoms)) "Found 2 name datoms")
56
+ (is (= 2 (count age-datoms)) "Found 2 age datoms"))
57
+
58
+ ;; Pull API
59
+ (let [entities (d/q '[:find ?e :where [?e :name _]] @conn)
60
+ e1 (ffirst entities)]
61
+ (when e1
62
+ (let [pulled (d/pull @conn [:name :age] e1)]
63
+ (is (:name pulled) "Pull retrieved name")
64
+ (is (:age pulled) "Pull retrieved age"))))
65
+
66
+ ;; Query API
67
+ (let [q1 (d/q '[:find ?e :where [?e :name _]] @conn)]
68
+ (is (= 2 (count q1)) "Single pattern: found 2 entities")
69
+ (is (every? #(number? (first %)) q1) "Single pattern: entity IDs are numbers"))
70
+
71
+ (let [q2 (d/q '[:find ?v :where [_ :name ?v]] @conn)
72
+ names (set (map first q2))]
73
+ (is (= 2 (count q2)) "Value query: found 2 names")
74
+ (is (contains? names "Alice") "Value query: found Alice")
75
+ (is (contains? names "Bob") "Value query: found Bob"))
76
+
77
+ (let [q3 (d/q '[:find ?e ?name ?age
78
+ :where
79
+ [?e :name ?name]
80
+ [?e :age ?age]] @conn)
81
+ results (into {} (map (fn [[e name age]] [name {:e e :age age}]) q3))]
82
+ (is (= 2 (count q3)) "Join query: found 2 entity/name/age tuples")
83
+ (is (number? (get-in results ["Alice" :e])) "Join query: Alice has valid entity ID")
84
+ (is (= 30 (get-in results ["Alice" :age])) "Join query: Alice is 30")
85
+ (is (number? (get-in results ["Bob" :e])) "Join query: Bob has valid entity ID")
86
+ (is (= 25 (get-in results ["Bob" :age])) "Join query: Bob is 25"))
87
+
88
+ ;; Entity API
89
+ (let [entities (d/q '[:find ?e :where [?e :name _]] @conn)
90
+ e1 (ffirst entities)]
91
+ (when e1
92
+ (let [entity (d/entity @conn e1)]
93
+ (is (:name entity) "Entity has name")
94
+ (is (:age entity) "Entity has age"))))
95
+
96
+ ;; Update for history
97
+ (let [entities (d/q '[:find ?e :where [?e :name _]] @conn)
98
+ e1 (ffirst entities)]
99
+ (when e1
100
+ (<! (d/transact! conn [[:db/add e1 :age 31]]))))
101
+
102
+ ;; Test as-of DB
103
+ (let [entities (d/q '[:find ?e :where [?e :name _]] @conn)
104
+ e1 (ffirst entities)
105
+ before-update-tx (:max-tx @conn)]
106
+ (when e1
107
+ (<! (d/transact! conn [[:db/add e1 :age 99]])))
108
+ (let [current-age-after (when e1 (:age (d/entity @conn e1)))]
109
+ (is (= 99 current-age-after) "Current DB shows updated age"))
110
+ (let [as-of-db (d/as-of @conn before-update-tx)
111
+ as-of-age (when e1 (:age (d/entity as-of-db e1)))]
112
+ (is (= 31 as-of-age) "as-of DB shows old age value")))
113
+
114
+ ;; History DB
115
+ (let [hist-db (d/history @conn)
116
+ hist-datoms (vec (filter #(= :age (:a %)) (d/datoms hist-db :eavt)))]
117
+ (is (>= (count hist-datoms) 4) "History contains multiple age values"))
118
+
119
+ (d/release conn))
120
+
121
+ ;; Reconnect to verify persistence
122
+ (let [conn2 (d/connect cfg)]
123
+ (is conn2 "Reconnected successfully")
124
+
125
+ (let [all-datoms (vec (d/datoms @conn2 :eavt))
126
+ name-datoms (filter #(= :name (:a %)) all-datoms)]
127
+ (is (= 2 (count name-datoms)) "Data persisted after reconnect"))
128
+
129
+ ;; Test different index access
130
+ (let [aevt-datoms (take 5 (d/datoms @conn2 :aevt))
131
+ avet-datoms (take 5 (d/datoms @conn2 :avet))]
132
+ (is (seq aevt-datoms) "Got datoms from AEVT index")
133
+ (is (seq avet-datoms) "Got datoms from AVET index"))
134
+
135
+ (d/release conn2))
136
+
137
+ ;; Delete database
138
+ (let [deleted (<! (d/delete-database cfg))]
139
+ (is (nil? deleted) "Database deleted"))
140
+
141
+ (is (not (fs.existsSync dir)) "Directory removed")
142
+
143
+ (catch js/Error e
144
+ (is false (str "Error: " (.-message e))))
145
+ (finally
146
+ (done)))))))
147
+
148
+ (deftest online-gc-basic-test
149
+ (async done
150
+ (go
151
+ (try
152
+ (let [dir (tmp-dir)
153
+ cfg-no-gc {:store {:backend :file :path dir :id (random-uuid)}
154
+ :online-gc {:enabled? false}
155
+ :crypto-hash? false
156
+ :keep-history? false
157
+ :schema-flexibility :write}]
158
+
159
+ ;; Create database without online GC initially
160
+ (<! (d/create-database cfg-no-gc))
161
+ (let [conn (d/connect cfg-no-gc)]
162
+
163
+ ;; Add schema
164
+ (<! (d/transact! conn [{:db/ident :name
165
+ :db/valueType :db.type/string
166
+ :db/cardinality :db.cardinality/one}]))
167
+
168
+ ;; Add data to create freed addresses
169
+ (<! (d/transact! conn [{:name "Alice"}]))
170
+ (<! (d/transact! conn [{:name "Bob"}]))
171
+
172
+ ;; Get freed count (should have freed addresses from schema + data txs)
173
+ (let [freed-atom (-> @conn :store :storage :freed-addresses)
174
+ initial-freed (count @freed-atom)]
175
+ (is (> initial-freed 0) "Should have freed addresses with GC disabled"))
176
+
177
+ ;; Run online GC explicitly
178
+ (let [gc-result (<! (online-gc/online-gc! (:store @conn)
179
+ {:enabled? true
180
+ :grace-period-ms 0
181
+ :sync? false}))]
182
+ (is (number? gc-result) "GC returned a count"))
183
+
184
+ ;; Check that freed addresses were cleared
185
+ (let [freed-atom (-> @conn :store :storage :freed-addresses)
186
+ final-freed (count @freed-atom)]
187
+ (is (= 0 final-freed) "Freed addresses should be cleared after GC"))
188
+
189
+ (d/release conn))
190
+
191
+ ;; Cleanup
192
+ (<! (d/delete-database cfg-no-gc)))
193
+
194
+ (catch js/Error e
195
+ (is false (str "Error in online-gc-basic-test: " (.-message e))))
196
+ (finally
197
+ (done))))))
198
+
199
+ (deftest online-gc-multi-branch-safety-test
200
+ (async done
201
+ (go
202
+ (try
203
+ (let [dir (tmp-dir)
204
+ cfg-no-gc {:store {:backend :file :path dir :id (random-uuid)}
205
+ :online-gc {:enabled? false}
206
+ :crypto-hash? false
207
+ :keep-history? false
208
+ :schema-flexibility :write}]
209
+
210
+ ;; Create database without online GC initially
211
+ (<! (d/create-database cfg-no-gc))
212
+ (let [conn (d/connect cfg-no-gc)]
213
+
214
+ ;; Add schema and data
215
+ (<! (d/transact! conn [{:db/ident :name
216
+ :db/valueType :db.type/string
217
+ :db/cardinality :db.cardinality/one}]))
218
+ (<! (d/transact! conn [{:name "Alice"}]))
219
+
220
+ ;; Simulate multi-branch scenario by adding a second branch
221
+ (<! (k/assoc (:store @conn) :branches #{:db :branch-a}))
222
+
223
+ ;; Verify multi-branch state
224
+ (let [branches (<! (k/get (:store @conn) :branches))]
225
+ (is (= 2 (count branches)) "Should have two branches"))
226
+
227
+ ;; Add more data to generate freed addresses
228
+ (<! (d/transact! conn [{:name "Bob"}]))
229
+
230
+ (let [freed-before (count @(-> @conn :store :storage :freed-addresses))]
231
+ (is (> freed-before 0) "Should have freed addresses with GC disabled"))
232
+
233
+ ;; Run online GC - should detect multi-branch and SKIP entirely
234
+ (let [gc-result (<! (online-gc/online-gc! (:store @conn)
235
+ {:enabled? true
236
+ :grace-period-ms 0
237
+ :sync? false}))]
238
+ (is (= 0 gc-result) "Multi-branch GC should be skipped (return 0)"))
239
+
240
+ ;; Freed addresses should remain (not deleted, re-marked for offline GC)
241
+ (let [freed-after (count @(-> @conn :store :storage :freed-addresses))]
242
+ (is (> freed-after 0) "Multi-branch GC should leave freed addresses for offline GC"))
243
+
244
+ ;; Verify database is still functional
245
+ (let [result (d/q '[:find ?e ?n :where [?e :name ?n]] @conn)]
246
+ (is (= 2 (count result)) "Both Alice and Bob should still exist"))
247
+
248
+ (d/release conn))
249
+
250
+ ;; Cleanup
251
+ (<! (d/delete-database cfg-no-gc)))
252
+
253
+ (catch js/Error e
254
+ (is false (str "Error in online-gc-multi-branch-safety-test: " (.-message e))))
255
+ (finally
256
+ (done)
257
+ (js/process.nextTick
258
+ (fn []
259
+ (.exit js/process 0))))))))
260
+
261
+ (defn -main []
262
+ (t/run-tests 'datahike.test.nodejs-test))