TYPESCRIPT · NODE.JS
Drivine
Built on NestJS. Decorated classes, a typed query DSL whose references come from the classes themselves, and injection with @InjectGraphObjectManager.
npm install @liberation-data/drivine
Graph database client for TypeScript & the JVM
Community project by Liberation Data.
npm install @liberation-data/drivineorg.drivine:drivine4jTHE DIVINE DRIVER
Drivine maps graphs to objects by composition. Declare the view a use case needs, made of node fragments and the relationships between them, and Drivine writes the statement. When you would rather write the Cypher yourself, the persistence manager is still there.
TYPESCRIPT · NODE.JS
Built on NestJS. Decorated classes, a typed query DSL whose references come from the classes themselves, and injection with @InjectGraphObjectManager.
npm install @liberation-data/drivineKOTLIN · JAVA · SPRING BOOT
The graph client for Embabel. Annotated models in Kotlin or Java, a DSL generated by KSP, and Spring Boot auto-configuration with test containers built in.
implementation("org.drivine:drivine4j:<version>")COMPOSITION OVER INHERITANCE
A person can play many roles. Rather than one model that serves every use case, declare a view for each, and load it in a single statement.
@NodeFragment maps one node.@GraphView composes fragments and relationships from a @Root.@NodeFragment({ labels: ['Person'] }) export class Person { @NodeId() uuid: string; name: string; bio?: string; } @GraphView() export class PersonCareer { @Root() person: Person; @GraphRelationship({ type: 'WORKS_FOR', target: () => WorkHistory }) employmentHistory: WorkHistory[] = []; } // Everyone whose bio mentions leading const leads = await gom.loadAll(PersonCareer, (q) => ({ where: q.person.bio.contains('Lead') }));
@NodeFragment(labels = ["Person"]) data class Person( @NodeId val uuid: String, val name: String, val bio: String? ) @GraphView data class PersonCareer( @Root val person: Person, @GraphRelationship(type = "WORKS_FOR") val employmentHistory: List<WorkHistory> ) // Everyone whose bio mentions leading val leads = graphObjectManager.loadAll<PersonCareer> { where { person.bio contains "Lead" } }
IN BOTH LIBRARIES
Filters, ordering, keyset paging and index advice, accepted by loads, counts, deletes, patches and grouped reads.
save and saveAll with null policies and replace modes, update, create, patch, cascading deletes, relate and unrelate.
@NodeStamp refuses a save when the node changed since it was loaded, in a stamp format Node and the JVM share.
loadNearest and loadMatching on views and fragments, with one score scale on every engine.
Range, uniqueness, vector and full-text indexes declared on your classes, enforced at startup, with drift detection.
A container for each datasource in CI. While you iterate, USE_LOCAL_NEO4J=true points tests at your own instance.
PERFORMANCE
Drivine is built to be fast. There's no lazy loading, no session cache to keep warm and no query for each relationship. A view, with everything related to it, loads in one statement, and a save is one statement too.
Graph Bench uses drivine4j to compare Neo4j, FalkorDB and Oracle on a graph of 1.1 million nodes and 4.5 million relationships, with the same Kotlin on each.
ENGINES
Dashed: through the persistence manager.
Configure a connection, inject the object manager, load your first view.