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:
Collection: Represents the core list, holding thevisibilitystate (PRIVATE,SHARED, orPUBLIC).CollectionItem: Represents an item (Product, Service, or Malet) within the list.CollectionCollaborator: Manages access control, mapping auserIdto a specificcollectionIdwith an assignedrole(VIEWERorEDITOR).
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:
- Exact
handlematch - Exact
emailmatch - Case-insensitive
displayNamematch (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:
- Pushes the core
Collectionmetadata to thenodessubgraph. - Iterates over all local
CollectionItems associated with the list and invokessyncAddItem(). - Upgrades the local ID to the backend-assigned UUID.
- Proceeds with the
addCollectionCollaboratormutation.
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(),
});
Related
- Alerts Resilience & Infrastructure โ Details on the TCP event emission pattern.
- Subgraphs: Nodes โ Core entity and federated graph references.
- User Identity Resolution โ Details on how handles, display names, and avatars are processed.
- Developer Docs Overview โ Back to the main documentation index.