Developer Docs

Social Wishlist Collaboration Architecture

The Mallnline Custom Lists system (internally referred to as Wishlists) extends beyond standard e-commerce features to provide robust, real-time collaboration capabilities. This document outlines the technical architecture, data synchronization flow, and identity resolution mechanisms that power list sharing between Malets and Visitors.

Architecture Overview

The collaboration system spans the nodes subgraph for backend state management and the SvelteKit frontend for local persistence and real-time UI updates.

Key Entities

The backend uses three primary entities within the nodes subgraph:

  1. Collection: Represents the core list, holding the visibility state (PRIVATE, SHARED, or PUBLIC).
  2. CollectionItem: Represents an item (Product, Service, or Malet) within the list.
  3. CollectionCollaborator: Manages access control, mapping a userId to a specific collectionId with an assigned role (VIEWER or EDITOR).

Identity Resolution

To provide a seamless UX during the invitation flow, the shareCollection and addCollectionCollaborator mutations support flexible identity resolution.

When a Malet Owner or Visitor inputs an identifier, the resolver attempts to match the target User via:

  1. Exact handle match
  2. Exact email match
  3. Case-insensitive displayName match (using regex)

Security Enhancements

To prevent exposing raw system ObjectIDs to the frontend, the CollectionCollaborator GraphQL type implements a @ResolveField mapping. The CollectionCollaboratorFieldResolver dynamically fetches and injects the handle, displayName, and avatarUrl from the target User entity before returning the payload to the client.

@Resolver(() => CollectionCollaborator)
export class CollectionCollaboratorFieldResolver {
  constructor(
    @InjectModel(User) private readonly userModel: ReturnModelType<typeof User>,
  ) {}

  @ResolveField(() => String, { nullable: true })
  async handle(@Parent() collaborator: CollectionCollaborator): Promise<string | undefined> {
    const user = await this.userModel.findOne({ userId: collaborator.userId });
    return user?.handle;
  }
  // ... similar resolvers for displayName and avatarUrl
}

Data Synchronization

To support offline-first capabilities and unauthenticated list creation, the frontend utilizes a dual-persistence strategy (IndexedDB + localStorage) via wishlistStore.svelte.ts.

Auto-Syncing Local Lists

When a user attempts to share a list that was created while logged out (or before authentication state was fully initialized), the system intercepts the mutation.

If the listId is missing a backend mapping (idMap.has(listId) === false), the frontend triggers syncCreateCollection(). This function:

  1. Pushes the core Collection metadata to the nodes subgraph.
  2. Iterates over all local CollectionItems associated with the list and invokes syncAddItem().
  3. Upgrades the local ID to the backend-assigned UUID.
  4. Proceeds with the addCollectionCollaborator mutation.

This ensures that invited collaborators immediately see the full, synchronized contents of the list, preventing "Collection not found" or "0 items" edge cases.

Event Emissions and Notifications

The collaboration flow integrates directly with the platform's central SIEM and notification infrastructure.

Upon successful addition of a collaborator, the nodes subgraph emits a collection_shared event over TCP to the alerts service. The alerts subgraph parses this payload and dispatches an in-app WishlistNotification to the target user.

this.alertsClient.emit('collection_shared', {
  collectionId: collection.id,
  collectionName: collection.name,
  sharerHandle,
  collaboratorUserId: targetUser.userId,
  timestamp: new Date().toISOString(),
});