@@ -826,9 +826,9 @@ interface GetFollowersByUserIdOptions {
826
826
*/
827
827
cursor? : string | null ;
828
828
/**
829
- * The number of items per page.
829
+ * The number of items per page. If `null`, the entire collection is returned.
830
830
*/
831
- limit: number ;
831
+ limit? : number | null ;
832
832
}
833
833
/**
834
834
* A hypothetical function that returns the actors that are following an actor.
@@ -839,7 +839,7 @@ interface GetFollowersByUserIdOptions {
839
839
*/
840
840
async function getFollowersByUserId(
841
841
userId : string ,
842
- options : GetFollowersByUserIdOptions ,
842
+ options : GetFollowersByUserIdOptions = {} ,
843
843
): Promise <ResultSet > {
844
844
return { users: [], nextCursor: null , last: true };
845
845
}
@@ -874,6 +874,132 @@ federation
874
874
> Every ` Actor ` object is also a ` Recipient ` object, so you can use the ` Actor `
875
875
> object as the ` Recipient ` object.
876
876
877
+ ### One-shot followers collection for gathering recipients
878
+
879
+ When you invoke ` Context.sendActivity() ` method with setting the ` recipients `
880
+ parameter to ` "followers" ` , Fedify automatically gathers the recipients from
881
+ the followers collection. In this case, the followers collection dispatcher
882
+ is not called by remote servers, but it's called in the same process.
883
+ Therefore, you don't have much merit to paginate the followers collection,
884
+ but instead you would want to gather all the followers at once.
885
+
886
+ Under the hood, the ` Context.sendActivity() ` method tries to gather the
887
+ recipients by calling the followers collection dispatcher with the ` cursor `
888
+ parameter set to ` null ` . However, if the followers collection dispatcher
889
+ returns ` null ` , the method treats it as a signal that the followers collection
890
+ is always paginated, and it gather the recipients by paginating the followers
891
+ collection with multiple invocation of the followers collection dispatcher.
892
+ If the followers collection dispatcher returns an object that contains
893
+ the entire followers collection, the method gathers the recipients at once.
894
+
895
+ Therefore, if you use ` "followers" ` as the ` recipients ` parameter of
896
+ the ` Context.sendActivity() ` method, you should return the entire followers
897
+ collection when the ` cursor ` parameter is ` null ` :
898
+
899
+ ~~~~ typescript{5-17} twoslash
900
+ import type { Federation, Recipient } from "@fedify/fedify";
901
+ const federation = null as unknown as Federation<void>;
902
+ /**
903
+ * A hypothetical type that represents an actor in the database.
904
+ */
905
+ interface User {
906
+ /**
907
+ * The URI of the actor.
908
+ */
909
+ uri: string;
910
+ /**
911
+ * The inbox URI of the actor.
912
+ */
913
+ inboxUri: string;
914
+ }
915
+ /**
916
+ * A hypothetical type that represents the result set of the actors that
917
+ * are following an actor.
918
+ */
919
+ interface ResultSet {
920
+ /**
921
+ * The actors that are following the actor.
922
+ */
923
+ users: User[];
924
+ /**
925
+ * The next cursor that represents the position of the next page.
926
+ */
927
+ nextCursor: string | null;
928
+ /**
929
+ * Whether the current page is the last page.
930
+ */
931
+ last: boolean;
932
+ }
933
+ /**
934
+ * A hypothetical type that represents the options for
935
+ * the `getFollowersByUserId` function.
936
+ */
937
+ interface GetFollowersByUserIdOptions {
938
+ /**
939
+ * The cursor that represents the position of the current page.
940
+ */
941
+ cursor?: string | null;
942
+ /**
943
+ * The number of items per page. If `null`, the entire collection is returned.
944
+ */
945
+ limit?: number | null;
946
+ }
947
+ /**
948
+ * A hypothetical function that returns the actors that are following an actor.
949
+ * @param userId The actor's identifier.
950
+ * @param options The options for the query.
951
+ * @returns The actors that are following the actor, the next cursor, and
952
+ * whether the current page is the last page.
953
+ */
954
+ async function getFollowersByUserId(
955
+ userId: string,
956
+ options: GetFollowersByUserIdOptions = {},
957
+ ): Promise<ResultSet> {
958
+ return { users: [], nextCursor: null, last: true };
959
+ }
960
+ // ---cut-before---
961
+ federation
962
+ .setFollowersDispatcher(
963
+ "/users/{identifier}/followers",
964
+ async (ctx, identifier, cursor) => {
965
+ // If a whole collection is requested, returns the entire collection
966
+ // instead of paginating it, as we prefer one-shot gathering:
967
+ if (cursor == null) {
968
+ // Work with the database to find the actors that are following the actor
969
+ // (the below `getFollowersByUserId` is a hypothetical function):
970
+ const { users } = await getFollowersByUserId(identifier);
971
+ return {
972
+ items: users.map(actor => ({
973
+ id: new URL(actor.uri),
974
+ inboxId: new URL(actor.inboxUri),
975
+ })),
976
+ };
977
+ }
978
+ const { users, nextCursor, last } = await getFollowersByUserId(
979
+ identifier,
980
+ cursor === "" ? { limit: 10 } : { cursor, limit: 10 }
981
+ );
982
+ // Turn the users into `Recipient` objects:
983
+ const items: Recipient[] = users.map(actor => ({
984
+ id: new URL(actor.uri),
985
+ inboxId: new URL(actor.inboxUri),
986
+ }));
987
+ return { items, nextCursor: last ? null : nextCursor };
988
+ }
989
+ )
990
+ // The first cursor is an empty string:
991
+ .setFirstCursor(async (ctx, identifier) => "");
992
+ ~~~~
993
+
994
+ > [ !CAUTION]
995
+ > The common pitfall is that the followers collection dispatcher returns
996
+ > the first page of the followers collection when the ` cursor ` parameter is
997
+ > ` null ` . If the followers collection dispatcher returns only the first page
998
+ > when the ` cursor ` parameter is ` null ` , the ` Context.sendActivity() ` method
999
+ > will treat it as the entire followers collection, and it will not gather
1000
+ > the rest of the followers collection. Therefore, it will send the activity
1001
+ > only to the followers in the first page. Watch out for this pitfall.
1002
+
877
1003
### Filtering by server
878
1004
879
1005
* This API is available since Fedify 0.8.0.*
0 commit comments