@nolag/feed

Activity feeds with posts, likes, comments, and fan-out delivery.

Overview

@nolag/feed powers real-time activity feeds for social apps, community platforms, and content aggregators. Posts, likes, and comments are delivered live to all subscribers of a channel and replayed for up to 30 days so users joining late always see a full timeline. Fan-out happens server-side: publish once to a channel and every subscriber receives the update instantly. Your app owns one core NoLag client and injects it into NoLagFeed; the wrapper attaches its behaviour to that connection.

Key Features

  • Real-time post, like, and comment delivery to all channel subscribers
  • 30-day replay across all three topics: posts, reactions, and comments
  • Unread post tracking with per-channel badge counts
  • Unlike support with live tally updates
  • Nested comment threads per post
  • Automatic reconnection with channel and state restoration

How It Works

NoLagFeed attaches to an injected @nolag/js-sdk client. Calling joinChannel() creates a FeedChannel that subscribes to three topics: posts for post creation events, reactions for like and unlike events, and comments for comment creation events. A PostStore accumulates posts and their reaction counts, while a CommentStore groups comments by post ID. An unread counter tracks posts that arrived while the user was away. The app owns the socket lifecycle; the wrapper never opens or closes it.

TopicPurposeReplay
postsPost creation events: text, media, author, timestamp30 days
reactionsLike and unlike events with running tally30 days
commentsComment creation events keyed by post ID30 days

Installation

npm install @nolag/feed @nolag/js-sdk

One core NoLag client can back several wrapper SDKs at once, for example a feed, chat, and notify on a single socket, as long as each wrapper uses a distinct appName. Each wrapper attaches its handlers on construction and releases them with detach(), and never touches the socket itself. Your app owns connect() and disconnect().

Quick Start

import { NoLag } from '@nolag/js-sdk'
import { NoLagFeed } from '@nolag/feed'

// The app owns one core client. In a browser, pass a token provider so the
// SDK can mint fresh short-lived client tokens from your backend.
const client = NoLag(async () => (await (await fetch('/api/nolag-token')).json()).token)

// Inject the client into the feed wrapper
const feed = new NoLagFeed({ client, username: 'Alice', avatar: '/img/alice.png' })

await client.connect()   // the app owns the connection
await feed.ready()       // wrapper setup complete

// Join a feed channel (e.g. a user timeline or community feed)
const channel = await feed.joinChannel('user-123-timeline')

// Listen for new posts in real time
channel.on('postCreated', (post) => {
  console.log(`${post.author.name}: ${post.text}`)
  console.log('Likes:', post.likeCount)
})

// Create a post
await channel.createPost({
  text: 'Just shipped a new feature!',
  media: [{ type: 'image', url: '/uploads/screenshot.png' }],
})

// Like a post
await channel.likePost('post-abc')

channel.on('postLiked', ({ postId, likeCount }) => {
  console.log(`Post ${postId} now has ${likeCount} likes`)
})

// Comment on a post
await channel.addComment('post-abc', 'Congrats!')

channel.on('commentAdded', ({ postId, comment }) => {
  console.log(`New comment on ${postId}: ${comment.text}`)
})

// Get unread count and mark as read
console.log('Unread:', channel.unreadCount)
await channel.markRead()

// Teardown: the wrapper releases its handlers; the app closes the socket.
feed.detach()
client.disconnect()

API Reference

NoLagFeed

The main class. Attaches to the injected core client, manages global user presence, and the feed channel lifecycle.

Constructor Options

OptionTypeDescription
clientNoLagSocketRequired. The injected core NoLag client the app owns and connects.
usernamestringRequired. Display name for this user.
avatarstringOptional avatar URL.
metadataRecord<string, unknown>Optional custom user data attached to presence.
appNamestringNoLag app for topic prefixes (default 'feed').
channelsstring[]Channels to subscribe to once the wrapper is ready.
maxPostCachenumberMax posts kept in memory per channel (default 200).
maxCommentCachenumberMax comments kept in memory per post (default 100).
debugbooleanEnable wrapper debug logging (default false).
MethodDescription
ready()Resolves once wrapper setup completed
detach()Release this wrapper's handlers and topics; terminal, never closes the socket
joinChannel(name)Join a feed channel; returns a FeedChannel instance
leaveChannel(name)Leave a channel and unsubscribe from its topics
getOnlineUsers()Return all users currently online across all joined channels

Events: NoLagFeed

EventPayloadDescription
connectednoneWebSocket connection established
disconnectedreason: stringConnection closed
reconnectednoneReconnection successful; channels are restored automatically
errorerror: ErrorUnrecoverable error occurred
userOnlineuser: FeedUserA user has come online
userOfflineuser: FeedUserA user has gone offline

FeedChannel

Returned by joinChannel(). Handles posts, reactions, comments, and unread tracking for a single channel.

Method / PropertyDescription
createPost(opts)Publish a new post with text, optional media array, and custom metadata
likePost(postId)Like a post; increments the like count for all subscribers
unlikePost(postId)Remove your like from a post
addComment(postId, text)Add a comment to the specified post
getPosts()Return all posts in the local store, newest first
getComments(postId)Return all comments for the specified post
unreadCountNumber of posts that arrived since markRead() was last called
markRead()Reset the unread count to zero

Events: FeedChannel

EventPayloadDescription
postCreatedFeedPostA new post arrived from another user
postSentFeedPostConfirmation that your own post was delivered
postLiked{ postId: string, likeCount: number, likedBy: string }A post received a new like
postUnliked{ postId: string, likeCount: number, unlikedBy: string }A like was removed from a post
commentAdded{ postId: string, comment: FeedComment }A comment was added to a post
commentSent{ postId: string, comment: FeedComment }Confirmation that your own comment was delivered
subscriberJoineduser: FeedUserA user joined this channel
subscriberLeftuser: FeedUserA user left this channel
replayStart{ count: number }Historical feed replay is beginning
replayEnd{ replayed: number }Historical feed replay is complete
unreadChanged{ count: number }The unread post count for this channel changed