https://github.com/seihmd/spinel
Decorator based OGM for Neo4j.
https://github.com/seihmd/spinel
cypher neo4j ogm typescript
Last synced: 3 months ago
JSON representation
Decorator based OGM for Neo4j.
- Host: GitHub
- URL: https://github.com/seihmd/spinel
- Owner: seihmd
- License: mit
- Created: 2022-07-18T13:21:27.000Z (about 3 years ago)
- Default Branch: main
- Last Pushed: 2023-05-17T04:07:59.000Z (over 2 years ago)
- Last Synced: 2025-05-23T00:54:10.396Z (5 months ago)
- Topics: cypher, neo4j, ogm, typescript
- Language: TypeScript
- Homepage:
- Size: 963 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Spinel
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# Installing
```
npm install @seihmd/spinel
```# Usage
## Define entities
```typescript
// 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;
}
}
``````typescript
// Define Relationship entity
@RelationshipEntity('FOLLOWS')
class Follows {
@Primary()
private id: string;constructor(id: string) {
this.id = id;
}
}
```## Define graphs
```mermaid
graph TD
A(follower) -->|follows| B(user)
``````typescript
// 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;
}
}
``````mermaid
graph TD
A(follower) -->|:FOLLOWS| B(user)
C(follower) -->|:FOLLOWS| B(user)
``````typescript
@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;
}
}
``````mermaid
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)
``````typescript
@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;
}
}
```## Configure
Run configure() at the application entry point.
```typescript
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.
```typescript
configure({
host: 'neo4j://localhost',
user: 'neo4j',
password: 'pass',
entities: [User, Follows]
});
```## Querying
### QueryDriver
`QueryDriver` is the entry point for query execution.
```typescript
const qd = getQueryDriver();
```### Find
```typescript
// 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[]
;
```### FindOne
```typescript
const query = await qd
.find(User)
.where("user.id = $userId")
.buildQuery({
userId: '1'
})
.run() // User|null
;
```### Save
```typescript
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
#### Detach between nodes
```typescript
// 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);
```### Delete
#### Delete node
```typescript
await qd.delete(user);
```#### Delete relationship and graph
`.delete()` does not support Relationship and Graph.
Use `.detach()` or `.detachDelete()` instead.### Detach Delete
```typescript
// DETACH DELETE the node
await qd.detachDelete(user);// DETACH DELETE entities included in the graph
await qd.detachDelete(userAndFollowers);
```## Transaction
Use `.transactional()`.
Rollback is executed When callback throws an error.```typescript
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);
});
```## Manage Constraints and Indexes
### Add constraints
#### Unique node property constraints
```typescript
@NodeEntity('User', {
unique: ['name']
})
class User {
@Primary()
private id: string;@Property()
private name: string;
}
```#### Node/Relationship property existence constraints
```typescript
@NodeEntity('User')
class User {
@Primary()
private id: string;@Property({ notNull: true })
private name: string;
}
```#### Node key constraints
```typescript
@NodeEntity('User', {
keys: [['name', 'address']]
})
class User {
@Primary()
private id: string;@Property()
private name: string;@Property()
private address: string;
}
```### Add Indexes
Currentry supports btree, text and full-text index types.
```typescript
@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;
}
```### Synchronize with database
```typescript
await qd.syncConstraints();
```