> ## Documentation Index
> Fetch the complete documentation index at: https://cindel.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Collection Relationships in Cindel: Links and Backlinks

> Connect root collections using CindelLink for to-one and CindelLinks for to-many relationships, and use @Backlink to access inverse links.

Relationships let one root collection point to objects in another root collection. Unlike embedded objects — which are stored inside their parent — linked objects keep their own collection, their own lifecycle, and their own typed query API. Cindel provides `CindelLink<T>` for to-one relationships, `CindelLinks<T>` for to-many relationships, and `@Backlink` for read-only inverse access.

## Model Overview

The examples throughout this page use `Artist` and `Song` collections.

```dart theme={null}
import 'package:cindel/cindel.dart';

part 'music.g.dart';

@Collection(name: 'artists')
class Artist {
  Id dbId = autoIncrement;

  late String name;

  @Backlink(to: 'featuredArtists')
  final songs = CindelLinks<Song>();
}

@Collection(name: 'songs')
class Song {
  Id dbId = autoIncrement;

  late String title;

  final featuredArtists = CindelLinks<Artist>();
  final primaryArtist = CindelLink<Artist>();
}
```

This defines three relationship fields:

* `Song.primaryArtist` — one song points to one artist.
* `Song.featuredArtists` — one song points to many artists.
* `Artist.songs` — read-only inverse: one artist can load songs whose `featuredArtists` link includes that artist.

Link containers must be declared as `final` fields. The target type must be another root Cindel collection — embedded objects cannot be relationship targets.

***

## To-One Relationships: `CindelLink<T>`

Use `CindelLink<T>` when one object points to zero or one object in another collection.

```dart theme={null}
@Collection(name: 'songs')
class Song {
  Id dbId = autoIncrement;

  late String title;

  final primaryArtist = CindelLink<Artist>();
}
```

### Set and Save

Assign the target through `.value`, then persist the relationship:

```dart theme={null}
song.primaryArtist.value = artist;
await song.primaryArtist.save();
```

### Load

Reading a `Song` from the database does **not** automatically load its link. Load it explicitly after fetching the object:

```dart theme={null}
final song = await db.songs.get(songId);

await song!.primaryArtist.load();

final artist = song.primaryArtist.value;
```

### Clear

Set `.value` to `null` and save to remove the relationship from storage:

```dart theme={null}
song.primaryArtist.value = null;
await song.primaryArtist.save();
```

### Reset In-Memory State

`.reset()` discards the currently loaded value in memory without touching the stored relationship. Use it when you want to force a fresh load later:

```dart theme={null}
await song.primaryArtist.reset();
```

***

## To-Many Relationships: `CindelLinks<T>`

Use `CindelLinks<T>` when one object points to a set of objects in another collection.

```dart theme={null}
@Collection(name: 'songs')
class Song {
  Id dbId = autoIncrement;

  late String title;

  final featuredArtists = CindelLinks<Artist>();
}
```

### Add, Remove, and Save

Modify the in-memory set, then persist it with a single save call:

```dart theme={null}
song.featuredArtists.add(firstArtist);
song.featuredArtists.add(secondArtist);
song.featuredArtists.remove(firstArtist);

await song.featuredArtists.save();
```

`save()` **replaces** the stored ID set with the current in-memory contents. If you add one artist and remove another and then call `save()`, the stored relationship reflects exactly what is in the container at that moment.

### Load and Iterate

Load the relationship explicitly after fetching the object:

```dart theme={null}
final song = await db.songs.get(songId);

await song!.featuredArtists.load();

for (final artist in song.featuredArtists) {
  print(artist.name);
}
```

`CindelLinks<T>` is iterable after loading. You can also convert it to a list:

```dart theme={null}
final artists = song.featuredArtists.toList();
```

### Reset In-Memory State

`.reset()` clears the in-memory set without changing what is stored:

```dart theme={null}
await song.featuredArtists.reset();
```

***

## Inverse Relationships: `@Backlink`

A backlink is a read-only inverse of a forward relationship. It lets an object load the collection objects that point to it, without requiring a separate forward field on both sides.

```dart theme={null}
@Collection(name: 'artists')
class Artist {
  Id dbId = autoIncrement;

  late String name;

  @Backlink(to: 'featuredArtists')
  final songs = CindelLinks<Song>();
}
```

The `to` value is the **Dart field name** of the forward link on the other collection — in this case `featuredArtists` on `Song`. It is not the collection name.

### Load a Backlink

Loading works exactly like a to-many:

```dart theme={null}
final artist = await db.artists.get(artistId);

await artist!.songs.load();

for (final song in artist.songs) {
  print(song.title);
}
```

### Backlinks Are Read-Only

Calling `.save()` on a backlink throws. To change what appears in a backlink, update and save the forward link:

```dart theme={null}
song.featuredArtists.add(artist);
await song.featuredArtists.save();
```

***

## Saving Relationships

<Warning>
  Always store the **target** object and the **source** object before saving the relationship. Cindel validates that both IDs exist when you call `save()`. If either object has not been stored yet, saving the relationship fails.
</Warning>

The correct write order inside a transaction:

<Steps>
  <Step title="Store the target">
    Put the object being pointed to first so its ID is assigned.
  </Step>

  <Step title="Store the source">
    Put the object that holds the link field.
  </Step>

  <Step title="Update in-memory containers">
    Set `.value` or call `.add()` / `.remove()` on the link containers.
  </Step>

  <Step title="Save the forward link">
    Call `.save()` on the `CindelLink` or `CindelLinks` field. Do not call `.save()` on a backlink.
  </Step>
</Steps>

```dart theme={null}
final artist = Artist()..name = 'Ana';
final song = Song()..title = 'Ship It';

await db.writeTxn(() async {
  await db.artists.put(artist);
  await db.songs.put(song);

  song.featuredArtists.add(artist);
  song.primaryArtist.value = artist;

  await song.featuredArtists.save();
  await song.primaryArtist.save();
});
```

***

## Loading Relationships

Reading an object from the database does **not** automatically load its link values. You must load each relationship explicitly.

```dart theme={null}
final song = await db.songs.get(songId);

await song!.featuredArtists.load();
await song.primaryArtist.load();

final primary = song.primaryArtist.value;
final featured = song.featuredArtists.toList();
```

The object you load from must be database-backed (i.e. previously stored). A newly constructed object that has never been put cannot load persisted relationships.

***

## Common Mistakes

| Mistake                                            | What goes wrong                                                         | Fix                                                                        |
| -------------------------------------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| Declaring link fields as non-`final`               | The container object can be replaced, breaking internal state           | Always declare `final featuredArtists = CindelLinks<Artist>()`             |
| Pointing a link at an embedded object              | Embedded objects have no ID — the link target must be a root collection | Model the target as a root collection                                      |
| Saving the relationship before putting the objects | Cindel cannot find the source or target ID, so `save()` fails           | Put both objects first, then save the link                                 |
| Calling `.save()` on a backlink                    | Backlinks are read-only and throw on `.save()`                          | Save the forward link instead                                              |
| Passing the collection name to `@Backlink(to:)`    | The backlink resolves to the wrong field                                | Pass the **Dart field name** of the forward link, e.g. `'featuredArtists'` |
| Expecting links to load automatically              | Accessing `.value` or iterating gives empty results                     | Call `.load()` on every link you need after fetching the parent object     |
