{"id":28105043,"url":"https://github.com/appsflyer/aerospike-clj","last_synced_at":"2025-10-04T21:33:14.841Z","repository":{"id":47607632,"uuid":"164300110","full_name":"AppsFlyer/aerospike-clj","owner":"AppsFlyer","description":"Clojure client for the Aerospike database.","archived":false,"fork":false,"pushed_at":"2025-08-20T10:50:05.000Z","size":381,"stargazers_count":29,"open_issues_count":2,"forks_count":22,"subscribers_count":13,"default_branch":"master","last_synced_at":"2025-09-16T15:01:09.788Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AppsFlyer.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-01-06T11:20:31.000Z","updated_at":"2025-08-20T10:50:09.000Z","dependencies_parsed_at":"2024-01-08T18:03:38.462Z","dependency_job_id":"8da9391d-b903-480a-8e72-e779e7dfc5ff","html_url":"https://github.com/AppsFlyer/aerospike-clj","commit_stats":{"total_commits":212,"total_committers":19,"mean_commits":"11.157894736842104","dds":0.5754716981132075,"last_synced_commit":"c601c8be59f4fe81a21d45b77fc6fd265c1e7e9f"},"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"purl":"pkg:github/AppsFlyer/aerospike-clj","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AppsFlyer%2Faerospike-clj","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AppsFlyer%2Faerospike-clj/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AppsFlyer%2Faerospike-clj/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AppsFlyer%2Faerospike-clj/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AppsFlyer","download_url":"https://codeload.github.com/AppsFlyer/aerospike-clj/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AppsFlyer%2Faerospike-clj/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278379365,"owners_count":25977214,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-04T02:00:05.491Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-13T21:20:13.584Z","updated_at":"2025-10-04T21:33:14.836Z","avatar_url":"https://github.com/AppsFlyer.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Aerospike-clj\n\nAn opinionated Clojure library wrapping Aerospike Java Client.\n\n[![Clojars Project](https://img.shields.io/clojars/v/com.appsflyer/aerospike-clj.svg)](https://clojars.org/com.appsflyer/aerospike-clj)\n\n[![Build Status](https://img.shields.io/github/workflow/status/AppsFlyer/aerospike-clj/Push%20CI%20-%20master?event=push\u0026branch=master\u0026label=build%20%26%20test)](https://github.com/AppsFlyer/aerospike-clj/actions)\n# Docs:\n[Generated docs](https://appsflyer.github.io/aerospike-clj/)\n\n## Tutorial\n[here.](https://appsflyer.github.io/aerospike-clj/tutorial.html)\n## More advanced docs\n* [Advanced asynchronous hooks.](https://appsflyer.github.io/aerospike-clj/advanced-async-hooks.html)\n\n# Requirements\n- Java 8\n- Clojure 1.8\n- Aerospike server version \u003e= `4.9.0`\n- Clojure version \u003e= `1.11.0`\n\n# Features\n- Converts Java client's callback model into Java(8) `CompletableFuture` based API.\n- Expose passing functional (asynchronous) transcoders over payloads (both put/get).\n- Health-check utility.\n- Functions return Clojure records.\n\n# Maturity\n- Feature completeness: ~~mostly~~ near complete.\n- Stability: production ready. Actively and widely used in production.\n\n# Opinionated\n- Non blocking only: Expose only the non-blocking API. Block with `deref` if you like.\n- Futures instead of callbacks. Futures (and functional chaining) are more composable and less cluttered.\nIf synchronous behaviour is still desired, the calling code can still `deref` (`@`) the returned future object.\nFor a more sophisticated coordination, a variety of control mechanisms can be used by directly using Java's\n`CompletableFuture` API or the more Clojure friendly [promesa](https://github.com/funcool/promesa) (which is also used internally),\nor via the library using [transcoders](https://appsflyer.github.io/aerospike-clj/index.html) or\n[hooks](https://appsflyer.github.io/aerospike-clj/advanced-async-hooks.html).\n- Follows the method names of the underlying Java APIs.\n- TTLs should be explicit, and developers should think about them. Forces passing a TTL and not use the cluster default\n(This can be still achieved by passing the [special values](https://www.aerospike.com/apidocs/java/com/aerospike/client/policy/WritePolicy.html#expiration) -2,-1 or 0).\n- Minimal dependencies.\n- Single client per Aerospike namespace. Namespaces in Aerospike usually indicate different cluster configurations.\nIn order to reduce overhead for clusters with more than a single namespace create 2 client instances and share an event\nloop between them.\n\n## Usage\n\n```clojure\nuser=\u003e (require '[aerospike-clj.client :as aero])\nnil\nuser=\u003e (def c (aero/init-simple-aerospike-client\n  #_=\u003e          [\"aerospike-001.com\", \"aerospik-002.com\"] \"my-ns\" {:enable-logging true}))\n```\n\nIt is possible to inject additional asynchronous user-defined behaviour. To do that add an implementation of  the\n`ClientEvents` protocol during client initialization or per operation.  \nSome useful info is passed in-order to support metering and to read client configuration. `op-start-time` is\n`(System/nanoTime)`. \nsee more [here](https://appsflyer.github.io/aerospike-clj/advanced-async-hooks.html).\n\n```clojure\n(let [c (aero/init-simple-aerospike-client\n          [\"localhost\"]\n          \"test\"\n          {:client-events (reify ClientEvents\n                            (on-success [_ op-name op-result index op-start-time]\n                                (println op-name \"success!\")))\n                            (on-failure [_  op-name op-ex index op-start-time]\n                                (println \"oh-no\" op-name \"failed on index\" index)))})]\n\n  (get-single c \"index\" \"set-name\"))\n; for better performance, a `deftype` might be preferred over `reify`, if possible.\n```\n\n### Query/Put\nFor demo purposes we will use a docker based local DB:\n```shell\n$ sudo docker run -d --name aerospike -p 3000:3000 -p 3001:3001 -p 3002:3002 -p 3003:3003 aerospike\n```\nAnd connect to it:\n```clojure\nuser=\u003e (def c (aero/init-simple-aerospike-client [\"localhost\"] \"test\"))\n#'user/db\n```\n\n```clojure\nuser=\u003e (require '[promesa.core :as p])\nnil\nuser=\u003e (aero/put c \"index\" \"set-name\" 42 1000)\n#object[java.util.concurrent.CompletableFuture 0x6264b083 \"pending\"]\nuser=\u003e (def f (aero/get-single c \"index\" \"set-name\"))\n#'user/f\nuser=\u003e (p/chain (aero/get-single c \"index\" \"set-name\")\n  #_=\u003e          :ttl\n  #_=\u003e          aero/expiry-unix\n  #_=\u003e          #(java.time.Instant/ofEpochSecond %)\n  #_=\u003e          str\n  #_=\u003e          println)\n2020-08-13T09:52:49Z\n#object[java.util.concurrent.CompletableFuture 0x654830f5 \"pending\"]\n```\nWe actually get back a record with the payload, the DB generation and the TTL (in an Aerospike style EPOCH format).\n```clojure\nuser=\u003e @(aero/get-single c \"index\" \"set-name\")\n#aerospike_clj.client.AerospikeRecord{:payload 42, :gen 1, :ttl 285167713}\n```\n\n#### Unix EPOCH TTL\nAerospike returns a TTL on the queried records that is epoch style, but with a different \"beginning of time\" which is \"2010-01-01T00:00:00Z\".\nCall `expiry-unix` with the returned TTL to get a TTL relative to the UNIX epoch.\n\n## Testing\n### Unit tests\nExecuted via running `lein test`.\n\n### Integration tests\nTesting is performed against a local Aerospike docker container.\n\n#### Mocking in application unit tests\nFor unit tests purposes you can use a mock client that implements the client protocols: `MockClient`.\n\nUsage:\n\n```clojure\n(ns com-example.app \n  (:require [clojure.test :refer [deftest use-fixtures]]\n            [aerospike-clj.protocols :as pt]\n            [aerospike-clj.mock-client :as mock])\n  (:import [aerospike_clj.client SimpleAerospikeClient]))\n\n(def ^:dynamic ^SimpleAerospikeClient client nil)\n\n(defn- bind-client-to-mock [test-fn]\n  (binding [client (mock/create-instance)]\n    (test-fn)))\n\n(use-fixtures :each bind-client-to-mock)\n\n(deftest ...) ;; define your application unit tests as usual\n```\n\nThe sample code executes on every test run. It initializes the mock with a proper type hint\nso you can just invoke all client protocol methods on it.\n\nNote: If the production client is initiated using a state management framework,\nyou would also need to stop and restart the state on each test run.\n\n\n## Contributing\nPRs are welcome with these rules:\n* A PR should increment the project's version in [`project.clj`](project.clj) according\nto Semantic Versioning.\n* A PR should have its above version set to `SNAPSHOT`, e.g. `1.0.2-SNAPSHOT`.\nOnce it will be merged into `master` this suffix would be trimmed before release.\n* All PRs would be linted and tested. Passing lint and tests is a reuirement for\nmaintainers to review the PR.\n\n## License\n\nDistributed under the Apache 2.0 License - found [here](https://github.com/AppsFlyer/aerospike-clj/blob/master/LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappsflyer%2Faerospike-clj","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fappsflyer%2Faerospike-clj","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fappsflyer%2Faerospike-clj/lists"}