Backend Link Types API

Complete reference for all Holochain link types and relationship patterns.

Link types define relationships between entries and enable efficient querying and indexing.

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    AllEntities,           // Path -> Entity (for listing all entities)
    EntityToRelated,       // Entity -> Related Entity
    TagToEntity,           // Tag Path -> Entity (for tag-based search)
}
}

File: dnas/requests_and_offers/zomes/integrity/service_types/src/lib.rs

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    AllServiceTypes,              // Path("all_service_types") -> ServiceType
    ServiceTypeToTag,             // ServiceType -> Tag Path
    TagToServiceType,             // Tag Path -> ServiceType
    ApprovedServiceTypes,         // Path("approved_service_types") -> ServiceType
    PendingServiceTypes,          // Path("pending_service_types") -> ServiceType
    RejectedServiceTypes,         // Path("rejected_service_types") -> ServiceType
}
}

All Service Types Index

#![allow(unused)]
fn main() {
// Create link when service type is created
let path = Path::from("all_service_types");
create_link(
    path.path_entry_hash()?,
    service_type_hash.clone(),
    LinkTypes::AllServiceTypes,
    ()
)?;

// Query all service types
let path = Path::from("all_service_types");
let links = get_links(path.path_entry_hash()?, LinkTypes::AllServiceTypes, None)?;
}

Tag-Based Indexing

#![allow(unused)]
fn main() {
// Create tag links for each tag
for tag in &service_type.tags {
    let tag_path = Path::from(format!("tag.{}", tag));
    create_link(
        tag_path.path_entry_hash()?,
        service_type_hash.clone(),
        LinkTypes::TagToServiceType,
        ()
    )?;
}

// Query by tag
let tag_path = Path::from(format!("tag.{}", search_tag));
let links = get_links(tag_path.path_entry_hash()?, LinkTypes::TagToServiceType, None)?;
}

Status-Based Indexing

#![allow(unused)]
fn main() {
// Create status-specific links
match service_type.status {
    ServiceTypeStatus::Approved => {
        let path = Path::from("approved_service_types");
        create_link(path.path_entry_hash()?, service_type_hash, LinkTypes::ApprovedServiceTypes, ())?;
    },
    ServiceTypeStatus::Pending => {
        let path = Path::from("pending_service_types");
        create_link(path.path_entry_hash()?, service_type_hash, LinkTypes::PendingServiceTypes, ())?;
    },
    ServiceTypeStatus::Rejected => {
        let path = Path::from("rejected_service_types");
        create_link(path.path_entry_hash()?, service_type_hash, LinkTypes::RejectedServiceTypes, ())?;
    }
}
}

File: dnas/requests_and_offers/zomes/integrity/requests/src/lib.rs

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    RequestUpdates,               // Original request -> Updated request (tracking chain)
    AllRequests,                  // Legacy (still present for compatibility)
    ActiveRequests,               // Path("requests.active") -> Active requests only
    ArchivedRequests,             // Path("requests.archived") -> Archived requests only
    UserRequests,                 // UserProfile -> Requests created by user
    OrganizationRequests,         // Organization -> Requests associated with organization
    RequestCreator,               // Request -> UserProfile (creator of request)
    RequestOrganization,          // Request -> Organization (if associated)
}
}

Active/Archived Path Pattern

The application uses separate DHT paths for active and archived requests to optimize query performance:

  • ActiveRequests: Path("requests.active") → Request (for visible/active requests)
  • ArchivedRequests: Path("requests.archived") → Request (for archived requests)

Benefits:

  • Queries fetch only relevant items (no client-side filtering needed)
  • Performance remains optimal as archived requests accumulate
  • Clear semantic separation of data states
  • Reduced DHT load for common queries

Archive Flow:

  1. New requests are created in requests.active path with ActiveRequests link type
  2. When archived, the link is deleted from requests.active and created in requests.archived with ArchivedRequests link type
  3. Entry status is also updated to ListingStatus::Archived for backward compatibility

Usage Examples

Service Type Relationships

#![allow(unused)]
fn main() {
// Link request to service type
create_link(
    request_hash.clone(),
    service_type_hash.clone(),
    LinkTypes::RequestToServiceType,
    ()
)?;

// Reverse link for querying requests by service type
create_link(
    service_type_hash.clone(),
    request_hash.clone(),
    LinkTypes::ServiceTypeToRequest,
    ()
)?;
}

User Ownership

#![allow(unused)]
fn main() {
// Link user to their requests
create_link(
    user_profile_hash.clone(),
    request_hash.clone(),
    LinkTypes::UserToRequest,
    ()
)?;
}

Status and Urgency Indexing

#![allow(unused)]
fn main() {
// Status-based indexing
let status_path = Path::from(format!("{:?}_requests", request.status).to_lowercase());
create_link(status_path.path_entry_hash()?, request_hash.clone(), /* appropriate LinkType */, ())?;

// Urgency-based indexing
let urgency_path = Path::from(format!("urgency.{:?}", request.urgency).to_lowercase());
create_link(urgency_path.path_entry_hash()?, request_hash.clone(), LinkTypes::UrgencyToRequest, ())?;
}

File: dnas/requests_and_offers/zomes/integrity/offers/src/lib.rs

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    OfferUpdates,                 // Original offer -> Updated offer (tracking chain)
    AllOffers,                    // Legacy (still present for compatibility)
    ActiveOffers,                 // Path("offers.active") -> Active offers only
    ArchivedOffers,               // Path("offers.archived") -> Archived offers only
    UserOffers,                   // UserProfile -> Offers created by user
    OrganizationOffers,           // Organization -> Offers associated with organization
    OfferCreator,                 // Offer -> UserProfile (creator of offer)
    OfferOrganization,            // Offer -> Organization (if associated)
}
}

Active/Archived Path Pattern

The application uses separate DHT paths for active and archived items to optimize query performance:

  • ActiveOffers: Path("offers.active") → Offer (for visible/active offers)
  • ArchivedOffers: Path("offers.archived") → Offer (for archived offers)

Benefits:

  • Queries fetch only relevant items (no client-side filtering needed)
  • Performance remains optimal as archived items accumulate
  • Clear semantic separation of data states
  • Reduced DHT load for common queries

Archive Flow:

  1. New offers are created in offers.active path with ActiveOffers link type
  2. When archived, the link is deleted from offers.active and created in offers.archived with ArchivedOffers link type
  3. Entry status is also updated to ListingStatus::Archived for backward compatibility

File: dnas/requests_and_offers/zomes/integrity/users/src/lib.rs

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    AllUsers,                     // Path("all_users") -> UserProfile
    AgentToProfile,               // AgentPubKey -> UserProfile
    ProfileToAgent,               // UserProfile -> AgentPubKey
    SkillToUser,                  // Path("skill.{name}") -> UserProfile
    UserToSkill,                  // UserProfile -> Path("skill.{name}")
    LocationToUser,               // Path("location.{name}") -> UserProfile
}
}

User Indexing Patterns

Agent-Profile Mapping

#![allow(unused)]
fn main() {
// Map agent to their profile
create_link(
    agent_hash.clone().into(),
    profile_hash.clone(),
    LinkTypes::AgentToProfile,
    ()
)?;

// Reverse mapping
create_link(
    profile_hash.clone(),
    agent_hash.clone().into(),
    LinkTypes::ProfileToAgent,
    ()
)?;
}

Skill-Based Discovery

#![allow(unused)]
fn main() {
// Create skill links
for skill in &user_profile.skills {
    let skill_path = Path::from(format!("skill.{}", skill.to_lowercase()));
    create_link(
        skill_path.path_entry_hash()?,
        profile_hash.clone(),
        LinkTypes::SkillToUser,
        ()
    )?;
}
}

File: dnas/requests_and_offers/zomes/integrity/organizations/src/lib.rs

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    AllOrganizations,             // Path("all_organizations") -> Organization
    OrganizationToMember,         // Organization -> UserProfile
    MemberToOrganization,         // UserProfile -> Organization
    TypeToOrganization,           // Path("type.{org_type}") -> Organization
    LocationToOrganization,       // Path("location.{location}") -> Organization
    OrganizationContacts,         // Organization -> UserProfile (tag = role string)
}
}

Organization Contact Person

The OrganizationContacts link type designates a single coordinator as the public-facing contact person for an organization. The role/title is stored in the link tag.

#![allow(unused)]
fn main() {
// Set contact person with role encoded in tag
create_link(
    organization_hash.clone(),
    user_hash.clone(),
    LinkTypes::OrganizationContacts,
    role.as_bytes().to_vec(),
)?;
}

Organization Membership

#![allow(unused)]
fn main() {
// Add member to organization
create_link(
    organization_hash.clone(),
    user_profile_hash.clone(),
    LinkTypes::OrganizationToMember,
    ()
)?;

// Reverse link for user's organizations
create_link(
    user_profile_hash.clone(),
    organization_hash.clone(),
    LinkTypes::MemberToOrganization,
    ()
)?;
}

File: dnas/requests_and_offers/zomes/integrity/administration/src/lib.rs

#![allow(unused)]
fn main() {
#[derive(Serialize, Deserialize)]
#[hdk_link_types]
pub enum LinkTypes {
    AdminsAnchor,                 // Path("admins") -> AdminRole
    ModeratorsAnchor,             // Path("moderators") -> AdminRole
    SuspensionsAnchor,            // Path("suspensions") -> UserSuspension
    AgentToSuspension,            // AgentPubKey -> UserSuspension
}
}

Administrative Indexing

#![allow(unused)]
fn main() {
// Add admin role to index
let admins_path = Path::from("admins");
create_link(
    admins_path.path_entry_hash()?,
    admin_role_hash,
    LinkTypes::AdminsAnchor,
    ()
)?;

// Link agent to suspension
create_link(
    suspended_agent.clone().into(),
    suspension_hash,
    LinkTypes::AgentToSuspension,
    ()
)?;
}
#![allow(unused)]
fn main() {
pub fn validate_create_link(
    action: CreateLink,
    base_address: AnyLinkableHash,
    target_address: AnyLinkableHash,
    tag: LinkTag,
) -> ExternResult<ValidateCallbackResult> {
    match action.link_type {
        LinkTypes::AllServiceTypes => {
            // Validate that base is the correct path
            // Validate that target is a ServiceType entry
            Ok(ValidateCallbackResult::Valid)
        },
        LinkTypes::TagToServiceType => {
            // Validate tag path format
            // Validate service type exists
            Ok(ValidateCallbackResult::Valid)
        },
        _ => Ok(ValidateCallbackResult::Valid),
    }
}
}
#![allow(unused)]
fn main() {
pub fn validate_delete_link(
    action: DeleteLink,
    original_action: CreateLink,
    base: AnyLinkableHash,
    target: AnyLinkableHash,
    tag: LinkTag,
) -> ExternResult<ValidateCallbackResult> {
    // Validate that agent has permission to delete this link
    // Check if link deletion is allowed for this link type
    Ok(ValidateCallbackResult::Valid)
}
}

Query Patterns

Efficient Queries

#![allow(unused)]
fn main() {
// Get all entities of a type
pub fn get_all_service_types() -> ExternResult<Vec<Record>> {
    let path = Path::from("all_service_types");
    let links = get_links(path.path_entry_hash()?, LinkTypes::AllServiceTypes, None)?;

    let get_input: Vec<GetInput> = links
        .into_iter()
        .map(|link| GetInput::new(
            ActionHash::from(link.target).into(),
            GetOptions::default(),
        ))
        .collect();

    let records = HDK.with(|hdk| hdk.borrow().get(get_input))?;
    let records: Vec<Record> = records.into_iter().filter_map(|r| r).collect();

    Ok(records)
}

// Get entities by tag
pub fn get_service_types_by_tag(tag: String) -> ExternResult<Vec<Record>> {
    let tag_path = Path::from(format!("tag.{}", tag));
    let links = get_links(tag_path.path_entry_hash()?, LinkTypes::TagToServiceType, None)?;

    // Convert links to records (same pattern as above)
    // ...
}
}

Performance Considerations

Indexing Strategy

  • Create multiple indexes for different query patterns
  • Use hierarchical paths for efficient filtering
  • Balance between query performance and storage overhead
  • Clean up orphaned links when entries are deleted
  • Use link tags for additional metadata when needed
  • Consider link direction for optimal query patterns

This link type reference provides the complete relationship structure for the Holochain application, enabling efficient querying and data discovery.