For the complete documentation index, see llms.txt. This page is also available as Markdown.

AtCollection.subCollection

AtCollection.subCollection is useful when your data structure has a parent-child relationship.

What is a sub-collection?

A sub-collection is an AtCollection that belongs to a specific CItem in a parent collection. Think of it like a one-to-many relationship: a blog post has many comments, a comment has many replies.

posts (AtCollection<Post>)
  └── "hello-world" (CItem<Post>)
        └── comments (AtCollection<Comment>)     <-- sub-collection
              ├── "c1" (CItem<Comment>)
              │     └── replies (AtCollection<Reply>) <-- nested sub-collection
              │           └── "r1" (CItem<Reply>)
              └── "c2" (CItem<Comment>)

When instantiating your own sub-collection, you must instantiate each AtCollection separately, then they are linked together by namespace.

AtCollection.subCollection

Any CItem can parent its own AtCollection<T>. This lets you model hierarchies like posts and comments, nested to arbitrary depth. Nesting is bounded only by the Atsign Protocol's 255-character key limit.

Creating a sub-collection

  1. First, create your domain objects. In this case, we'll create Post , Comment, and Reply.

class Post {
  final String title;
  final String body;
  Post(this.title, this.body);
  Map<String, dynamic> toJson() => {'title': title, 'body': body};
  factory Post.fromJson(Map<String, dynamic> json) =>
      Post(json['title'], json['body']);
}

class Comment {
  final String text;
  Comment(this.text);
  Map<String, dynamic> toJson() => {'text': text};
  factory Comment.fromJson(Map<String, dynamic> json) =>
      Comment(json['text']);
}
  1. Instantiate your AtCollection<T> objects. See Create AtCollection from domain object for a better explanation on how to do that.

The returned sub-collection is a plain AtCollection<T>. Everything works on it just like a regular AtCollection.

This means multi-level nesting works the same way: simply define a sub-collection on the sub-collection.

In practice, with a 15-character application namespace and single-character sub-collection names, the theoretical depth ceiling is 11 levels (root plus 10 nested sub-collections). Each subCollection call enforces the budget and throws ArgumentError before any I/O if the composed namespace would overflow.

Sub-collection events

AtCollection.subUpdates

AtCollection.subUpdates is a stream of CSubItemUpdated. This lets you listen for stream for sub-collection item updates (such as creates or updates).

Note: when you're reacting to a CSubItemUpdated event from several levels down and only have its ancestry, getDescendant walks the whole chain in one call instead of you threading subCollection calls by hand. See example code below.

AtCollection.subDeletes

AtCollection.subDeletes is a stream of CSubItemDeleted. This lets you stream delete events of sub-collection items.

getDescendant returns null if any link in the chain is missing (a parent expired before the leaf event arrived, for instance) rather than throwing. It requires every CAncestor.owner in ancestry to be non-null; CSubItemDeleted events carry null owners by design, so for those, cache the most recent CSubItemUpdated for the same (id, subName) and reuse its ancestry instead.

Constraints

subCollection throws ArgumentError when:

  • subName is empty or contains a .

  • subName is __rr -- reserved for the built-in read-receipt sub-collection

  • parent.id contains a .

  • the composed namespace would exceed 128 characters

Cascading deletes

Deleting a parent item without cascade: true is blocked if it has self-owned descendants:

Without cascade: true, sub-collection items become orphans. You can clean them up later with cleanupOrphans(). See cascade: true.

Last updated