Decorator based OGM for Neo4j.
Currently in beta version with several steps left before v1 release
- some additional features
- detailed documentation
- bug fixes
- quality implovement
npm install @seihmd/spinel
// Define Node entity
@NodeEntity('User')
class User {
@Primary()
private id: string; // Entity must have one primary property
@Property()
private name: string;
constructor(id: string, name: string) {
this.id = id;
this.name = name;
}
}
// Define Relationship entity
@RelationshipEntity('FOLLOWS')
class Follows {
@Primary()
private id: string;
constructor(id: string) {
this.id = id;
}
}
graph TD
A(follower) -->|follows| B(user)
// Define Node-Relationship-Node graph
@Graph('user<-follows-follower')
class UserAndFollower {
@GraphNode()
private user: User;
@GraphRelationship()
private follows: Follows;
@GraphNode()
private follower: User;
constructor(user: User, follows: Follows, follower: User) {
this.user = user;
this.follows = follows;
this.follower = follower;
}
}
graph TD
A(follower) -->|:FOLLOWS| B(user)
C(follower) -->|:FOLLOWS| B(user)
@Graph('user')
class UserAndFollowers {
@GraphNode()
private user: User;
@GraphBranch(User, 'user<-[:FOLLOWS]-.')
private followers: User[];
constructor(user: User, followers: User[]) {
this.user = user;
this.followers = followers;
}
}
graph TD
A(:User) -->|:FOLLOWS| B(UserAndFollowers.user)
C(:User) -->|:FOLLOWS| B(UserAndFollowers.user)
A(:User) -->|:FOLLOWS| D(FollowedUser.user)
C(:User) -->|:FOLLOWS| D(FollowedUser.user)
@GraphFragment('-[:FOLLOWS]->user')
class FollowedUser {
@GraphNode()
private user: User;
constructor(user: User) {
this.user = user;
}
}
@Graph('user')
class UserAndFollowers {
@GraphNode()
private user: User;
@GraphBranch(User, 'user<-[:FOLLOWS]-(:User)')
private usersFollowedBySame: FollowedUser[];
constructor(user: User, usersFollowedBySame: FollowedUser[]) {
this.user = user;
this.usersFollowedBySame = usersFollowedBySame;
}
}
Run configure() at the application entry point.
import { configure } from '@seihmd/spinel';
configure({
entities: [User, Follows]
});
By default, Neo4j host, user, and password are read from environment variables:
SPINEL_HOST
SPINEL_USER
SPINEL_PASSWORD
Alternatively, the values can be set explicitly.
configure({
host: 'neo4j://localhost',
user: 'neo4j',
password: 'pass',
entities: [User, Follows]
});
QueryDriver
is the entry point for query execution.
const qd = getQueryDriver();
// Find entities
const users = await qd
.find(User)
.where("user.id IN :userIds") // In WHERE statement, Use capitalCased Entity name
.orderBy("u.id", 'ASC')
.limit(2)
.buildQuery({
userIds: ['1', '2']
})
.run() // User[]
;
// Find graphs
const userAndFollowersList = await qd
.find(UserAndFollowers)
.where("user.id IN :userIds")
.orderBy("u.id", 'ASC')
.limit(2)
.buildQuery({
userIds: ['1', '2']
})
.run() // UserAndFollowers[]
;
const query = await qd
.find(User)
.where("user.id = $userId")
.buildQuery({
userId: '1'
})
.run() // User|null
;
const user = new User();
// Save node entity
await qd.save(user);
// Save graph
const userAndFollowers = new UserAndFollowers(user, [new User()]);
await qd.save(userAndFollowers);
// Detach any relationships having direction `user -> user2`.
await qd.detach(user, user2);
// Detach specified relationship having direction `user -> user2`.
await qd.detach(user, user2, 'FOLLOWS');
// Detach specified relationship having direction `user <- user2`.
await qd.detach(user, user2, 'FOLLOWS', '<-');
// Detach specified relationship regardless direction.
await qd.detach(user, user2, 'FOLLOWS', '-');
// Relationship constructor can also be passed.
await qd.detach(user, user2, Follows);
await qd.delete(user);
.delete()
does not support Relationship and Graph.
Use .detach()
or .detachDelete()
instead.
// DETACH DELETE the node
await qd.detachDelete(user);
// DETACH DELETE entities included in the graph
await qd.detachDelete(userAndFollowers);
Use .transactional()
.
Rollback is executed When callback throws an error.
await qd.transactional(async (qd: QueryDriver) => {
const userAndFollowers = await qd
.findOne(UserAndFollowers)
.where('uhop.id=$userId')
.buildQuery({
userId: '1',
})
.run();
userAndFollowers.addFollower(new User());
await qd.save(userAndFollowers);
});
@NodeEntity('User', {
unique: ['name']
})
class User {
@Primary()
private id: string;
@Property()
private name: string;
}
@NodeEntity('User')
class User {
@Primary()
private id: string;
@Property({ notNull: true })
private name: string;
}
@NodeEntity('User', {
keys: [['name', 'address']]
})
class User {
@Primary()
private id: string;
@Property()
private name: string;
@Property()
private address: string;
}
Currentry supports btree, text and full-text index types.
@NodeEntity('User', {
indexes: [
{
type: 'btree',
on: ['name'],
},
{
name: 'index_IndexTestNode_arbitrary_name',
type: 'fulltext',
on: ['name', 'description'],
},
],
})
class User {
@Primary()
private id: string;
@Property()
private name: string;
@Property()
private description: string;
}
await qd.syncConstraints();