birdclaw 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/CHANGELOG.md +92 -1
  2. package/README.md +75 -5
  3. package/package.json +8 -2
  4. package/scripts/browser-perf.mjs +1 -0
  5. package/scripts/start-test-server.mjs +16 -3
  6. package/src/cli.ts +812 -37
  7. package/src/components/AccountSwitcher.tsx +186 -0
  8. package/src/components/AppNav.tsx +37 -7
  9. package/src/components/AvatarChip.tsx +9 -3
  10. package/src/components/DmWorkspace.tsx +18 -8
  11. package/src/components/LinkPreviewCard.tsx +40 -18
  12. package/src/components/MarkdownViewer.tsx +818 -0
  13. package/src/components/ProfileAnalysisStream.tsx +428 -0
  14. package/src/components/ProfilePreview.tsx +120 -9
  15. package/src/components/SavedTimelineView.tsx +30 -8
  16. package/src/components/SyncNowButton.tsx +60 -25
  17. package/src/components/ThemeSlider.tsx +55 -50
  18. package/src/components/TimelineCard.tsx +240 -92
  19. package/src/components/TimelineRouteFrame.tsx +38 -8
  20. package/src/components/TweetMediaGrid.tsx +87 -38
  21. package/src/components/TweetRichText.tsx +45 -17
  22. package/src/components/account-selection.ts +64 -0
  23. package/src/components/useTimelineRouteData.ts +97 -13
  24. package/src/lib/account-sync-job.ts +666 -0
  25. package/src/lib/actions-transport.ts +216 -146
  26. package/src/lib/api-client.ts +128 -53
  27. package/src/lib/archive-finder.ts +78 -63
  28. package/src/lib/archive-import.ts +1593 -1291
  29. package/src/lib/authored-live.ts +262 -204
  30. package/src/lib/avatar-cache.ts +208 -43
  31. package/src/lib/backup.ts +1536 -954
  32. package/src/lib/bird-actions.ts +139 -57
  33. package/src/lib/bird-command.ts +101 -28
  34. package/src/lib/bird.ts +582 -194
  35. package/src/lib/blocklist.ts +40 -23
  36. package/src/lib/blocks-write.ts +129 -80
  37. package/src/lib/blocks.ts +165 -97
  38. package/src/lib/bookmark-sync-job.ts +250 -160
  39. package/src/lib/config.ts +35 -2
  40. package/src/lib/conversation-surface.ts +79 -48
  41. package/src/lib/data-sources.ts +219 -0
  42. package/src/lib/db.ts +95 -4
  43. package/src/lib/dms-live.ts +720 -66
  44. package/src/lib/effect-runtime.ts +45 -0
  45. package/src/lib/follow-graph.ts +224 -180
  46. package/src/lib/geocoding.ts +296 -0
  47. package/src/lib/http-effect.ts +222 -0
  48. package/src/lib/inbox.ts +74 -43
  49. package/src/lib/link-index.ts +88 -76
  50. package/src/lib/link-insights.ts +24 -0
  51. package/src/lib/link-preview-metadata.ts +472 -52
  52. package/src/lib/location.ts +137 -0
  53. package/src/lib/media-fetch.ts +286 -213
  54. package/src/lib/mention-threads-live.ts +445 -288
  55. package/src/lib/mentions-live.ts +549 -354
  56. package/src/lib/moderation-target.ts +102 -65
  57. package/src/lib/moderation-write.ts +77 -18
  58. package/src/lib/mutes-write.ts +129 -80
  59. package/src/lib/mutes.ts +8 -1
  60. package/src/lib/network-map.ts +382 -0
  61. package/src/lib/openai.ts +84 -53
  62. package/src/lib/period-digest.ts +1317 -0
  63. package/src/lib/profile-affiliation-hydration.ts +93 -54
  64. package/src/lib/profile-analysis.ts +1272 -0
  65. package/src/lib/profile-bio-entities.ts +1 -1
  66. package/src/lib/profile-hydration.ts +124 -72
  67. package/src/lib/profile-replies.ts +60 -43
  68. package/src/lib/profile-resolver.ts +402 -294
  69. package/src/lib/queries.ts +983 -203
  70. package/src/lib/research.ts +165 -120
  71. package/src/lib/search-discussion.ts +1016 -0
  72. package/src/lib/sqlite.ts +1 -0
  73. package/src/lib/timeline-collections-live.ts +204 -167
  74. package/src/lib/timeline-live.ts +325 -51
  75. package/src/lib/tweet-account-edges.ts +2 -0
  76. package/src/lib/tweet-lookup.ts +30 -19
  77. package/src/lib/tweet-render.ts +141 -1
  78. package/src/lib/tweet-search-live.ts +565 -0
  79. package/src/lib/types.ts +75 -3
  80. package/src/lib/ui.ts +31 -8
  81. package/src/lib/url-expansion.ts +226 -55
  82. package/src/lib/url-safety.ts +220 -0
  83. package/src/lib/web-sync.ts +222 -149
  84. package/src/lib/whois.ts +166 -147
  85. package/src/lib/x-web.ts +102 -71
  86. package/src/lib/xurl-rate-limits.ts +267 -0
  87. package/src/lib/xurl.ts +1185 -405
  88. package/src/routeTree.gen.ts +273 -0
  89. package/src/routes/__root.tsx +24 -5
  90. package/src/routes/api/action.tsx +127 -78
  91. package/src/routes/api/avatar.tsx +39 -30
  92. package/src/routes/api/blocks.tsx +26 -23
  93. package/src/routes/api/conversation.tsx +25 -14
  94. package/src/routes/api/data-sources.tsx +24 -0
  95. package/src/routes/api/inbox.tsx +27 -21
  96. package/src/routes/api/link-insights.tsx +31 -25
  97. package/src/routes/api/link-preview.tsx +25 -21
  98. package/src/routes/api/network-map.tsx +55 -0
  99. package/src/routes/api/period-digest.tsx +133 -0
  100. package/src/routes/api/profile-analysis.tsx +152 -0
  101. package/src/routes/api/profile-hydrate.tsx +31 -28
  102. package/src/routes/api/query.tsx +80 -55
  103. package/src/routes/api/search-discussion.tsx +169 -0
  104. package/src/routes/api/status.tsx +18 -10
  105. package/src/routes/api/sync.tsx +75 -29
  106. package/src/routes/api/xurl-rate-limits.tsx +24 -0
  107. package/src/routes/data-sources.tsx +255 -0
  108. package/src/routes/discuss.tsx +419 -0
  109. package/src/routes/dms.tsx +95 -28
  110. package/src/routes/inbox.tsx +32 -19
  111. package/src/routes/network-map.tsx +1035 -0
  112. package/src/routes/profile-analyze.tsx +112 -0
  113. package/src/routes/profiles.$handle.tsx +228 -0
  114. package/src/routes/rate-limits.tsx +309 -0
  115. package/src/routes/today.tsx +455 -0
  116. package/src/styles.css +22 -0
@@ -1,5 +1,7 @@
1
1
  import type { Database } from "./sqlite";
2
+ import { Effect } from "effect";
2
3
  import { getNativeDb } from "./db";
4
+ import { runEffectPromise, tryPromise } from "./effect-runtime";
3
5
  import { readSyncCache, writeSyncCache } from "./sync-cache";
4
6
  import type {
5
7
  TweetEntities,
@@ -26,6 +28,15 @@ import {
26
28
 
27
29
  export type AuthoredSyncMode = "xurl";
28
30
 
31
+ export interface SyncAuthoredTweetsOptions {
32
+ account?: string;
33
+ mode?: AuthoredSyncMode;
34
+ limit?: number;
35
+ maxPages?: number;
36
+ sinceId?: string;
37
+ untilId?: string;
38
+ }
39
+
29
40
  export class AuthoredSyncError extends Error {
30
41
  constructor(
31
42
  message: string,
@@ -108,6 +119,17 @@ const AUTHORED_MEDIA_FIELDS = [
108
119
  "alt_text",
109
120
  ];
110
121
 
122
+ function toError(error: unknown) {
123
+ return error instanceof Error ? error : new Error(String(error));
124
+ }
125
+
126
+ function trySync<T>(try_: () => T) {
127
+ return Effect.try({
128
+ try: try_,
129
+ catch: toError,
130
+ });
131
+ }
132
+
111
133
  function assertXurlLimit(limit: number) {
112
134
  if (
113
135
  !Number.isFinite(limit) ||
@@ -413,57 +435,65 @@ function userFromAuthenticatedPayload(
413
435
  };
414
436
  }
415
437
 
416
- async function resolveAuthoredIdentity({
438
+ function resolveAuthoredIdentityEffect({
417
439
  account,
418
440
  db,
419
441
  }: {
420
442
  account?: string;
421
443
  db: Database;
422
444
  }) {
423
- const status = await getTransportStatus();
424
- if (status.availableTransport !== "xurl") {
425
- throw new AuthoredSyncError(status.statusText, 4);
426
- }
445
+ return Effect.gen(function* () {
446
+ const status = yield* tryPromise(() => getTransportStatus());
447
+ if (status.availableTransport !== "xurl") {
448
+ return yield* Effect.fail(new AuthoredSyncError(status.statusText, 4));
449
+ }
427
450
 
428
- const resolvedAccount = resolveAccount(db, account);
429
- if (resolvedAccount.externalUserId) {
430
- return {
431
- ...resolvedAccount,
432
- userId: resolvedAccount.externalUserId,
433
- authenticatedUser: undefined,
434
- };
435
- }
451
+ const resolvedAccount = yield* trySync(() => resolveAccount(db, account));
452
+ if (resolvedAccount.externalUserId) {
453
+ return {
454
+ ...resolvedAccount,
455
+ userId: resolvedAccount.externalUserId,
456
+ authenticatedUser: undefined,
457
+ };
458
+ }
436
459
 
437
- const authenticated = await lookupAuthenticatedUser();
438
- const authenticatedUser = userFromAuthenticatedPayload(authenticated);
439
- if (!authenticatedUser?.id) {
440
- throw new AuthoredSyncError(
441
- "Could not resolve authenticated Twitter user id",
442
- 4,
443
- );
444
- }
460
+ const authenticated = yield* tryPromise(() => lookupAuthenticatedUser());
461
+ const authenticatedUser = userFromAuthenticatedPayload(authenticated);
462
+ if (!authenticatedUser?.id) {
463
+ return yield* Effect.fail(
464
+ new AuthoredSyncError(
465
+ "Could not resolve authenticated Twitter user id",
466
+ 4,
467
+ ),
468
+ );
469
+ }
445
470
 
446
- if (
447
- normalizeUsername(authenticatedUser.username) !==
448
- normalizeUsername(resolvedAccount.username)
449
- ) {
450
- throw new AuthoredSyncError(
451
- `xurl is authenticated as @${authenticatedUser.username}, but selected account ${resolvedAccount.accountId} is @${resolvedAccount.username}. Link the account external_user_id or switch xurl login before syncing authored tweets.`,
452
- 4,
453
- );
454
- }
471
+ if (
472
+ normalizeUsername(authenticatedUser.username) !==
473
+ normalizeUsername(resolvedAccount.username)
474
+ ) {
475
+ return yield* Effect.fail(
476
+ new AuthoredSyncError(
477
+ `xurl is authenticated as @${authenticatedUser.username}, but selected account ${resolvedAccount.accountId} is @${resolvedAccount.username}. Link the account external_user_id or switch xurl login before syncing authored tweets.`,
478
+ 4,
479
+ ),
480
+ );
481
+ }
455
482
 
456
- persistAccountExternalUserId(
457
- db,
458
- resolvedAccount.accountId,
459
- authenticatedUser.id,
460
- );
483
+ yield* trySync(() =>
484
+ persistAccountExternalUserId(
485
+ db,
486
+ resolvedAccount.accountId,
487
+ authenticatedUser.id,
488
+ ),
489
+ );
461
490
 
462
- return {
463
- ...resolvedAccount,
464
- userId: authenticatedUser.id,
465
- authenticatedUser,
466
- };
491
+ return {
492
+ ...resolvedAccount,
493
+ userId: authenticatedUser.id,
494
+ authenticatedUser,
495
+ };
496
+ });
467
497
  }
468
498
 
469
499
  function toFallbackUser({
@@ -887,188 +917,216 @@ function buildResult({
887
917
  };
888
918
  }
889
919
 
890
- export async function syncAuthoredTweets({
920
+ export function syncAuthoredTweetsEffect({
891
921
  account,
892
922
  mode = "xurl",
893
923
  limit = DEFAULT_LIMIT,
894
924
  maxPages,
895
925
  sinceId,
896
926
  untilId,
897
- }: {
898
- account?: string;
899
- mode?: AuthoredSyncMode;
900
- limit?: number;
901
- maxPages?: number;
902
- sinceId?: string;
903
- untilId?: string;
904
- }) {
905
- if (mode !== "xurl") {
906
- throw new Error("authored sync only supports --mode xurl");
907
- }
927
+ }: SyncAuthoredTweetsOptions) {
928
+ return Effect.gen(function* () {
929
+ if (mode !== "xurl") {
930
+ return yield* Effect.fail(
931
+ new Error("authored sync only supports --mode xurl"),
932
+ );
933
+ }
908
934
 
909
- const pageLimit = assertXurlLimit(limit);
910
- const parsedMaxPages = parseMaxPages(maxPages);
911
- const db = getNativeDb();
912
- const identity = await resolveAuthoredIdentity({ account, db });
913
- const cursor = readAuthoredCursor(db, identity.accountId);
914
- const usePersistedForward =
915
- sinceId === undefined && !untilId && cursor.state === "pending-forward";
916
- const usePersistedUntil =
917
- sinceId === undefined &&
918
- Boolean(untilId) &&
919
- cursor.state === "pending-until" &&
920
- cursor.untilId === untilId;
921
- const shouldSeedFromArchive =
922
- !usePersistedForward &&
923
- !cursor.sinceId &&
924
- sinceId === undefined &&
925
- !untilId;
926
- const archiveSinceSeed = shouldSeedFromArchive
927
- ? findArchiveAuthoredSinceSeed(db, identity.accountId)
928
- : null;
929
- if (shouldSeedFromArchive && !archiveSinceSeed) {
930
- console.error(
931
- "birdclaw sync authored: no archive baseline found; starting a full backwards scan",
935
+ const pageLimit = yield* trySync(() => assertXurlLimit(limit));
936
+ const parsedMaxPages = yield* trySync(() => parseMaxPages(maxPages));
937
+ const db = yield* trySync(() => getNativeDb());
938
+ const identity = yield* resolveAuthoredIdentityEffect({ account, db });
939
+ const cursor = yield* trySync(() =>
940
+ readAuthoredCursor(db, identity.accountId),
932
941
  );
933
- }
934
- const persistedUntilSinceId: string | null = usePersistedUntil
935
- ? (("requestedSinceId" in cursor
936
- ? cursor.requestedSinceId
937
- : cursor.sinceId) ?? null)
938
- : null;
939
- const effectiveSinceId: string | null =
940
- sinceId ??
941
- archiveSinceSeed ??
942
- (untilId ? persistedUntilSinceId : cursor.sinceId) ??
943
- null;
944
- let nextToken = usePersistedForward
945
- ? cursor.token
946
- : usePersistedUntil
942
+ const usePersistedForward =
943
+ sinceId === undefined && !untilId && cursor.state === "pending-forward";
944
+ const usePersistedUntil =
945
+ sinceId === undefined &&
946
+ Boolean(untilId) &&
947
+ cursor.state === "pending-until" &&
948
+ cursor.untilId === untilId;
949
+ const shouldSeedFromArchive =
950
+ !usePersistedForward &&
951
+ !cursor.sinceId &&
952
+ sinceId === undefined &&
953
+ !untilId;
954
+ const archiveSinceSeed = shouldSeedFromArchive
955
+ ? yield* trySync(() =>
956
+ findArchiveAuthoredSinceSeed(db, identity.accountId),
957
+ )
958
+ : null;
959
+ if (shouldSeedFromArchive && !archiveSinceSeed) {
960
+ console.error(
961
+ "birdclaw sync authored: no archive baseline found; starting a full backwards scan",
962
+ );
963
+ }
964
+ const persistedUntilSinceId: string | null = usePersistedUntil
965
+ ? (("requestedSinceId" in cursor
966
+ ? cursor.requestedSinceId
967
+ : cursor.sinceId) ?? null)
968
+ : null;
969
+ const effectiveSinceId: string | null =
970
+ sinceId ??
971
+ archiveSinceSeed ??
972
+ (untilId ? persistedUntilSinceId : cursor.sinceId) ??
973
+ null;
974
+ let nextToken = usePersistedForward
947
975
  ? cursor.token
948
- : undefined;
949
- let newestSeenId = usePersistedForward
950
- ? maxTweetId(cursor.sinceId, cursor.pendingNewestId)
951
- : cursor.sinceId;
952
- const pages: XurlUserTweetsResponse[] = [];
953
- const sourceUser = toFallbackUser({
954
- userId: identity.userId,
955
- username: identity.username,
956
- authenticatedUser: identity.authenticatedUser,
957
- });
958
- let pageCount = 0;
959
-
960
- while (parsedMaxPages === null || pageCount < parsedMaxPages) {
961
- let page: XurlUserTweetsResponse;
962
- try {
963
- page = await listUserTweets(identity.userId, {
964
- maxResults: pageLimit,
965
- paginationToken: nextToken,
966
- excludeRetweets: false,
967
- sinceId: effectiveSinceId ?? undefined,
968
- untilId,
969
- tweetFields: AUTHORED_TWEET_FIELDS,
970
- expansions: AUTHORED_EXPANSIONS,
971
- userFields: AUTHORED_USER_FIELDS,
972
- mediaFields: AUTHORED_MEDIA_FIELDS,
973
- auth: "oauth2",
974
- });
975
- } catch (error) {
976
- if (pages.length === 0) {
977
- throw error;
976
+ : usePersistedUntil
977
+ ? cursor.token
978
+ : undefined;
979
+ let newestSeenId = usePersistedForward
980
+ ? maxTweetId(cursor.sinceId, cursor.pendingNewestId)
981
+ : cursor.sinceId;
982
+ const pages: XurlUserTweetsResponse[] = [];
983
+ const sourceUser = toFallbackUser({
984
+ userId: identity.userId,
985
+ username: identity.username,
986
+ authenticatedUser: identity.authenticatedUser,
987
+ });
988
+ let pageCount = 0;
989
+
990
+ while (parsedMaxPages === null || pageCount < parsedMaxPages) {
991
+ const fetched = yield* tryPromise(() =>
992
+ listUserTweets(identity.userId, {
993
+ maxResults: pageLimit,
994
+ paginationToken: nextToken,
995
+ excludeRetweets: false,
996
+ sinceId: effectiveSinceId ?? undefined,
997
+ untilId,
998
+ tweetFields: AUTHORED_TWEET_FIELDS,
999
+ expansions: AUTHORED_EXPANSIONS,
1000
+ userFields: AUTHORED_USER_FIELDS,
1001
+ mediaFields: AUTHORED_MEDIA_FIELDS,
1002
+ auth: "oauth2",
1003
+ username: identity.username,
1004
+ }),
1005
+ ).pipe(
1006
+ Effect.map((page) => ({ ok: true as const, page })),
1007
+ Effect.catchAll((error) =>
1008
+ Effect.succeed({ ok: false as const, error }),
1009
+ ),
1010
+ );
1011
+
1012
+ if (!fetched.ok) {
1013
+ if (pages.length === 0) {
1014
+ return yield* Effect.fail(fetched.error);
1015
+ }
1016
+ const payload = mergePages({
1017
+ pages,
1018
+ userId: identity.userId,
1019
+ nextToken: nextToken ?? null,
1020
+ });
1021
+ return buildResult({
1022
+ accountId: identity.accountId,
1023
+ userId: identity.userId,
1024
+ effectiveSinceId,
1025
+ nextSinceId: untilId ? cursor.sinceId : effectiveSinceId,
1026
+ nextToken: nextToken ?? null,
1027
+ pageCount,
1028
+ payload,
1029
+ partial: true,
1030
+ error: formatError(fetched.error),
1031
+ });
978
1032
  }
979
- const payload = mergePages({
980
- pages,
981
- userId: identity.userId,
982
- nextToken: nextToken ?? null,
983
- });
984
- return buildResult({
985
- accountId: identity.accountId,
1033
+
1034
+ const page = fetched.page;
1035
+ const pagePayload = mergePages({
1036
+ pages: [page],
986
1037
  userId: identity.userId,
987
- effectiveSinceId,
988
- nextSinceId: untilId ? cursor.sinceId : effectiveSinceId,
989
- nextToken: nextToken ?? null,
990
- pageCount,
991
- payload,
992
- partial: true,
993
- error: formatError(error),
1038
+ nextToken: page.nextToken,
994
1039
  });
995
- }
1040
+ yield* trySync(() =>
1041
+ mergeAuthoredPayloadIntoLocalStore({
1042
+ db,
1043
+ accountId: identity.accountId,
1044
+ payload: pagePayload,
1045
+ sourceUser,
1046
+ }),
1047
+ );
1048
+ pages.push(page);
1049
+ pageCount += 1;
1050
+ newestSeenId = maxTweetId(newestSeenId, pagePayload.meta.newest_id);
1051
+ nextToken = page.nextToken ?? undefined;
996
1052
 
997
- const pagePayload = mergePages({
998
- pages: [page],
999
- userId: identity.userId,
1000
- nextToken: page.nextToken,
1001
- });
1002
- mergeAuthoredPayloadIntoLocalStore({
1003
- db,
1004
- accountId: identity.accountId,
1005
- payload: pagePayload,
1006
- sourceUser,
1007
- });
1008
- pages.push(page);
1009
- pageCount += 1;
1010
- newestSeenId = maxTweetId(newestSeenId, pagePayload.meta.newest_id);
1011
- nextToken = page.nextToken ?? undefined;
1012
-
1013
- if (nextToken && untilId) {
1014
- writePendingUntilCursor(db, identity.accountId, {
1015
- sinceId: cursor.sinceId,
1016
- token: nextToken,
1017
- untilId,
1018
- requestedSinceId: effectiveSinceId,
1019
- });
1020
- } else if (nextToken) {
1021
- writePendingForwardCursor(db, identity.accountId, {
1022
- sinceId: effectiveSinceId,
1023
- token: nextToken,
1024
- pendingNewestId: newestSeenId,
1025
- });
1053
+ const pendingToken = nextToken;
1054
+ if (pendingToken && untilId) {
1055
+ yield* trySync(() =>
1056
+ writePendingUntilCursor(db, identity.accountId, {
1057
+ sinceId: cursor.sinceId,
1058
+ token: pendingToken,
1059
+ untilId,
1060
+ requestedSinceId: effectiveSinceId,
1061
+ }),
1062
+ );
1063
+ } else if (pendingToken) {
1064
+ yield* trySync(() =>
1065
+ writePendingForwardCursor(db, identity.accountId, {
1066
+ sinceId: effectiveSinceId,
1067
+ token: pendingToken,
1068
+ pendingNewestId: newestSeenId,
1069
+ }),
1070
+ );
1071
+ }
1072
+
1073
+ if (!nextToken) {
1074
+ break;
1075
+ }
1026
1076
  }
1027
1077
 
1028
- if (!nextToken) {
1029
- break;
1078
+ const capped = Boolean(nextToken);
1079
+ const nextSinceId = untilId
1080
+ ? cursor.sinceId
1081
+ : capped
1082
+ ? effectiveSinceId
1083
+ : maxTweetId(newestSeenId, effectiveSinceId, cursor.sinceId);
1084
+ if (untilId && capped && nextToken) {
1085
+ yield* trySync(() =>
1086
+ writePendingUntilCursor(db, identity.accountId, {
1087
+ sinceId: cursor.sinceId,
1088
+ token: nextToken,
1089
+ untilId,
1090
+ requestedSinceId: effectiveSinceId,
1091
+ }),
1092
+ );
1093
+ } else if (untilId) {
1094
+ yield* trySync(() =>
1095
+ writeCommittedCursor(db, identity.accountId, cursor.sinceId),
1096
+ );
1097
+ } else if (capped && nextToken) {
1098
+ yield* trySync(() =>
1099
+ writePendingForwardCursor(db, identity.accountId, {
1100
+ sinceId: nextSinceId,
1101
+ token: nextToken,
1102
+ pendingNewestId: newestSeenId,
1103
+ }),
1104
+ );
1105
+ } else {
1106
+ yield* trySync(() =>
1107
+ writeCommittedCursor(db, identity.accountId, nextSinceId),
1108
+ );
1030
1109
  }
1031
- }
1032
1110
 
1033
- const capped = Boolean(nextToken);
1034
- const nextSinceId = untilId
1035
- ? cursor.sinceId
1036
- : capped
1037
- ? effectiveSinceId
1038
- : maxTweetId(newestSeenId, effectiveSinceId, cursor.sinceId);
1039
- if (untilId && capped && nextToken) {
1040
- writePendingUntilCursor(db, identity.accountId, {
1041
- sinceId: cursor.sinceId,
1042
- token: nextToken,
1043
- untilId,
1044
- requestedSinceId: effectiveSinceId,
1111
+ const payload = mergePages({
1112
+ pages,
1113
+ userId: identity.userId,
1114
+ nextToken: nextToken ?? null,
1045
1115
  });
1046
- } else if (untilId) {
1047
- writeCommittedCursor(db, identity.accountId, cursor.sinceId);
1048
- } else if (capped && nextToken) {
1049
- writePendingForwardCursor(db, identity.accountId, {
1050
- sinceId: nextSinceId,
1051
- token: nextToken,
1052
- pendingNewestId: newestSeenId,
1116
+ return buildResult({
1117
+ accountId: identity.accountId,
1118
+ userId: identity.userId,
1119
+ effectiveSinceId,
1120
+ nextSinceId,
1121
+ nextToken: nextToken ?? null,
1122
+ pageCount,
1123
+ payload,
1124
+ partial: capped,
1125
+ ...(capped ? { error: "max pages reached before sync completed" } : {}),
1053
1126
  });
1054
- } else {
1055
- writeCommittedCursor(db, identity.accountId, nextSinceId);
1056
- }
1057
-
1058
- const payload = mergePages({
1059
- pages,
1060
- userId: identity.userId,
1061
- nextToken: nextToken ?? null,
1062
- });
1063
- return buildResult({
1064
- accountId: identity.accountId,
1065
- userId: identity.userId,
1066
- effectiveSinceId,
1067
- nextSinceId,
1068
- nextToken: nextToken ?? null,
1069
- pageCount,
1070
- payload,
1071
- partial: capped,
1072
- ...(capped ? { error: "max pages reached before sync completed" } : {}),
1073
1127
  });
1074
1128
  }
1129
+
1130
+ export function syncAuthoredTweets(options: SyncAuthoredTweetsOptions) {
1131
+ return runEffectPromise(syncAuthoredTweetsEffect(options));
1132
+ }