// Feed with no extra fields, of feed group "user"
let feed = client.feed(group: "user", id: "john")
try await feed.getOrCreate()
// More options
let query = FeedQuery(
group: "user",
id: "jack",
data: .init(
description: "My personal feed",
name: "jack",
visibility: "public"
)
)
let feed2 = client.feed(for: query)
try await feed.getOrCreate()
Feeds
Creating a Feed
// Feed with no extra fields, of feed group "user"
const feed = client.feeds.feed("user", "jack");
await feed.getOrCreate();
// Subscribe to WebSocket events for state updates
await feed.getOrCreate({ watch: true });
// More options
const feed = client.feeds.feed("user", "jack");
await feed.getOrCreate({
data: {
description: "My personal feed",
name: "jack",
visibility: "public",
},
});
// Feed with no extra fields, of feed group "user"
const feed = client.feeds.feed("user", "jack");
await feed.getOrCreate({
// The owner of the feed
user_id: "<user id>",
});
// More options
const feed = client.feeds.feed("user", "jack");
await feed.getOrCreate({
data: {
description: "My personal feed",
name: "jack",
visibility: "public",
},
// The owner of the feed
user_id: "<user id>",
});
Reading a Feed
Here is a basic example of how to read a feed:
let feed = client.feed(group: "user", id: "john")
try await feed.getOrCreate()
let feedData = feed.state.feed
let activities = feed.state.activities
let members = feed.state.members
const feed = client.feed(group: "user", id: "john")
await feed.getOrCreate({ watch: true });
const currentState = feed.state.getLatestValue();
const feedData = currentState.feed;
const activities = currentState.activities;
const members = currentState.members;
// Or subscribe to state changes
feed.state.subscribe((state) => {
// Called everytime the state changes
console.log(state);
});
// or if you care only part of the state
feed.state.subscribeWithSelector(
(state) => ({
activities: state.activities,
}),
(state, prevState) => {
console.log(state.activities, prevState.activities);
},
);
const feed = client.feed(group: "user", id: "john")
const response = await feed.getOrCreate({
user_id: '<user id>'
})
const feedData = response.feed;
const activities = response.activities;
const members = response.members;
The response will contain the following data.
You have more options when reading a feed, let’s go over a few:
let query = FeedQuery(
group: "user",
id: "john",
activityFilter: .in(.filterTags, ["green"]), // filter activities with filter tag green
activityLimit: 10,
externalRanking: ["user_score": 0.8], // additional data used for ranking
followerLimit: 10,
followingLimit: 10,
memberLimit: 10,
view: "myview", // overwrite the default ranking or aggregation logic for this feed. good for split testing
watch: true // receive web-socket events with real-time updates
)
let feed = client.feed(for: query)
try await feed.getOrCreate()
let activities = feed.state.activities
let feedData = feed.state.feed
const feed = client.feeds.feed("user", "jack");
const response = await feed.getOrCreate({
limit: 10,
filter: {
filter_tags: ["green"], // filter activities with filter tag green
},
external_ranking: {
user_score: 0.8, // additional data used for ranking
},
follower_pagination: {
limit: 10,
},
following_pagination: {
limit: 10,
},
member_pagination: {
limit: 10,
},
view: "myview", // overwrite the default ranking or aggregation logic for this feed. good for split testing
});
const feed = client.feeds.feed("user", "jack");
const response = await feed.getOrCreate({
limit: 10,
filter: {
filter_tags: ["green"], // filter activities with filter tag green
},
external_ranking: {
user_score: 0.8, // additional data used for ranking
},
follower_pagination: {
limit: 10,
},
following_pagination: {
limit: 10,
},
member_pagination: {
limit: 10,
},
view: "myview", // overwrite the default ranking or aggregation logic for this feed. good for split testing
user_id: "<user id>",
});
Feed Pagination
Here is how you can read the next page on the feed:
let feed = client.feed(
for: .init(
group: "user",
id: "john",
activityLimit: 10
)
)
// Page 1
try await feed.getOrCreate()
let activities = feed.state.activities // First 10 activities
// Page 2
let page2Activities = try await feed.queryMoreActivities(limit: 10)
let page1And2Activities = feed.state.activities
const feed = client.feeds.feed("user", "jack");
// First page
await feed.getOrCreate({
limit: 10,
user_id: "user_id",
});
// Second page
await feed.getNextPage();
console.log(feed.state.getLatestValue().activities);
const feed = client.feeds.feed("user", "jack");
const firstPage = await feed.getOrCreate({
limit: 10,
user_id: "user_id",
});
const nextPage = await feed.getOrCreate({
next: firstPage.next,
limit: 10,
user_id: "user_id",
});
Filtering Examples
Another common use case is filtering a feed. This is trickier than it seems. Keep in mind that for performance reasons feeds often have to be computed on write time. To allow for filtering we expose the following API.
// Add a few activities
let fid = FeedId(group: "user", id: "john")
try await client.upsertActivities([
ActivityRequest(fids: [fid.rawValue], filterTags: ["green", "blue"], text: "first", type: "post"),
ActivityRequest(fids: [fid.rawValue], filterTags: ["yellow", "blue"], text: "second", type: "post"),
ActivityRequest(fids: [fid.rawValue], filterTags: ["orange"], text: "third", type: "post"),
])
// Now read the feed, this will fetch activity 1 and 2
let query = FeedQuery(fid: fid, activityFilter: .in(.filterTags, value: ["blue"]))
let feed = client.feed(for: query)
try await feed.getOrCreate()
let activities = feed.state.activities // contains first and second
const feed = client.feeds.feed("user", "123");
// Add a few activities
client.upsertActivities({
activities: [
{
fids: [feed.fid],
type: "post",
text: "first",
filter_tags: ["green", "blue"],
},
{
fids: [feed.fid],
type: "post",
text: "second",
filter_tags: ["yellow", "blue"],
},
{
fids: [feed.fid],
type: "post",
text: "third",
filter_tags: ["orange"],
},
],
});
const response = await feed.getOrCreate({
filter: {
filter_tags: ["blue"],
},
});
const feed = client.feeds.feed("user", "123");
// Add a few activities
client.feeds.upsertActivities({
activities: [
{
fids: [feed.fid],
type: "post",
text: "first",
filter_tags: ["green", "blue"],
user_id: "<user id>",
},
{
fids: [feed.fid],
type: "post",
text: "second",
filter_tags: ["yellow", "blue"],
user_id: "<user id>",
},
{
fids: [feed.fid],
type: "post",
text: "third",
filter_tags: ["orange"],
user_id: "<user id>",
},
],
});
const response = await feed.getOrCreate({
filter: {
filter_tags: ["blue"],
},
user_id: "<user id>",
});
The filter syntax also supports $or
and $and
, so here’s an example that’s a little more complicated:
// Get all the activities where tags contain "green" and type is "post" or tag contains "orange" and type is "activity"
let query = FeedQuery(
group: "user",
id: "john",
activityFilter: .or([
.and([
.in(.filterTags, ["green"]),
.equal(.type, "post"),
]),
.and([
.in(.filterTags, ["orange"]),
.equal(.type, "activity")
])
])
)
try await feed.getOrCreate()
let activities = feed.state.activities
// Get all the activities where tags contain "green" and type is "post" or tag contains "orange" and type is "activity"
const response = await feed.getOrCreate({
filter: {
$or: [
{
$and: [{ filter_tags: ["green"] }, { type: "post" }],
},
{
$and: [{ filter_tags: ["orange"] }, { type: "activity" }],
},
],
},
});
// Get all the activities where tags contain "green" and type is "post" or tag contains "orange" and type is "activity"
const response = await feed.getOrCreate({
filter: {
$or: [
{
$and: [{ filter_tags: ["green"] }, { type: "post" }],
},
{
$and: [{ filter_tags: ["orange"] }, { type: "activity" }],
},
],
},
user_id: "<user id>",
});
Feed Members
You can add and remove members to a feed. This is useful for building communities where a set of users can add content to the feed.
// The following methods are available to edit the members of a feed
try await feed.updateFeedMembers(
request: .init(
members: [.init(
custom: ["joined": "2024-01-01"],
role: "moderator",
userId: "john"
)],
operation: .upsert
)
)
// Remove members
try await feed.updateFeedMembers(
request: .init(
members: [.init(userId: "john"), .init(userId: "jane")],
operation: .remove
)
)
// Set members (overwrites the list)
try await feed.updateFeedMembers(
request: .init(
members: [.init(role: "moderator", userId: "john")],
operation: .set
)
)
// The following methods are available to add or edit the members of a feed
await feed.updateFeedMembers({
operation: "upsert",
members: [
{
user_id: "john",
role: "moderator",
custom: {
joined: "2024-01-01",
},
},
],
});
// Remove members
await feed.updateFeedMembers({
operation: "remove",
members: [
{
user_id: "john",
},
{
user_id: "jane",
},
],
});
// Set members (overwrites the list)
await feed.updateFeedMembers({
operation: "set",
members: [
{
user_id: "john",
role: "moderator",
},
],
});
// The following methods are available to add or edit the members of a feed
await feed.updateFeedMembers({
operation: "upsert",
members: [
{
user_id: "john",
role: "moderator",
custom: {
joined: "2024-01-01",
},
},
],
});
// Remove members
await feed.updateFeedMembers({
operation: "remove",
members: [
{
user_id: "john",
},
{
user_id: "jane",
},
],
});
// Set members (overwrites the list)
await feed.updateFeedMembers({
operation: "set",
members: [
{
user_id: "john",
role: "moderator",
},
],
});
Member invites
You can invite members with the invite
flag, where invited users can accept or reject the membership.
// Request to become a member
try await feed.updateFeedMembers(
request: .init(
members: [.init(
custom: ["reason": "community builder"],
invite: true,
role: "moderator",
userId: "john"
)],
operation: .upsert
)
)
// Accept and reject member requests
_ = try await feed.acceptFeedMember()
_ = try await feed.rejectFeedMember()
await invitingFeed.updateFeedMembers({
operation: "upsert",
members: [
{
user_id: "john",
role: "moderator",
invite: true,
custom: {
reason: "community builder",
},
},
],
});
const feed = johnClient.feed(invitingFeed.group, invitingFeed.id);
// Then John can accept or reject
await feed.acceptFeedMemberInvite();
await feed.rejectFeedMemberInvite();
await feed.updateFeedMembers({
operation: "upsert",
members: [
{
user_id: "john",
role: "moderator",
invite: true,
custom: {
reason: "community builder",
},
},
],
});
await feed.acceptFeedMemberInvite({
user_id: "john",
});
await feed.rejectFeedMemberInvite({
user_id: "john",
});
Query Feeds
Querying feeds allows you to do things like showing the list of communities you’ve joined.
Here’s an example of how to query feeds:
Querying My Feeds
let query = FeedsQuery(
filter: .equal(.createdById, "john"),
sort: [Sort(field: .createdAt, direction: .reverse)],
limit: 10,
watch: true
)
let feedList = client.feedList(for: query)
// Page 1
let page1 = try await feedList.get()
// Page 2
let page2 = try await feedList.queryMoreFeeds(limit: 10)
let page1And2 = feedList.state.feeds
const firstPage = await client.queryFeeds({
filter: {
created_by_id: "john",
},
limit: 10,
sort: [{ field: "created_at", direction: -1 }],
});
const secondPage = await client.queryFeeds({
filter: {
created_by_id: "john",
},
limit: 10,
sort: [{ field: "created_at", direction: -1 }],
next: firstPage.next,
});
const firstPage = await client.feeds.feedsQueryFeeds({
filter: {
created_by_id: "john",
},
limit: 10,
sort: [{ field: "created_at", direction: -1 }],
});
const secondPage = await client.feeds.feedsQueryFeeds({
filter: {
created_by_id: "john",
},
limit: 10,
sort: [{ field: "created_at", direction: -1 }],
next: firstPage.next,
});
Querying Feeds Where I Am a Member
let query = FeedsQuery(
filter: .contains(.members, "john")
)
let feedList = client.feedList(for: query)
let feeds = try await feedList.get()
await client.queryFeeds({
filter: {
members: { $in: ["john"] },
},
});
await client.feeds.feedsQueryFeeds({
filter: {
members: { $in: ["john"] },
},
});
Querying feeds by name or description
let sportsQuery = FeedsQuery(
filter: .and([
.equal(.visibility, "public"),
.query(.name, "Sports")
])
)
let sportsFeedList = client.feedList(for: sportsQuery)
let sportsFeeds = try await sportsFeedList.get()
let techQuery = FeedsQuery(
filter: .and([
.equal(.visibility, "public"),
.autocomplete(.description, "tech")
])
)
let techFeedList = client.feedList(for: techQuery)
let techFeeds = try await techFeedList.get()
await client.queryFeeds({
filter: {
visibility: { $eq: "public" },
name: { $q: "Sports" },
},
});
// search public feeds by description
await client.queryFeeds({
filter: {
// Shorthand for the $eq operator
visibility: "public",
description: { $autocomplete: "tech" },
},
});
await client.feeds.feedsQueryFeeds({
filter: {
visibility: { $eq: "public" },
name: { $q: "Sports" },
},
});
// search public feeds by description
await client.feeds.feedsQueryFeeds({
filter: {
// Shorthand for the $eq operator
visibility: "public",
description: { $autocomplete: "tech" },
},
});
Querying feeds by creator name
let query = FeedsQuery(
filter: .and([
.equal(.visibility, "public"),
.query(.createdByName, "Thompson")
])
)
let feedList = client.feedList(for: query)
let feeds = try await feedList.get()
// search public feeds created by users with 'Thompson' in their name
await client.queryFeeds({
filter: {
visibility: "public",
"created_by.name": { $q: "Thompson" },
},
});
// search public feeds created by users with 'Thompson' in their name
await client.feeds.feedsQueryFeeds({
filter: {
visibility: "public",
"created_by.name": { $q: "Thompson" },
},
});
Feeds Queryable Built-in Fields
name | type | description | supported operations | example |
---|---|---|---|---|
id | string or list of strings | The ID of the feed | $in , $eq | { id: { $in: [ 'abc', 'xyz' ] } } |
group_id | string or list of strings | The ID of the group this feed belongs to | $in , $eq | { group_id: { $in: [ 'abc', 'xyz' ] } } |
fid | string or list of strings | The fully qualified feed ID (group_id:id) | $in , $eq | { fid: { $in: [ 'abc', 'xyz' ] } } |
visibility | string or list of strings | The visibility setting of the feed | $in , $eq | { visibility: { $eq: 'public' } } |
created_by_id | string or list of strings | The ID of the user who created the feed | $in , $eq | { created_by_id: { $in: [ 'abc', 'xyz' ] } } |
created_by.name | string | The name of the user who created the feed | $eq , $q , $autocomplete | { 'created_by.name': { $autocomplete: 'Frank' } } |
name | string | The name of the feed | $eq , $q , $autocomplete | { name: { $q: 'Sports' } } |
description | string | The description of the feed | $eq , $q , $autocomplete | { description: { $autocomplete: 'tech' } } |
member_count | number | The number of members in this feed | $eq , $ne , $gt , $lt , $gte , $lte | { member_count: { $gt: 100 } } |
members | list of strings | The list of members in this feed | $in | { members: { $in: [ 'bob', 'alice' ] } } |
following_count | number | The number of feeds this feed follows | $eq , $ne , $gt , $lt , $gte , $lte | { following_count: { $gt: 100 } } |
following_feeds | list of strings | The list of feeds this feed follows | $in | { following_feeds: { $in: [ 'feed1', 'feed2' ] } } |
follower_count | number | The number of followers of this feed | $eq , $ne , $gt , $lt , $gte , $lte | { follower_count: { $gt: 100 } } |
created_at | string, RFC3339 timestamp | The time the feed was created | $eq , $gt , $lt , $gte , $lte | { created_at: { $gte: '2023-12-04T09:30:20.45Z' } } |
updated_at | string, RFC3339 timestamp | The time the feed was updated | $eq , $gt , $lt , $gte , $lte | { updated_at: { $gte: '2023-12-04T09:30:20.45Z' } } |
Feeds can be sorted by created_at
, updated_at
, member_count
, follower_count
, and following_count
.
Be sure to reach out to support if you need additional query feed capabilities.