Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3c95095
Modularize EACL backends and add DataScript adapter
theronic Mar 12, 2026
c6b94ad
Fix DataScript schema pagination and validation metadata
theronic Mar 15, 2026
64345b4
Implement paginated indexed read-relationships
theronic Mar 15, 2026
274aca4
speed up `eacl/read-relationships` with v7 tuples
theronic Mar 15, 2026
0a5f244
Merge branch 'fix/audit-root-causes' into fix/audit-root-causes-datas…
theronic Jul 6, 2026
e47f403
fix: port audit root-cause fixes into modular backends (eacl, eacl-da…
theronic Jul 6, 2026
1a508e0
Merge pull request #73 from theronic/fix/audit-root-causes-datascript
theronic Jul 6, 2026
6f8611a
fix #74: only write-schema! busts the path cache — revert derived digest
theronic Jul 14, 2026
9c29176
fix #74 (modules): schema-version stamp replaces derived digest in ea…
theronic Jul 14, 2026
1e18e54
Merge pull request #75 from theronic/fix/issue-74-schema-version
theronic Jul 14, 2026
0f8e6a4
Port EACL v7.3 to the DataScript modules
theronic Jul 28, 2026
457b137
Return no cursor for empty first pages
theronic Jul 28, 2026
bd00c83
feat(datahike): scaffold the eacl-datahike backend module
whilo Jul 30, 2026
dad397b
feat(datahike): a datahike backend, held to the shared contract
whilo Jul 30, 2026
adf9cbc
fix(datahike): pin the module's own datahike dep to the release
whilo Jul 30, 2026
4f53878
Merge remote-tracking branch 'origin/eacl/datascript' into codex/pr81…
theronic Aug 1, 2026
a40df80
fix(datahike): make the backend independently releasable
theronic Aug 1, 2026
8e7e464
Merge pull request #81 from whilo/feat/datahike-backend
theronic Aug 1, 2026
5b420db
fix(datahike): preserve exhausted cursor frontiers
theronic Aug 1, 2026
e8cee0d
test(datahike): cover exhausted cursor resumption
theronic Aug 1, 2026
d39bcc5
Merge pull request #90 from theronic/codex/fix-datahike-exhausted-cur…
theronic Aug 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.clj-kondo/
.idea/
.cpcache/
/spicedb
/spicedb
target/
.nrepl-port
59 changes: 55 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

EACL is a _situated_ [ReBAC](https://en.wikipedia.org/wiki/Relationship-based_access_control) authorization library based on [SpiceDB](https://authzed.com/spicedb), built in Clojure and backed by Datomic.

_Situated_ here means that your permission data lives _next to_ your application data in Datomic, which has some benefits:
_Situated_ here means that your permission data lives _next to_ your application data in the backend you already control, which has some benefits:
1. Avoids a network hop. To leverage SpiceDB's consistency semantics, you need to hit your DB (or cache) to retrieve the latest stored ZedToken anyway, so you might as well query the DB directly, which is what EACL does.
2. One less external dependency to deploy & sync relationships.
3. Fully consistent queries – an external authz system necessitates eventual consistency.
Expand Down Expand Up @@ -59,6 +59,7 @@ Public `eacl3_` cursors are string-safe AES-GCM envelopes. Authentication is req
> v7.3 is the current development version of EACL. It retains the v7.2 pagination API and recursive traversal engine, and adds direction-scoped cursor frontiers for deep acyclic lookup pages. Releases are not tagged yet, so pin the Git SHA.
> Upgrading from v6? The relationship storage model changed — follow the [v6 → v7 migration guide](docs/migration-v6-to-v7.md).


## ReBAC: Relationship-based Access Control

In a [ReBAC](https://en.wikipedia.org/wiki/Relationship-based_access_control) system like EACL, objects (_Subjects_ & _Resources_) are related via _Relationships_.
Expand Down Expand Up @@ -112,7 +113,7 @@ In SpiceDB schema DSL, `+` means union (OR-logic). EACL does not support negatio

## EACL API

The `IAuthorization` protocol in [src/eacl/core.clj](src/eacl/core.clj) defines an idiomatic Clojure interface that maps to and extends the [SpiceDB gRPC API](https://buf.build/authzed/api/docs/main:authzed.api.v1):
The `IAuthorization` protocol in [modules/eacl/src/eacl/core.cljc](modules/eacl/src/eacl/core.cljc) defines an idiomatic Clojure interface that maps to and extends the [SpiceDB gRPC API](https://buf.build/authzed/api/docs/main:authzed.api.v1):

### Queries

Expand Down Expand Up @@ -201,11 +202,11 @@ To go back from page2, pass its `:start-cursor` as `:before` with `:last`:

Forward and backward pages return results in the same order for the query. Acyclic lookup uses Datomic eid order; recursive lookup uses deterministic traversal order. Backward pagination returns the previous window; it does not reverse the result order. Bare `:last` without `:before` is not supported for recursive lookup because it requires traversing the full closure.

## Quickstart
## Datomic Quickstart

The following example is contained in [eacl-example](https://github.com/theronic/eacl-example).

Add the EACL dependency to your `deps.edn` file:
Add the Datomic adapter dependency to your `deps.edn` file:

```clojure
{:deps {theronic/eacl {:git/url "git@github.com:theronic/eacl.git"
Expand Down Expand Up @@ -298,6 +299,56 @@ Add the EACL dependency to your `deps.edn` file:
; :has-previous-page? false}}
```

## DataScript Quickstart

For server-side or browser demos, use the DataScript adapter:

```clojure
(ns my-eacl-datascript-demo
(:require [datascript.core :as ds]
[eacl.core :as eacl]
[eacl.datascript.core :as eacl.datascript]))

(def conn (eacl.datascript/create-conn))
(def acl (eacl.datascript/make-client conn {}))

(ds/transact! conn
[{:db/id -1 :eacl/id "user-1"}
{:db/id -2 :eacl/id "account-1"}])

(eacl/write-schema! acl
"definition user {}

definition account {
relation owner: user
permission admin = owner
}")

(eacl/create-relationship! acl
(eacl/spice-object :user "user-1")
:owner
(eacl/spice-object :account "account-1"))

(eacl/can? acl
(eacl/spice-object :user "user-1")
:admin
(eacl/spice-object :account "account-1"))
; => true
```

Run the shared CLJS contract suite with:

```clojure
clojure -M:datascript-cljs-test
```

## Migration Notes

- Existing Datomic `require` forms remain valid: `eacl.datomic.core`, `eacl.datomic.impl`, and `eacl.datomic.schema`.
- `count-subjects` is now part of the public `IAuthorization` protocol and is implemented by both adapters.
- `Relation` and `Permission` now live in the backend-neutral schema model, with Datomic compatibility re-exports still available.
- The repository root is a development workspace now. Consumers should depend on the module they actually need.

## EACL Schema

EACL uses the SpiceDB schema DSL to define your authorization model. Use `eacl/write-schema!` to parse, validate, and transact your schema:
Expand Down
76 changes: 57 additions & 19 deletions deps.edn
Original file line number Diff line number Diff line change
@@ -1,33 +1,71 @@
{:deps {org.clojure/clojure {:mvn/version "1.11.4"}
{:paths ["modules/eacl/src"
"modules/eacl-datomic/src"
"modules/eacl-datascript/src"
"modules/eacl-datahike/src"
"resources"]
:deps {org.clojure/clojure {:mvn/version "1.11.4"}

; Specter for coercing objects in deep structures
com.rpl/specter {:mvn/version "1.1.4"}

; Malli for specs
metosin/malli {:mvn/version "0.19.1"}

;; Datomic
com.datomic/peer {:mvn/version "1.0.7622"}
datascript/datascript {:mvn/version "1.7.8"}
;; 0.8.1759 is the first release carrying replikativ/datahike#921
;; (composite tuples derived + validated under :attribute-refs?)
org.replikativ/datahike {:mvn/version "0.8.1759"}

;; Caching
;; Caching
org.clojure/core.cache {:mvn/version "1.1.234"}

;; Parsing
instaparse/instaparse {:mvn/version "1.5.0"}

;; Logging
org.clojure/tools.logging {:mvn/version "1.3.0"}
ch.qos.logback/logback-classic {:mvn/version "1.5.12"}}
:paths ["src" "resources"]
:aliases {:dev {:extra-paths ["test"]}
:build {:extra-paths ["src-build"]
:ns-default build
:extra-deps {io.github.clojure/tools.build {:mvn/version "0.10.8"}}}
:test {:extra-paths ["test"]
org.clojure/tools.logging {:mvn/version "1.3.0"}}
:aliases {:dev {:extra-paths ["modules/eacl/test"
"modules/eacl-datomic/test"
"modules/eacl-datascript/test"
"modules/eacl-datahike/test"]}
:build-eacl {:extra-paths ["modules/eacl"]
:ns-default build
:extra-deps {io.github.clojure/tools.build {:mvn/version "0.10.8"}}}
:build-eacl-datomic {:extra-paths ["modules/eacl-datomic"]
:ns-default build
:extra-deps {io.github.clojure/tools.build {:mvn/version "0.10.8"}}}
:build-eacl-datahike {:extra-paths ["modules/eacl-datahike"]
:ns-default build
:extra-deps {io.github.clojure/tools.build {:mvn/version "0.10.8"}}}
:build-eacl-datascript {:extra-paths ["modules/eacl-datascript"]
:ns-default build
:extra-deps {io.github.clojure/tools.build {:mvn/version "0.10.8"}}}
:cljs-test {:extra-paths ["modules/eacl/test"
"modules/eacl-datascript/test"]
:extra-deps {org.clojure/clojurescript {:mvn/version "1.12.42"}}}
:datascript-cljs-test {:extra-paths ["modules/eacl/test"
"modules/eacl-datascript/test"]
:extra-deps {org.clojure/clojurescript {:mvn/version "1.12.42"}}
:main-opts ["-m" "cljs.main"
"-re" "node"
"-m" "eacl.datascript.cljs-test-runner"]}
:test {:extra-paths ["modules/eacl/test"
"modules/eacl-datomic/test"
"modules/eacl-datascript/test"
"modules/eacl-datahike/test"]
:extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}
criterium/criterium {:mvn/version "0.4.6"}}
:main-opts ["-m" "cognitect.test-runner"]
:exec-fn cognitect.test-runner.api/test}
:nrepl {:extra-paths ["test"]
:main-opts ["-m" "cognitect.test-runner"
"-d" "modules/eacl/test"
"-d" "modules/eacl-datomic/test"
"-d" "modules/eacl-datascript/test"
"-d" "modules/eacl-datahike/test"]
:exec-fn cognitect.test-runner.api/test
;; scan module test dirs only - the legacy top-level test/ tree
;; is retained for history but is not on this branch's classpath.
:exec-args {:dirs ["modules/eacl/test"
"modules/eacl-datomic/test"
"modules/eacl-datascript/test"
"modules/eacl-datahike/test"]}}
:nrepl {:extra-paths ["modules/eacl/test"
"modules/eacl-datomic/test"
"modules/eacl-datascript/test"
"modules/eacl-datahike/test"]
:extra-deps {nrepl/nrepl {:mvn/version "1.3.1"}}
:main-opts ["-m" "nrepl.cmdline"]}}}
Loading
Loading