birdclaw 0.8.1 → 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 +25 -0
- package/package.json +2 -4
- package/scripts/browser-perf.mjs +27 -0
- 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/ConversationThread.tsx +4 -0
- package/src/components/EmbeddedTweetCard.tsx +4 -0
- package/src/components/FloatingPreview.tsx +382 -0
- package/src/components/MarkdownCitations.tsx +680 -0
- package/src/components/MarkdownViewer.tsx +7 -715
- package/src/components/ProfileAnalysisClient.ts +191 -0
- package/src/components/ProfileAnalysisStream.tsx +16 -185
- package/src/components/ProfilePreview.tsx +41 -81
- package/src/components/TimelineCard.tsx +18 -2
- package/src/components/TweetArticleCard.tsx +66 -0
- package/src/components/TweetRichText.tsx +33 -2
- 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/useDebouncedValue.ts +12 -0
- package/src/components/useTimelineRouteData.ts +142 -170
- package/src/lib/analysis-runtime.ts +238 -0
- package/src/lib/api-client.ts +16 -100
- package/src/lib/api-contracts.ts +328 -0
- package/src/lib/archive-finder.ts +38 -0
- package/src/lib/archive-import-plan.ts +102 -0
- package/src/lib/archive-import.ts +170 -239
- package/src/lib/authored-live.ts +77 -182
- package/src/lib/backup.ts +335 -424
- package/src/lib/bird.ts +32 -1
- 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 +141 -334
- 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 -176
- package/src/lib/mentions-export.ts +1 -1
- package/src/lib/mentions-live.ts +57 -225
- 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 +37 -111
- package/src/lib/queries.ts +6 -2380
- 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 +53 -14
- 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 -256
- package/src/lib/timeline-live.ts +86 -235
- package/src/lib/timeline-read-model.ts +1191 -0
- package/src/lib/tweet-render.ts +78 -0
- package/src/lib/tweet-repository.ts +156 -0
- package/src/lib/tweet-search-live.ts +63 -166
- package/src/lib/types.ts +8 -0
- package/src/lib/ui.ts +1 -1
- package/src/lib/web-sync.ts +67 -50
- package/src/lib/whois.ts +2 -1
- package/src/router.tsx +1 -1
- package/src/routes/__root.tsx +11 -21
- 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 -2
- 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 +168 -167
- package/src/routes/inbox.tsx +63 -57
- package/src/routes/links.tsx +31 -329
- package/src/routes/network-map.tsx +41 -344
- package/src/routes/rate-limits.tsx +17 -21
- package/vite.config.ts +0 -2
package/src/lib/timeline-live.ts
CHANGED
|
@@ -1,18 +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 {
|
|
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";
|
|
8
13
|
import type {
|
|
9
14
|
XurlMediaItem,
|
|
10
15
|
XurlMentionUser,
|
|
11
16
|
XurlMentionsResponse,
|
|
12
17
|
} from "./types";
|
|
13
|
-
import {
|
|
14
|
-
import { ensureStubProfileForXUser, upsertProfileFromXUser } from "./x-profile";
|
|
15
|
-
import { listHomeTimelineViaXurlEffect } from "./xurl";
|
|
18
|
+
import { ingestTweetPayload } from "./tweet-repository";
|
|
16
19
|
|
|
17
20
|
const DEFAULT_TIMELINE_CACHE_TTL_MS = 2 * 60_000;
|
|
18
21
|
const MAX_XURL_TIMELINE_PAGE_SIZE = 100;
|
|
@@ -40,13 +43,6 @@ export interface SyncHomeTimelineOptions {
|
|
|
40
43
|
onProgress?: (progress: HomeTimelineProgress) => void;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
function parseCacheTtlMs(value?: number) {
|
|
44
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
45
|
-
return DEFAULT_TIMELINE_CACHE_TTL_MS;
|
|
46
|
-
}
|
|
47
|
-
return Math.floor(value);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
46
|
function assertLimit(limit: number) {
|
|
51
47
|
if ((!Number.isFinite(limit) && limit !== Infinity) || limit < 1) {
|
|
52
48
|
throw new Error("--limit must be at least 1");
|
|
@@ -89,14 +85,6 @@ function reachedStartTimeBoundary(
|
|
|
89
85
|
});
|
|
90
86
|
}
|
|
91
87
|
|
|
92
|
-
function getReferencedTweetId(
|
|
93
|
-
tweet: XurlMentionsResponse["data"][number],
|
|
94
|
-
type: "replied_to" | "quoted",
|
|
95
|
-
) {
|
|
96
|
-
return tweet.referenced_tweets?.find((reference) => reference.type === type)
|
|
97
|
-
?.id;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
88
|
function mergeTimelinePayloads(
|
|
101
89
|
payloads: XurlMentionsResponse[],
|
|
102
90
|
limit: number,
|
|
@@ -132,133 +120,19 @@ function mergeTimelinePayloads(
|
|
|
132
120
|
} satisfies XurlMentionsResponse;
|
|
133
121
|
}
|
|
134
122
|
|
|
135
|
-
function resolveAccount(db: Database, accountId?: string) {
|
|
136
|
-
const row = accountId
|
|
137
|
-
? (db
|
|
138
|
-
.prepare(
|
|
139
|
-
"select id, handle, external_user_id, is_default as isDefault from accounts where id = ?",
|
|
140
|
-
)
|
|
141
|
-
.get(accountId) as
|
|
142
|
-
| ({ id: string; handle: string; external_user_id: string | null } & {
|
|
143
|
-
isDefault: number;
|
|
144
|
-
})
|
|
145
|
-
| undefined)
|
|
146
|
-
: (db
|
|
147
|
-
.prepare(
|
|
148
|
-
`
|
|
149
|
-
select id, handle, external_user_id, is_default as isDefault
|
|
150
|
-
from accounts
|
|
151
|
-
order by is_default desc, created_at asc
|
|
152
|
-
limit 1
|
|
153
|
-
`,
|
|
154
|
-
)
|
|
155
|
-
.get() as
|
|
156
|
-
| ({ id: string; handle: string; external_user_id: string | null } & {
|
|
157
|
-
isDefault: number;
|
|
158
|
-
})
|
|
159
|
-
| undefined);
|
|
160
|
-
|
|
161
|
-
if (!row) {
|
|
162
|
-
throw new Error(`Unknown account: ${accountId ?? "default"}`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
accountId: row.id,
|
|
167
|
-
isDefault: row.isDefault === 1,
|
|
168
|
-
username: row.handle.replace(/^@/, ""),
|
|
169
|
-
externalUserId:
|
|
170
|
-
typeof row.external_user_id === "string" &&
|
|
171
|
-
row.external_user_id.trim().length > 0
|
|
172
|
-
? row.external_user_id.trim()
|
|
173
|
-
: undefined,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function replaceTweetFts(db: Database, tweetId: string, text: string) {
|
|
178
|
-
db.prepare("delete from tweets_fts where tweet_id = ?").run(tweetId);
|
|
179
|
-
db.prepare("insert into tweets_fts (tweet_id, text) values (?, ?)").run(
|
|
180
|
-
tweetId,
|
|
181
|
-
text,
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
123
|
function mergeHomeTimelineIntoLocalStore(
|
|
186
124
|
db: Database,
|
|
187
125
|
accountId: string,
|
|
188
126
|
payload: XurlMentionsResponse,
|
|
189
127
|
source: "bird" | "xurl",
|
|
190
128
|
) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
is_replied, reply_to_id, like_count, media_count, bookmarked, liked,
|
|
199
|
-
entities_json, media_json, quoted_tweet_id
|
|
200
|
-
) values (?, ?, ?, 'home', ?, ?, ?, ?, ?, ?, 0, 0, ?, ?, ?)
|
|
201
|
-
on conflict(id) do update set
|
|
202
|
-
account_id = tweets.account_id,
|
|
203
|
-
author_profile_id = excluded.author_profile_id,
|
|
204
|
-
kind = tweets.kind,
|
|
205
|
-
text = excluded.text,
|
|
206
|
-
created_at = excluded.created_at,
|
|
207
|
-
is_replied = max(tweets.is_replied, excluded.is_replied),
|
|
208
|
-
reply_to_id = coalesce(tweets.reply_to_id, excluded.reply_to_id),
|
|
209
|
-
like_count = excluded.like_count,
|
|
210
|
-
media_count = max(tweets.media_count, excluded.media_count),
|
|
211
|
-
entities_json = excluded.entities_json,
|
|
212
|
-
media_json = case
|
|
213
|
-
when excluded.media_json not in ('', '[]', 'null') then excluded.media_json
|
|
214
|
-
else tweets.media_json
|
|
215
|
-
end,
|
|
216
|
-
bookmarked = tweets.bookmarked,
|
|
217
|
-
liked = tweets.liked,
|
|
218
|
-
quoted_tweet_id = coalesce(tweets.quoted_tweet_id, excluded.quoted_tweet_id)
|
|
219
|
-
`,
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
db.transaction(() => {
|
|
223
|
-
const seenAt = new Date().toISOString();
|
|
224
|
-
for (const tweet of payload.data) {
|
|
225
|
-
const author =
|
|
226
|
-
usersById.get(tweet.author_id) ??
|
|
227
|
-
({
|
|
228
|
-
id: tweet.author_id,
|
|
229
|
-
username: `user_${tweet.author_id}`,
|
|
230
|
-
name: `user_${tweet.author_id}`,
|
|
231
|
-
} as const);
|
|
232
|
-
const profile = usersById.has(tweet.author_id)
|
|
233
|
-
? upsertProfileFromXUser(db, author)
|
|
234
|
-
: ensureStubProfileForXUser(db, tweet.author_id);
|
|
235
|
-
const replyToId = getReferencedTweetId(tweet, "replied_to") ?? null;
|
|
236
|
-
const quotedTweetId = getReferencedTweetId(tweet, "quoted") ?? null;
|
|
237
|
-
upsertTweet.run(
|
|
238
|
-
tweet.id,
|
|
239
|
-
accountId,
|
|
240
|
-
profile.profile.id,
|
|
241
|
-
tweet.text,
|
|
242
|
-
tweet.created_at,
|
|
243
|
-
0,
|
|
244
|
-
replyToId,
|
|
245
|
-
Number(tweet.public_metrics?.like_count ?? 0),
|
|
246
|
-
countTweetMedia(tweet),
|
|
247
|
-
JSON.stringify(tweet.entities ?? {}),
|
|
248
|
-
buildMediaJsonFromIncludes(tweet, payload.includes?.media),
|
|
249
|
-
quotedTweetId,
|
|
250
|
-
);
|
|
251
|
-
upsertTweetAccountEdge(db, {
|
|
252
|
-
accountId,
|
|
253
|
-
tweetId: tweet.id,
|
|
254
|
-
kind: "home",
|
|
255
|
-
source,
|
|
256
|
-
seenAt,
|
|
257
|
-
rawJson: JSON.stringify(tweet),
|
|
258
|
-
});
|
|
259
|
-
replaceTweetFts(db, tweet.id, tweet.text);
|
|
260
|
-
}
|
|
261
|
-
})();
|
|
129
|
+
ingestTweetPayload(db, {
|
|
130
|
+
accountId,
|
|
131
|
+
payload,
|
|
132
|
+
kind: "home",
|
|
133
|
+
edgeKind: "home",
|
|
134
|
+
source,
|
|
135
|
+
});
|
|
262
136
|
}
|
|
263
137
|
|
|
264
138
|
export function syncHomeTimelineEffect({
|
|
@@ -302,7 +176,7 @@ export function syncHomeTimelineEffect({
|
|
|
302
176
|
? Infinity
|
|
303
177
|
: parseMaxPages(maxPages);
|
|
304
178
|
const db = getNativeDb();
|
|
305
|
-
const resolvedAccount =
|
|
179
|
+
const resolvedAccount = resolveLiveSyncAccount(db, account);
|
|
306
180
|
const accountId = resolvedAccount.accountId;
|
|
307
181
|
const effectiveMode =
|
|
308
182
|
parsedMode === "auto" &&
|
|
@@ -311,115 +185,95 @@ export function syncHomeTimelineEffect({
|
|
|
311
185
|
? "xurl"
|
|
312
186
|
: parsedMode;
|
|
313
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"}`;
|
|
314
|
-
const ttlMs = parseCacheTtlMs(cacheTtlMs);
|
|
315
|
-
const cached = readSyncCache<XurlMentionsResponse>(cacheKey, db);
|
|
316
|
-
const cacheAgeMs = cached
|
|
317
|
-
? Date.now() - new Date(cached.updatedAt).getTime()
|
|
318
|
-
: Number.POSITIVE_INFINITY;
|
|
319
|
-
|
|
320
|
-
if (!refresh && cached && cacheAgeMs <= ttlMs) {
|
|
321
|
-
yield* Effect.sync(() =>
|
|
322
|
-
onProgress?.({
|
|
323
|
-
source: "cache",
|
|
324
|
-
fetched: cached.value.data.length,
|
|
325
|
-
total: Number.isFinite(effectiveLimit) ? effectiveLimit : undefined,
|
|
326
|
-
done: true,
|
|
327
|
-
}),
|
|
328
|
-
);
|
|
329
|
-
return {
|
|
330
|
-
ok: true,
|
|
331
|
-
source: "cache",
|
|
332
|
-
kind: "timeline",
|
|
333
|
-
accountId,
|
|
334
|
-
feed: following ? "following" : "for-you",
|
|
335
|
-
count: cached.value.data.length,
|
|
336
|
-
payload: cached.value,
|
|
337
|
-
} as const;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
188
|
const fetchViaXurl = Effect.gen(function* () {
|
|
341
189
|
if (!following) {
|
|
342
190
|
return yield* Effect.fail(
|
|
343
191
|
new Error("xurl home timeline mode does not support --for-you"),
|
|
344
192
|
);
|
|
345
193
|
}
|
|
346
|
-
const
|
|
347
|
-
|
|
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
|
-
(Number.isFinite(parsedMaxPages) && page + 1 >= parsedMaxPages) ||
|
|
376
|
-
(Number.isFinite(effectiveLimit) && totalFetched >= effectiveLimit) ||
|
|
377
|
-
reachedStartTimeBoundary(pagePayload, parsedStartTime?.time);
|
|
378
|
-
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 }) =>
|
|
379
223
|
onProgress?.({
|
|
380
224
|
source: "xurl",
|
|
381
|
-
fetched
|
|
225
|
+
fetched,
|
|
382
226
|
total: Number.isFinite(effectiveLimit) ? effectiveLimit : undefined,
|
|
383
|
-
page:
|
|
227
|
+
page: pageNumber,
|
|
384
228
|
maxPages: Number.isFinite(parsedMaxPages)
|
|
385
229
|
? parsedMaxPages
|
|
386
230
|
: undefined,
|
|
387
|
-
pageSize,
|
|
231
|
+
pageSize: pageSizes.get(pageIndex),
|
|
388
232
|
done,
|
|
389
233
|
}),
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
break;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
return mergeTimelinePayloads(pages, effectiveLimit);
|
|
234
|
+
});
|
|
235
|
+
return mergeTimelinePayloads(result.pages, effectiveLimit);
|
|
396
236
|
});
|
|
397
|
-
const fetchViaBird =
|
|
237
|
+
const fetchViaBird = liveTransportGateway.bird.listHomeTimeline({
|
|
398
238
|
maxResults: finiteFallbackLimit,
|
|
399
239
|
following,
|
|
400
240
|
});
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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,
|
|
413
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
|
+
}),
|
|
414
276
|
);
|
|
415
|
-
payload = fetched.value;
|
|
416
|
-
source = fetched.source;
|
|
417
|
-
} else {
|
|
418
|
-
payload = yield* listHomeTimelineViaBirdEffect({
|
|
419
|
-
maxResults: finiteFallbackLimit,
|
|
420
|
-
following,
|
|
421
|
-
});
|
|
422
|
-
source = "bird";
|
|
423
277
|
}
|
|
424
278
|
if (source === "bird") {
|
|
425
279
|
yield* Effect.sync(() =>
|
|
@@ -431,9 +285,6 @@ export function syncHomeTimelineEffect({
|
|
|
431
285
|
}),
|
|
432
286
|
);
|
|
433
287
|
}
|
|
434
|
-
mergeHomeTimelineIntoLocalStore(db, accountId, payload, source);
|
|
435
|
-
writeSyncCache(cacheKey, payload, db);
|
|
436
|
-
|
|
437
288
|
return {
|
|
438
289
|
ok: true,
|
|
439
290
|
source,
|