birdclaw 0.8.2 → 0.8.3
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.
- package/CHANGELOG.md +16 -0
- package/package.json +2 -1
- package/src/cli/command-context.ts +17 -0
- package/src/cli/register-analysis.ts +500 -0
- package/src/cli/register-compose.ts +40 -0
- package/src/cli/register-graph.ts +132 -0
- package/src/cli/register-inbox.ts +41 -0
- package/src/cli/register-storage.ts +106 -0
- package/src/cli.ts +30 -750
- package/src/components/AccountSwitcher.tsx +7 -15
- package/src/components/AvatarChip.tsx +1 -1
- package/src/components/AvatarPreload.ts +149 -0
- package/src/components/MarkdownCitations.tsx +680 -0
- package/src/components/MarkdownViewer.tsx +8 -674
- package/src/components/ProfileAnalysisClient.ts +191 -0
- package/src/components/ProfileAnalysisStream.tsx +16 -185
- package/src/components/ProfilePreview.tsx +2 -0
- package/src/components/links-controller.ts +162 -0
- package/src/components/links-model.ts +198 -0
- package/src/components/network-map-controller.ts +84 -0
- package/src/components/network-map-model.ts +255 -0
- package/src/components/useTimelineRouteData.ts +105 -235
- package/src/lib/analysis-runtime.ts +238 -0
- package/src/lib/api-client.ts +16 -215
- package/src/lib/api-contracts.ts +328 -0
- package/src/lib/archive-import-plan.ts +102 -0
- package/src/lib/archive-import.ts +170 -239
- package/src/lib/authored-live.ts +75 -120
- package/src/lib/backup.ts +335 -424
- package/src/lib/blocks-write.ts +30 -26
- package/src/lib/blocks.ts +18 -20
- package/src/lib/database-metrics.ts +88 -0
- package/src/lib/database-migrations.ts +34 -0
- package/src/lib/database-schema.ts +312 -0
- package/src/lib/database-writer.ts +69 -0
- package/src/lib/db.ts +84 -330
- package/src/lib/dm-read-model.ts +533 -0
- package/src/lib/dms-live.ts +34 -97
- package/src/lib/follow-graph.ts +17 -27
- package/src/lib/import-repository.ts +138 -0
- package/src/lib/inbox.ts +2 -1
- package/src/lib/live-sync-engine.ts +209 -0
- package/src/lib/live-transport-gateway.ts +128 -0
- package/src/lib/mention-threads-live.ts +90 -177
- package/src/lib/mentions-export.ts +1 -1
- package/src/lib/mentions-live.ts +57 -181
- package/src/lib/moderation-target.ts +15 -4
- package/src/lib/moderation-write.ts +1 -1
- package/src/lib/mutes-write.ts +30 -26
- package/src/lib/openai-response-runtime.ts +251 -0
- package/src/lib/paginated-sync.ts +93 -0
- package/src/lib/period-digest.ts +116 -304
- package/src/lib/profile-analysis.ts +36 -110
- package/src/lib/queries.ts +6 -2381
- package/src/lib/query-actions.ts +437 -0
- package/src/lib/query-client.tsx +47 -0
- package/src/lib/query-read-model-shared.ts +52 -0
- package/src/lib/query-read-models.ts +5 -0
- package/src/lib/query-resource.ts +41 -0
- package/src/lib/query-status.ts +164 -0
- package/src/lib/research.ts +1 -1
- package/src/lib/runtime-services.ts +20 -0
- package/src/lib/search-discussion.ts +75 -279
- package/src/lib/server-runtime-services.ts +30 -0
- package/src/lib/sqlite.ts +48 -12
- package/src/lib/streaming-ingestion.ts +240 -0
- package/src/lib/sync-cache.ts +6 -1
- package/src/lib/sync-plan.ts +175 -0
- package/src/lib/timeline-collections-live.ts +83 -257
- package/src/lib/timeline-live.ts +86 -236
- package/src/lib/timeline-read-model.ts +1191 -0
- package/src/lib/tweet-repository.ts +156 -0
- package/src/lib/tweet-search-live.ts +63 -167
- package/src/lib/web-sync.ts +67 -50
- package/src/lib/whois.ts +2 -1
- package/src/routes/__root.tsx +11 -8
- package/src/routes/api/action.tsx +1 -1
- package/src/routes/api/conversation.tsx +1 -1
- package/src/routes/api/query.tsx +32 -26
- package/src/routes/api/status.tsx +6 -4
- package/src/routes/api/sync.tsx +5 -2
- package/src/routes/blocks.tsx +97 -131
- package/src/routes/data-sources.tsx +17 -25
- package/src/routes/dms.tsx +167 -184
- package/src/routes/inbox.tsx +63 -57
- package/src/routes/links.tsx +31 -394
- package/src/routes/network-map.tsx +41 -344
- package/src/routes/rate-limits.tsx +17 -21
- package/src/lib/client-cache.ts +0 -109
package/src/lib/timeline-live.ts
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import type { Database } from "./sqlite";
|
|
3
|
-
import { listHomeTimelineViaBirdEffect } from "./bird";
|
|
4
3
|
import { getNativeDb } from "./db";
|
|
5
4
|
import { runEffectPromise } from "./effect-runtime";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
5
|
+
import { liveTransportGateway } from "./live-transport-gateway";
|
|
6
|
+
import {
|
|
7
|
+
createLiveTransportAdapter,
|
|
8
|
+
normalizeCacheTtlMs,
|
|
9
|
+
resolveLiveSyncAccount,
|
|
10
|
+
runCachedLiveSyncEffect,
|
|
11
|
+
} from "./live-sync-engine";
|
|
12
|
+
import { collectPaginatedEffect } from "./paginated-sync";
|
|
9
13
|
import type {
|
|
10
14
|
XurlMediaItem,
|
|
11
15
|
XurlMentionUser,
|
|
12
16
|
XurlMentionsResponse,
|
|
13
17
|
} from "./types";
|
|
14
|
-
import {
|
|
15
|
-
import { ensureStubProfileForXUser, upsertProfileFromXUser } from "./x-profile";
|
|
16
|
-
import { listHomeTimelineViaXurlEffect } from "./xurl";
|
|
18
|
+
import { ingestTweetPayload } from "./tweet-repository";
|
|
17
19
|
|
|
18
20
|
const DEFAULT_TIMELINE_CACHE_TTL_MS = 2 * 60_000;
|
|
19
21
|
const MAX_XURL_TIMELINE_PAGE_SIZE = 100;
|
|
@@ -41,13 +43,6 @@ export interface SyncHomeTimelineOptions {
|
|
|
41
43
|
onProgress?: (progress: HomeTimelineProgress) => void;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
function parseCacheTtlMs(value?: number) {
|
|
45
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
46
|
-
return DEFAULT_TIMELINE_CACHE_TTL_MS;
|
|
47
|
-
}
|
|
48
|
-
return Math.floor(value);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
46
|
function assertLimit(limit: number) {
|
|
52
47
|
if ((!Number.isFinite(limit) && limit !== Infinity) || limit < 1) {
|
|
53
48
|
throw new Error("--limit must be at least 1");
|
|
@@ -90,14 +85,6 @@ function reachedStartTimeBoundary(
|
|
|
90
85
|
});
|
|
91
86
|
}
|
|
92
87
|
|
|
93
|
-
function getReferencedTweetId(
|
|
94
|
-
tweet: XurlMentionsResponse["data"][number],
|
|
95
|
-
type: "replied_to" | "quoted",
|
|
96
|
-
) {
|
|
97
|
-
return tweet.referenced_tweets?.find((reference) => reference.type === type)
|
|
98
|
-
?.id;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
88
|
function mergeTimelinePayloads(
|
|
102
89
|
payloads: XurlMentionsResponse[],
|
|
103
90
|
limit: number,
|
|
@@ -133,133 +120,19 @@ function mergeTimelinePayloads(
|
|
|
133
120
|
} satisfies XurlMentionsResponse;
|
|
134
121
|
}
|
|
135
122
|
|
|
136
|
-
function resolveAccount(db: Database, accountId?: string) {
|
|
137
|
-
const row = accountId
|
|
138
|
-
? (db
|
|
139
|
-
.prepare(
|
|
140
|
-
"select id, handle, external_user_id, is_default as isDefault from accounts where id = ?",
|
|
141
|
-
)
|
|
142
|
-
.get(accountId) as
|
|
143
|
-
| ({ id: string; handle: string; external_user_id: string | null } & {
|
|
144
|
-
isDefault: number;
|
|
145
|
-
})
|
|
146
|
-
| undefined)
|
|
147
|
-
: (db
|
|
148
|
-
.prepare(
|
|
149
|
-
`
|
|
150
|
-
select id, handle, external_user_id, is_default as isDefault
|
|
151
|
-
from accounts
|
|
152
|
-
order by is_default desc, created_at asc
|
|
153
|
-
limit 1
|
|
154
|
-
`,
|
|
155
|
-
)
|
|
156
|
-
.get() as
|
|
157
|
-
| ({ id: string; handle: string; external_user_id: string | null } & {
|
|
158
|
-
isDefault: number;
|
|
159
|
-
})
|
|
160
|
-
| undefined);
|
|
161
|
-
|
|
162
|
-
if (!row) {
|
|
163
|
-
throw new Error(`Unknown account: ${accountId ?? "default"}`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
accountId: row.id,
|
|
168
|
-
isDefault: row.isDefault === 1,
|
|
169
|
-
username: row.handle.replace(/^@/, ""),
|
|
170
|
-
externalUserId:
|
|
171
|
-
typeof row.external_user_id === "string" &&
|
|
172
|
-
row.external_user_id.trim().length > 0
|
|
173
|
-
? row.external_user_id.trim()
|
|
174
|
-
: undefined,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function replaceTweetFts(db: Database, tweetId: string, text: string) {
|
|
179
|
-
db.prepare("delete from tweets_fts where tweet_id = ?").run(tweetId);
|
|
180
|
-
db.prepare("insert into tweets_fts (tweet_id, text) values (?, ?)").run(
|
|
181
|
-
tweetId,
|
|
182
|
-
text,
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
123
|
function mergeHomeTimelineIntoLocalStore(
|
|
187
124
|
db: Database,
|
|
188
125
|
accountId: string,
|
|
189
126
|
payload: XurlMentionsResponse,
|
|
190
127
|
source: "bird" | "xurl",
|
|
191
128
|
) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
is_replied, reply_to_id, like_count, media_count, bookmarked, liked,
|
|
200
|
-
entities_json, media_json, quoted_tweet_id
|
|
201
|
-
) values (?, ?, ?, 'home', ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, ?)
|
|
202
|
-
on conflict(id) do update set
|
|
203
|
-
account_id = tweets.account_id,
|
|
204
|
-
author_profile_id = excluded.author_profile_id,
|
|
205
|
-
kind = tweets.kind,
|
|
206
|
-
text = excluded.text,
|
|
207
|
-
created_at = excluded.created_at,
|
|
208
|
-
is_replied = max(tweets.is_replied, excluded.is_replied),
|
|
209
|
-
reply_to_id = coalesce(tweets.reply_to_id, excluded.reply_to_id),
|
|
210
|
-
like_count = excluded.like_count,
|
|
211
|
-
media_count = max(tweets.media_count, excluded.media_count),
|
|
212
|
-
entities_json = excluded.entities_json,
|
|
213
|
-
media_json = case
|
|
214
|
-
when excluded.media_json not in ('', '[]', 'null') then excluded.media_json
|
|
215
|
-
else tweets.media_json
|
|
216
|
-
end,
|
|
217
|
-
bookmarked = tweets.bookmarked,
|
|
218
|
-
liked = tweets.liked,
|
|
219
|
-
quoted_tweet_id = coalesce(tweets.quoted_tweet_id, excluded.quoted_tweet_id)
|
|
220
|
-
`,
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
db.transaction(() => {
|
|
224
|
-
const seenAt = new Date().toISOString();
|
|
225
|
-
for (const tweet of payload.data) {
|
|
226
|
-
const author =
|
|
227
|
-
usersById.get(tweet.author_id) ??
|
|
228
|
-
({
|
|
229
|
-
id: tweet.author_id,
|
|
230
|
-
username: `user_${tweet.author_id}`,
|
|
231
|
-
name: `user_${tweet.author_id}`,
|
|
232
|
-
} as const);
|
|
233
|
-
const profile = usersById.has(tweet.author_id)
|
|
234
|
-
? upsertProfileFromXUser(db, author)
|
|
235
|
-
: ensureStubProfileForXUser(db, tweet.author_id);
|
|
236
|
-
const replyToId = getReferencedTweetId(tweet, "replied_to") ?? null;
|
|
237
|
-
const quotedTweetId = getReferencedTweetId(tweet, "quoted") ?? null;
|
|
238
|
-
upsertTweet.run(
|
|
239
|
-
tweet.id,
|
|
240
|
-
accountId,
|
|
241
|
-
profile.profile.id,
|
|
242
|
-
tweet.text,
|
|
243
|
-
tweet.created_at,
|
|
244
|
-
0,
|
|
245
|
-
replyToId,
|
|
246
|
-
Number(tweet.public_metrics?.like_count ?? 0),
|
|
247
|
-
countTweetMedia(tweet),
|
|
248
|
-
JSON.stringify(tweetEntitiesFromXurl(tweet.entities)),
|
|
249
|
-
buildMediaJsonFromIncludes(tweet, payload.includes?.media),
|
|
250
|
-
quotedTweetId,
|
|
251
|
-
);
|
|
252
|
-
upsertTweetAccountEdge(db, {
|
|
253
|
-
accountId,
|
|
254
|
-
tweetId: tweet.id,
|
|
255
|
-
kind: "home",
|
|
256
|
-
source,
|
|
257
|
-
seenAt,
|
|
258
|
-
rawJson: JSON.stringify(tweet),
|
|
259
|
-
});
|
|
260
|
-
replaceTweetFts(db, tweet.id, tweet.text);
|
|
261
|
-
}
|
|
262
|
-
})();
|
|
129
|
+
ingestTweetPayload(db, {
|
|
130
|
+
accountId,
|
|
131
|
+
payload,
|
|
132
|
+
kind: "home",
|
|
133
|
+
edgeKind: "home",
|
|
134
|
+
source,
|
|
135
|
+
});
|
|
263
136
|
}
|
|
264
137
|
|
|
265
138
|
export function syncHomeTimelineEffect({
|
|
@@ -303,7 +176,7 @@ export function syncHomeTimelineEffect({
|
|
|
303
176
|
? Infinity
|
|
304
177
|
: parseMaxPages(maxPages);
|
|
305
178
|
const db = getNativeDb();
|
|
306
|
-
const resolvedAccount =
|
|
179
|
+
const resolvedAccount = resolveLiveSyncAccount(db, account);
|
|
307
180
|
const accountId = resolvedAccount.accountId;
|
|
308
181
|
const effectiveMode =
|
|
309
182
|
parsedMode === "auto" &&
|
|
@@ -312,115 +185,95 @@ export function syncHomeTimelineEffect({
|
|
|
312
185
|
? "xurl"
|
|
313
186
|
: parsedMode;
|
|
314
187
|
const cacheKey = `timeline:${effectiveMode}:${accountId}:${following ? "following" : "for-you"}:${Number.isFinite(effectiveLimit) ? String(effectiveLimit) : "all"}:${Number.isFinite(parsedMaxPages) ? String(parsedMaxPages) : "all-pages"}:${parsedStartTime?.iso ?? "no-start"}`;
|
|
315
|
-
const ttlMs = parseCacheTtlMs(cacheTtlMs);
|
|
316
|
-
const cached = readSyncCache<XurlMentionsResponse>(cacheKey, db);
|
|
317
|
-
const cacheAgeMs = cached
|
|
318
|
-
? Date.now() - new Date(cached.updatedAt).getTime()
|
|
319
|
-
: Number.POSITIVE_INFINITY;
|
|
320
|
-
|
|
321
|
-
if (!refresh && cached && cacheAgeMs <= ttlMs) {
|
|
322
|
-
yield* Effect.sync(() =>
|
|
323
|
-
onProgress?.({
|
|
324
|
-
source: "cache",
|
|
325
|
-
fetched: cached.value.data.length,
|
|
326
|
-
total: Number.isFinite(effectiveLimit) ? effectiveLimit : undefined,
|
|
327
|
-
done: true,
|
|
328
|
-
}),
|
|
329
|
-
);
|
|
330
|
-
return {
|
|
331
|
-
ok: true,
|
|
332
|
-
source: "cache",
|
|
333
|
-
kind: "timeline",
|
|
334
|
-
accountId,
|
|
335
|
-
feed: following ? "following" : "for-you",
|
|
336
|
-
count: cached.value.data.length,
|
|
337
|
-
payload: cached.value,
|
|
338
|
-
} as const;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
188
|
const fetchViaXurl = Effect.gen(function* () {
|
|
342
189
|
if (!following) {
|
|
343
190
|
return yield* Effect.fail(
|
|
344
191
|
new Error("xurl home timeline mode does not support --for-you"),
|
|
345
192
|
);
|
|
346
193
|
}
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
(Number.isFinite(parsedMaxPages) && page + 1 >= parsedMaxPages) ||
|
|
377
|
-
(Number.isFinite(effectiveLimit) && totalFetched >= effectiveLimit) ||
|
|
378
|
-
reachedStartTimeBoundary(pagePayload, parsedStartTime?.time);
|
|
379
|
-
yield* Effect.sync(() =>
|
|
194
|
+
const pageSizes = new Map<number, number>();
|
|
195
|
+
const result = yield* collectPaginatedEffect({
|
|
196
|
+
fetchPage: ({ cursor, fetched, pageIndex }) => {
|
|
197
|
+
const remaining = Number.isFinite(effectiveLimit)
|
|
198
|
+
? Math.max(1, effectiveLimit - fetched)
|
|
199
|
+
: Infinity;
|
|
200
|
+
const pageSize = Math.min(
|
|
201
|
+
MAX_XURL_TIMELINE_PAGE_SIZE,
|
|
202
|
+
Math.max(5, remaining),
|
|
203
|
+
);
|
|
204
|
+
pageSizes.set(pageIndex, pageSize);
|
|
205
|
+
return liveTransportGateway.xurl.listHomeTimeline({
|
|
206
|
+
maxResults: pageSize,
|
|
207
|
+
userId: resolvedAccount.externalUserId,
|
|
208
|
+
username: resolvedAccount.username,
|
|
209
|
+
...(cursor ? { paginationToken: cursor } : {}),
|
|
210
|
+
timeoutMs,
|
|
211
|
+
});
|
|
212
|
+
},
|
|
213
|
+
getItemCount: (page) => page.data.length,
|
|
214
|
+
getNextCursor: (page) =>
|
|
215
|
+
typeof page.meta?.next_token === "string"
|
|
216
|
+
? page.meta.next_token
|
|
217
|
+
: undefined,
|
|
218
|
+
maxItems: effectiveLimit,
|
|
219
|
+
maxPages: parsedMaxPages,
|
|
220
|
+
shouldStop: ({ page }) =>
|
|
221
|
+
reachedStartTimeBoundary(page, parsedStartTime?.time),
|
|
222
|
+
onPage: ({ fetched, pageIndex, pageNumber, done }) =>
|
|
380
223
|
onProgress?.({
|
|
381
224
|
source: "xurl",
|
|
382
|
-
fetched
|
|
225
|
+
fetched,
|
|
383
226
|
total: Number.isFinite(effectiveLimit) ? effectiveLimit : undefined,
|
|
384
|
-
page:
|
|
227
|
+
page: pageNumber,
|
|
385
228
|
maxPages: Number.isFinite(parsedMaxPages)
|
|
386
229
|
? parsedMaxPages
|
|
387
230
|
: undefined,
|
|
388
|
-
pageSize,
|
|
231
|
+
pageSize: pageSizes.get(pageIndex),
|
|
389
232
|
done,
|
|
390
233
|
}),
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
break;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
return mergeTimelinePayloads(pages, effectiveLimit);
|
|
234
|
+
});
|
|
235
|
+
return mergeTimelinePayloads(result.pages, effectiveLimit);
|
|
397
236
|
});
|
|
398
|
-
const fetchViaBird =
|
|
237
|
+
const fetchViaBird = liveTransportGateway.bird.listHomeTimeline({
|
|
399
238
|
maxResults: finiteFallbackLimit,
|
|
400
239
|
following,
|
|
401
240
|
});
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
241
|
+
const transports =
|
|
242
|
+
effectiveMode === "xurl"
|
|
243
|
+
? [createLiveTransportAdapter("xurl", fetchViaXurl)]
|
|
244
|
+
: effectiveMode === "bird"
|
|
245
|
+
? [createLiveTransportAdapter("bird", fetchViaBird)]
|
|
246
|
+
: [
|
|
247
|
+
createLiveTransportAdapter("xurl", fetchViaXurl),
|
|
248
|
+
createLiveTransportAdapter("bird", fetchViaBird),
|
|
249
|
+
];
|
|
250
|
+
const syncResult = yield* runCachedLiveSyncEffect({
|
|
251
|
+
db,
|
|
252
|
+
cacheKey,
|
|
253
|
+
refresh,
|
|
254
|
+
cacheTtlMs: normalizeCacheTtlMs(
|
|
255
|
+
cacheTtlMs,
|
|
256
|
+
DEFAULT_TIMELINE_CACHE_TTL_MS,
|
|
257
|
+
),
|
|
258
|
+
transports,
|
|
259
|
+
persistLive: (writeDb, livePayload, liveSource) =>
|
|
260
|
+
mergeHomeTimelineIntoLocalStore(
|
|
261
|
+
writeDb,
|
|
262
|
+
accountId,
|
|
263
|
+
livePayload,
|
|
264
|
+
liveSource,
|
|
414
265
|
),
|
|
266
|
+
});
|
|
267
|
+
const { source, payload } = syncResult;
|
|
268
|
+
if (source === "cache") {
|
|
269
|
+
yield* Effect.sync(() =>
|
|
270
|
+
onProgress?.({
|
|
271
|
+
source: "cache",
|
|
272
|
+
fetched: payload.data.length,
|
|
273
|
+
total: Number.isFinite(effectiveLimit) ? effectiveLimit : undefined,
|
|
274
|
+
done: true,
|
|
275
|
+
}),
|
|
415
276
|
);
|
|
416
|
-
payload = fetched.value;
|
|
417
|
-
source = fetched.source;
|
|
418
|
-
} else {
|
|
419
|
-
payload = yield* listHomeTimelineViaBirdEffect({
|
|
420
|
-
maxResults: finiteFallbackLimit,
|
|
421
|
-
following,
|
|
422
|
-
});
|
|
423
|
-
source = "bird";
|
|
424
277
|
}
|
|
425
278
|
if (source === "bird") {
|
|
426
279
|
yield* Effect.sync(() =>
|
|
@@ -432,9 +285,6 @@ export function syncHomeTimelineEffect({
|
|
|
432
285
|
}),
|
|
433
286
|
);
|
|
434
287
|
}
|
|
435
|
-
mergeHomeTimelineIntoLocalStore(db, accountId, payload, source);
|
|
436
|
-
writeSyncCache(cacheKey, payload, db);
|
|
437
|
-
|
|
438
288
|
return {
|
|
439
289
|
ok: true,
|
|
440
290
|
source,
|