How Macro's Bidirectional @linking System Functions Across Diverse Entity Types
Macro implements a generic @link (entity‑mention) system using a central EntityMention struct that records both source and target for every relationship, enabling bidirectional queries and notifications across messages, documents, projects, email threads, calls, and more.
The macro-inc/macro codebase powers a unified collaboration platform where any object can reference any other object. The bidirectional @linking system serves as the backbone for cross-entity navigation, notifications, and relationship discovery. This article examines the architecture, data model, and API flow that make this polymorphic linking possible.
The Core Data Model: EntityMention
At the heart of the system lies the EntityMention struct, defined in crates/channels/src/domain/models.rs (lines 1385–1400). This struct captures four essential identifiers that make any link traversable from either direction:
source_entity_type+source_entity_id— the object creating the referenceentity_type+entity_id— the object being referencedcreated_by— the user who created the linkcreated_at— timestamp for ordering and auditing
// Conceptual representation based on EntityMention definition
pub struct EntityMention {
pub id: Uuid,
pub source_entity_type: String, // e.g., "message", "document"
pub source_entity_id: Uuid, // UUID of the source
pub entity_type: String, // e.g., "Document", "Chat", "user"
pub entity_id: Uuid, // UUID of the target
pub created_by: Uuid,
pub created_at: DateTime<Utc>,
}
The CreateEntityMentionOptions struct (lines 1404–1415) mirrors these fields for API-level operations, while CreateEntityMentionRequest (lines 1420–1429) serves as the HTTP payload for POST /channels/mentions.
Creating Links: The Insertion Flow
When a user creates an @link, the system validates the request and persists the relationship via insert_message_mentions in crates/channels/src/outbound/pg_channels_repo.rs (lines 62–70).
// Simplified flow showing the key operations
pub async fn insert_message_mentions(
&self,
message_id: Uuid,
mentions: &[CreateEntityMentionOptions],
created_by: Uuid,
) -> Result<Vec<Uuid>, Error> { // Returns user IDs to notify
// 1. Deduplicate existing mentions for this message
// 2. Insert new rows into comms_entity_mentions
// 3. If entity_type == "user", collect for notification
}
This function demonstrates polymorphic handling: it accepts any target type in the entity_type field, yet applies special logic when the target is a user (triggering push notifications).
Querying Links: Bidirectional Retrieval
The system enables two fundamental query patterns through the same table structure.
Forward Lookup: "What does this source mention?"
When fetching channel messages, get_latest_channel_messages_batch (lines 2120–2130) aggregates all mentions as "entity_type:entity_id" strings:
-- From pg_channels_repo.rs message retrieval query
SELECT
m.id,
m.content,
ARRAY(
SELECT em.entity_type || ':' || em.entity_id
FROM comms_entity_mentions em
WHERE em.source_entity_id = m.id
) AS mentions
FROM comms_messages m
WHERE m.channel_id = $1
ORDER BY m.created_at DESC
LIMIT $2;
The frontend parses these strings to render clickable @links with appropriate icons and routing.
Reverse Lookup: "What sources mention this target?"
By filtering on entity_type + entity_id, the system finds all incoming references. The thread participant logic uses this implicitly: get_thread_data and get_thread_messages join comms_entity_mentions to identify users @mentioned in any message, ensuring they receive thread notifications regardless of direct channel membership.
Deletion and Consistency
Link lifecycle management relies on delete_entity_mentions_by_source (lines 33–40), which removes all rows matching a source entity ID. This generic deletion works across all source types:
pub async fn delete_entity_mentions_by_source(
&self,
source_entity_ids: &[Uuid],
) -> Result<u64, Error> {
// DELETE FROM comms_entity_mentions
// WHERE source_entity_id = ANY($1)
}
This design ensures referential integrity without cascading constraints — when a message is deleted, its outgoing @links disappear automatically.
Supported Entity Types and Extensibility
The ReferencedShareItemType enum defines built-in target types in models.rs:
| Type | Description |
|---|---|
Document |
Stored files and content |
Chat |
Channels and direct messages |
Project |
Workspace containers |
EmailThread |
Email conversations |
Call |
Voice/video call records |
Because comms_entity_mentions stores types as plain String values, new entity categories require no schema migration. The service layer need only recognize and validate the new type string.
API Flow Summary
- Client constructs a
CreateEntityMentionRequestwith source and target identifiers - Router accepts
POST /channels/mentionsand validates the payload - Repository calls
insert_message_mentions(or generic equivalent) to write rows - Database stores bidirectional relationship in
comms_entity_mentions - Notification service receives user IDs when @user mentions occur
- Query handlers join the mentions table for rendering and reverse lookups
- Cleanup triggers
delete_entity_mentions_by_sourceon entity deletion
Key Source Files
| File | Responsibility |
|---|---|
crates/channels/src/domain/models.rs |
EntityMention, request/response types, ReferencedShareItemType |
crates/channels/src/outbound/pg_channels_repo.rs |
SQL operations: insert_message_mentions, delete_entity_mentions_by_source, message queries with mention arrays |
crates/channels/src/domain/mention_events.rs |
Event metadata generation for notifications |
crates/channels/src/inbound/axum_router.rs |
HTTP route handlers for mention endpoints |
Summary
- Bidirectional @linking in Macro relies on a single table (
comms_entity_mentions) with four identifier columns enabling traversal in either direction EntityMentionstruct (lines 1385–1400) provides the canonical representation, decoupled from any specific source or target type- Polymorphic insertion via
insert_message_mentionshandles type-specific side effects (user notifications) while preserving generic storage - Deletion by source with
delete_entity_mentions_by_sourcemaintains graph consistency without foreign key cascades - String-based typing allows runtime extensibility — new entity types require code changes, not schema migrations
Frequently Asked Questions
How does Macro handle notification routing for @mentions?
The insertion function insert_message_mentions checks whether entity_type == "user" for each mention. When true, it resolves the referenced user's notification preferences and channel participation status, returning their UUIDs to the caller for enqueueing push/email notifications.
Can two different entity types link to each other arbitrarily?
Yes. The data model imposes no restrictions on source-target type combinations. A Call can reference a Document, an EmailThread can reference a Project, and so on. The frontend determines how to render each combination based on the entity_type string.
What happens to @links when a target entity is deleted?
The current implementation does not automatically cascade deletions when a target is removed. The mentions array in query results may contain stale references (dangling links), which the frontend typically handles by showing "unavailable" placeholders. Deletion cascades only from source entities via delete_entity_mentions_by_source.
How scalable is the comms_entity_mentions table for high-volume workspaces?
The table structure supports indexing strategies for both query directions: (source_entity_type, source_entity_id) for forward lookups and (entity_type, entity_id) for reverse lookups. The batch message query uses ARRAY aggregation to minimize round-trips, and the deduplication logic in insert_message_mentions prevents redundant rows for frequently edited messages.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →