Onyx Database Relationships
Cascade Policy Tutorial
Compare how cascade policies operate in the Onyx open source ORM with the resolver-driven approach used in Onyx Cloud. Use the version toggle to view entity annotations or cloud-native resolver workflows.
Choose Onyx Open Source to review CascadePolicy annotations or Onyx Cloud to focus on resolver cascades.
Onyx Open Source Tutorial
Switch to Onyx Open Source to explore CascadePolicy annotations, cascading saves, and delete semantics.
Onyx Cloud Tutorial
In Onyx Cloud Database you attach resolver relationships in the schema designer and drive cascading writes or deletes directly from the database client.
Configure Resolver Relationships
Resolver relationships are defined per attribute in the Admin Console. Each resolver runs server-side code that can query other tables, perform joins, or compute derived results. When you cascade operations, the platform uses the resolver's output to understand which related rows should be persisted or deleted alongside the parent record.
Add a resolver attribute
In the schema designer, open your table and select Attributes with Resolvers. Choose Add Resolver Attribute, provide a property-style name (for example, actors), and paste the resolver code.
Return related data from the resolver
The Admin Console injects an authenticated db client. Use the fluent query builder helpers like where(), eq(), and list() to shape the resolver result. The example below resolves actors for the current movie record.
1// Schema designer → Attributes with Resolvers
2 return await db
3 .from("Actor")
4 .where(eq("movieId", this.id))
5 .orderBy(asc("lastName"))
6 .list();Publish the schema
Validate and publish your schema changes. Once deployed, the resolver name becomes available to scripts and API calls—both for explicit resolution (db.resolve('actors')) and cascading operations.
Cascade Save with Graph Paths
Use the graph syntax inside db.cascade() to describe how the resolver-backed records relate to the parent entity. The format is resolverName:EntityType(childForeignKey, parentPrimaryKey). When you call save(), the SDK automatically assigns foreign keys and persists the entire graph.
1const movieWithActors = {
2 id: "movie_001",
3 title: "Inception",
4 releaseYear: 2010,
5 genre: "Sci-Fi",
6 actors: [
7 { id: "actor_001", movieId: "movie_001", name: "Leonardo DiCaprio" },
8 { id: "actor_002", movieId: "movie_001", name: "Marion Cotillard" }
9 ]
10};
11
12await db
13 .cascade('actors:Actor(movieId, id)')
14 .save('Movie', movieWithActors);Shape the object graph
Build a plain JavaScript object that mirrors the parent entity and its resolver-backed children. Provide stable identifiers for every record so repeated saves perform updates instead of inserts.
Persist with cascade save
Call db.cascade() with the graph path and finish with save(). The SDK writes the parent first, then batches the resolver children using the supplied key mapping.
Verify the resolver data
Query the table and resolve the relationship to confirm that every actor is associated with the saved movie.
1const movie = await db
2 .from('Movie')
3 .resolve('actors')
4 .where(eq('id', 'movie_001'))
5 .firstOrNull();
6
7db.results = movie;Cascade Delete via Resolvers
To remove a parent entity and its resolver-backed children in one call, chain cascade() onto a query builder and pass the resolver name. The SDK uses the resolver's output to locate dependent records before issuing the delete.
1await db
2 .from('Movie')
3 .where(eq('id', 'movie_001'))
4 .cascade('actors')
5 .delete();When deleting, you only need the resolver names—no graph path is required because the resolver already expresses how the related data should be collected. Combine where() clauses to scope the delete to a single record or a filtered set.
Troubleshooting & Tips
- Resolver returns no data: Confirm the resolver queries use the parent identifier (
this.id, for example) and publish the schema after edits. - Graph save fails: Check that every child includes the foreign key referenced in the graph path and that identifiers are unique across saves.
- Delete misses children: Ensure the resolver returns every dependent record that should be removed. Add filters cautiously—cascade delete only removes what the resolver exposes.