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
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
import type { Database } from "./sqlite";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
|
-
import {
|
|
4
|
-
listBookmarkedTweetsViaBirdEffect,
|
|
5
|
-
listLikedTweetsViaBirdEffect,
|
|
6
|
-
} from "./bird";
|
|
7
3
|
import { getNativeDb } from "./db";
|
|
8
|
-
import { runEffectPromise
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
4
|
+
import { runEffectPromise } from "./effect-runtime";
|
|
5
|
+
import { liveTransportGateway } from "./live-transport-gateway";
|
|
6
|
+
import {
|
|
7
|
+
createLiveTransportAdapter,
|
|
8
|
+
normalizeCacheTtlMs,
|
|
9
|
+
resolveLiveSyncAccount,
|
|
10
|
+
runCachedLiveSyncEffect,
|
|
11
|
+
} from "./live-sync-engine";
|
|
12
12
|
import type {
|
|
13
13
|
XurlMentionData,
|
|
14
14
|
XurlMentionsResponse,
|
|
15
15
|
XurlMediaItem,
|
|
16
16
|
XurlMentionUser,
|
|
17
17
|
} from "./types";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
listBookmarkedTweetsViaXurl,
|
|
21
|
-
listLikedTweetsViaXurl,
|
|
22
|
-
lookupUsersByHandles,
|
|
23
|
-
} from "./xurl";
|
|
18
|
+
import { ingestTweetPayload } from "./tweet-repository";
|
|
24
19
|
|
|
25
20
|
export type TimelineCollectionKind = "likes" | "bookmarks";
|
|
26
21
|
export type TimelineCollectionMode = "auto" | "xurl" | "bird";
|
|
@@ -52,13 +47,6 @@ function trySync<T>(try_: () => T) {
|
|
|
52
47
|
});
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
function parseCacheTtlMs(value?: number) {
|
|
56
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
57
|
-
return DEFAULT_COLLECTION_CACHE_TTL_MS;
|
|
58
|
-
}
|
|
59
|
-
return Math.floor(value);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
50
|
function parseMaxPages(value?: number) {
|
|
63
51
|
if (value === undefined) {
|
|
64
52
|
return null;
|
|
@@ -81,57 +69,6 @@ function assertXurlLimit(limit: number) {
|
|
|
81
69
|
}
|
|
82
70
|
}
|
|
83
71
|
|
|
84
|
-
function resolveAccount(db: Database, accountId?: string) {
|
|
85
|
-
const row = accountId
|
|
86
|
-
? (db
|
|
87
|
-
.prepare(
|
|
88
|
-
"select id, handle, external_user_id from accounts where id = ?",
|
|
89
|
-
)
|
|
90
|
-
.get(accountId) as
|
|
91
|
-
| { id: string; handle: string; external_user_id: string | null }
|
|
92
|
-
| undefined)
|
|
93
|
-
: (db
|
|
94
|
-
.prepare(
|
|
95
|
-
`
|
|
96
|
-
select id, handle, external_user_id
|
|
97
|
-
from accounts
|
|
98
|
-
order by is_default desc, created_at asc
|
|
99
|
-
limit 1
|
|
100
|
-
`,
|
|
101
|
-
)
|
|
102
|
-
.get() as
|
|
103
|
-
| { id: string; handle: string; external_user_id: string | null }
|
|
104
|
-
| undefined);
|
|
105
|
-
|
|
106
|
-
if (!row) {
|
|
107
|
-
throw new Error(`Unknown account: ${accountId ?? "default"}`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
accountId: row.id,
|
|
112
|
-
username: row.handle.replace(/^@/, ""),
|
|
113
|
-
externalUserId:
|
|
114
|
-
typeof row.external_user_id === "string" &&
|
|
115
|
-
row.external_user_id.length > 0
|
|
116
|
-
? row.external_user_id
|
|
117
|
-
: undefined,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function replaceTweetFts(db: Database, tweetId: string, text: string) {
|
|
122
|
-
db.prepare("delete from tweets_fts where tweet_id = ?").run(tweetId);
|
|
123
|
-
db.prepare("insert into tweets_fts (tweet_id, text) values (?, ?)").run(
|
|
124
|
-
tweetId,
|
|
125
|
-
text,
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function getReferencedTweetId(tweet: XurlMentionData, type: string) {
|
|
130
|
-
return (
|
|
131
|
-
tweet.referenced_tweets?.find((item) => item.type === type)?.id ?? null
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
72
|
function mergePayloads(pages: XurlMentionsResponse[]): XurlMentionsResponse {
|
|
136
73
|
const tweets: XurlMentionData[] = [];
|
|
137
74
|
const seenTweetIds = new Set<string>();
|
|
@@ -237,95 +174,16 @@ function mergeTimelineCollectionIntoLocalStore(
|
|
|
237
174
|
payload: XurlMentionsResponse,
|
|
238
175
|
source: "xurl" | "bird",
|
|
239
176
|
) {
|
|
240
|
-
const usersById = new Map(
|
|
241
|
-
(payload.includes?.users ?? []).map((user) => [user.id, user]),
|
|
242
|
-
);
|
|
243
177
|
const tweetKind = kind === "likes" ? "like" : "bookmark";
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
on conflict(id) do update set
|
|
254
|
-
account_id = tweets.account_id,
|
|
255
|
-
author_profile_id = excluded.author_profile_id,
|
|
256
|
-
kind = case
|
|
257
|
-
when tweets.kind in ('authored', 'home', 'mention') then tweets.kind
|
|
258
|
-
else excluded.kind
|
|
259
|
-
end,
|
|
260
|
-
text = excluded.text,
|
|
261
|
-
created_at = excluded.created_at,
|
|
262
|
-
like_count = excluded.like_count,
|
|
263
|
-
media_count = max(tweets.media_count, excluded.media_count),
|
|
264
|
-
entities_json = excluded.entities_json,
|
|
265
|
-
media_json = case
|
|
266
|
-
when excluded.media_json not in ('', '[]', 'null') then excluded.media_json
|
|
267
|
-
else tweets.media_json
|
|
268
|
-
end,
|
|
269
|
-
is_replied = max(tweets.is_replied, excluded.is_replied),
|
|
270
|
-
reply_to_id = coalesce(excluded.reply_to_id, tweets.reply_to_id),
|
|
271
|
-
quoted_tweet_id = coalesce(excluded.quoted_tweet_id, tweets.quoted_tweet_id),
|
|
272
|
-
bookmarked = tweets.bookmarked,
|
|
273
|
-
liked = tweets.liked
|
|
274
|
-
`,
|
|
275
|
-
);
|
|
276
|
-
const upsertCollection = db.prepare(`
|
|
277
|
-
insert into tweet_collections (
|
|
278
|
-
account_id, tweet_id, kind, collected_at, source, raw_json, updated_at
|
|
279
|
-
) values (?, ?, ?, null, ?, ?, ?)
|
|
280
|
-
on conflict(account_id, tweet_id, kind) do update set
|
|
281
|
-
source = excluded.source,
|
|
282
|
-
raw_json = excluded.raw_json,
|
|
283
|
-
updated_at = excluded.updated_at
|
|
284
|
-
`);
|
|
285
|
-
|
|
286
|
-
db.transaction(() => {
|
|
287
|
-
const updatedAt = new Date().toISOString();
|
|
288
|
-
for (const tweet of payload.data) {
|
|
289
|
-
const author =
|
|
290
|
-
usersById.get(tweet.author_id) ??
|
|
291
|
-
({
|
|
292
|
-
id: tweet.author_id,
|
|
293
|
-
username: `user_${tweet.author_id}`,
|
|
294
|
-
name: `user_${tweet.author_id}`,
|
|
295
|
-
} as const);
|
|
296
|
-
const profile = usersById.has(tweet.author_id)
|
|
297
|
-
? upsertProfileFromXUser(db, author)
|
|
298
|
-
: ensureStubProfileForXUser(db, tweet.author_id);
|
|
299
|
-
const replyToId = getReferencedTweetId(tweet, "replied_to");
|
|
300
|
-
const quotedTweetId = getReferencedTweetId(tweet, "quoted");
|
|
301
|
-
upsertTweet.run(
|
|
302
|
-
tweet.id,
|
|
303
|
-
accountId,
|
|
304
|
-
profile.profile.id,
|
|
305
|
-
tweetKind,
|
|
306
|
-
tweet.text,
|
|
307
|
-
tweet.created_at,
|
|
308
|
-
replyToId ? 1 : 0,
|
|
309
|
-
replyToId,
|
|
310
|
-
Number(tweet.public_metrics?.like_count ?? 0),
|
|
311
|
-
countTweetMedia(tweet),
|
|
312
|
-
bookmarked,
|
|
313
|
-
liked,
|
|
314
|
-
JSON.stringify(tweetEntitiesFromXurl(tweet.entities)),
|
|
315
|
-
buildMediaJsonFromIncludes(tweet, payload.includes?.media),
|
|
316
|
-
quotedTweetId,
|
|
317
|
-
);
|
|
318
|
-
upsertCollection.run(
|
|
319
|
-
accountId,
|
|
320
|
-
tweet.id,
|
|
321
|
-
kind,
|
|
322
|
-
source,
|
|
323
|
-
JSON.stringify(tweet),
|
|
324
|
-
updatedAt,
|
|
325
|
-
);
|
|
326
|
-
replaceTweetFts(db, tweet.id, tweet.text);
|
|
327
|
-
}
|
|
328
|
-
})();
|
|
178
|
+
ingestTweetPayload(db, {
|
|
179
|
+
accountId,
|
|
180
|
+
payload,
|
|
181
|
+
kind: tweetKind,
|
|
182
|
+
collectionKind: kind,
|
|
183
|
+
markRepliesAsReplied: true,
|
|
184
|
+
replaceSecondaryKind: true,
|
|
185
|
+
source,
|
|
186
|
+
});
|
|
329
187
|
}
|
|
330
188
|
|
|
331
189
|
function fetchXurlCollectionEffect({
|
|
@@ -352,9 +210,8 @@ function fetchXurlCollectionEffect({
|
|
|
352
210
|
return Effect.gen(function* () {
|
|
353
211
|
let resolvedUserId = userId;
|
|
354
212
|
if (!resolvedUserId) {
|
|
355
|
-
const [accountUser] =
|
|
356
|
-
lookupUsersByHandles([username])
|
|
357
|
-
);
|
|
213
|
+
const [accountUser] =
|
|
214
|
+
yield* liveTransportGateway.xurl.lookupUsersByHandles([username]);
|
|
358
215
|
if (!accountUser?.id) {
|
|
359
216
|
return yield* Effect.fail(
|
|
360
217
|
new Error(`Could not resolve Twitter user id for @${username}`),
|
|
@@ -368,22 +225,20 @@ function fetchXurlCollectionEffect({
|
|
|
368
225
|
let pageCount = 0;
|
|
369
226
|
let saturatedAtPage: number | undefined;
|
|
370
227
|
do {
|
|
371
|
-
const payload = yield*
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}),
|
|
386
|
-
);
|
|
228
|
+
const payload = yield* kind === "likes"
|
|
229
|
+
? liveTransportGateway.xurl.listLikes({
|
|
230
|
+
maxResults: limit,
|
|
231
|
+
username,
|
|
232
|
+
userId: resolvedUserId,
|
|
233
|
+
paginationToken: nextToken,
|
|
234
|
+
})
|
|
235
|
+
: liveTransportGateway.xurl.listBookmarks({
|
|
236
|
+
maxResults: limit,
|
|
237
|
+
username,
|
|
238
|
+
userId: resolvedUserId,
|
|
239
|
+
isPaginatedWalk: all,
|
|
240
|
+
paginationToken: nextToken,
|
|
241
|
+
});
|
|
387
242
|
pageCount += 1;
|
|
388
243
|
if (earlyStop) {
|
|
389
244
|
const tweetIds = payload.data.map((tweet) => tweet.id);
|
|
@@ -438,12 +293,12 @@ function fetchBirdCollectionEffect({
|
|
|
438
293
|
maxPages: number | null;
|
|
439
294
|
}) {
|
|
440
295
|
return kind === "likes"
|
|
441
|
-
?
|
|
296
|
+
? liveTransportGateway.bird.listLikes({
|
|
442
297
|
maxResults: limit,
|
|
443
298
|
all,
|
|
444
299
|
maxPages: maxPages ?? undefined,
|
|
445
300
|
})
|
|
446
|
-
:
|
|
301
|
+
: liveTransportGateway.bird.listBookmarks({
|
|
447
302
|
maxResults: limit,
|
|
448
303
|
all,
|
|
449
304
|
maxPages: maxPages ?? undefined,
|
|
@@ -474,31 +329,11 @@ export function syncTimelineCollectionEffect({
|
|
|
474
329
|
}
|
|
475
330
|
|
|
476
331
|
const db = yield* trySync(() => getNativeDb());
|
|
477
|
-
const resolvedAccount = yield* trySync(() =>
|
|
332
|
+
const resolvedAccount = yield* trySync(() =>
|
|
333
|
+
resolveLiveSyncAccount(db, account),
|
|
334
|
+
);
|
|
478
335
|
const cacheMaxPages = mode === "bird" ? parsedMaxPages : xurlMaxPages;
|
|
479
336
|
const cacheKey = `${kind}:${mode}:${resolvedAccount.accountId}:${String(limit)}:${all ? "all" : "single"}:${cacheMaxPages === null ? "all-pages" : String(cacheMaxPages)}${earlyStop ? ":early-stop" : ""}`;
|
|
480
|
-
const ttlMs = parseCacheTtlMs(cacheTtlMs);
|
|
481
|
-
const cached = yield* trySync(() =>
|
|
482
|
-
readSyncCache<XurlMentionsResponse>(cacheKey, db),
|
|
483
|
-
);
|
|
484
|
-
const cacheAgeMs = cached
|
|
485
|
-
? Date.now() - new Date(cached.updatedAt).getTime()
|
|
486
|
-
: Number.POSITIVE_INFINITY;
|
|
487
|
-
|
|
488
|
-
if (!refresh && cached && cacheAgeMs <= ttlMs) {
|
|
489
|
-
const saturatedAtPage = readSaturatedAtPage(cached.value);
|
|
490
|
-
return {
|
|
491
|
-
ok: true,
|
|
492
|
-
source: "cache",
|
|
493
|
-
kind,
|
|
494
|
-
accountId: resolvedAccount.accountId,
|
|
495
|
-
count: cached.value.data.length,
|
|
496
|
-
payload: cached.value,
|
|
497
|
-
...(saturatedAtPage === undefined
|
|
498
|
-
? {}
|
|
499
|
-
: { saturated_at_page: saturatedAtPage }),
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
337
|
|
|
503
338
|
if (shouldApplyEarlyStopCap) {
|
|
504
339
|
console.error(
|
|
@@ -506,60 +341,51 @@ export function syncTimelineCollectionEffect({
|
|
|
506
341
|
);
|
|
507
342
|
}
|
|
508
343
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
} else {
|
|
543
|
-
payload = yield* fetchBirdCollectionEffect({
|
|
544
|
-
kind,
|
|
545
|
-
limit,
|
|
546
|
-
all,
|
|
547
|
-
maxPages: parsedMaxPages,
|
|
548
|
-
});
|
|
549
|
-
source = "bird";
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
yield* trySync(() =>
|
|
554
|
-
mergeTimelineCollectionIntoLocalStore(
|
|
555
|
-
db,
|
|
556
|
-
resolvedAccount.accountId,
|
|
557
|
-
kind,
|
|
558
|
-
payload,
|
|
559
|
-
source,
|
|
344
|
+
const xurlFetch = fetchXurlCollectionEffect({
|
|
345
|
+
db,
|
|
346
|
+
kind,
|
|
347
|
+
accountId: resolvedAccount.accountId,
|
|
348
|
+
username: resolvedAccount.username,
|
|
349
|
+
userId: resolvedAccount.externalUserId,
|
|
350
|
+
limit,
|
|
351
|
+
all,
|
|
352
|
+
maxPages: xurlMaxPages,
|
|
353
|
+
earlyStop,
|
|
354
|
+
});
|
|
355
|
+
const birdFetch = fetchBirdCollectionEffect({
|
|
356
|
+
kind,
|
|
357
|
+
limit,
|
|
358
|
+
all,
|
|
359
|
+
maxPages: parsedMaxPages,
|
|
360
|
+
});
|
|
361
|
+
const transports =
|
|
362
|
+
mode === "bird"
|
|
363
|
+
? [createLiveTransportAdapter("bird", birdFetch)]
|
|
364
|
+
: mode === "xurl"
|
|
365
|
+
? [createLiveTransportAdapter("xurl", xurlFetch)]
|
|
366
|
+
: [
|
|
367
|
+
createLiveTransportAdapter("xurl", xurlFetch),
|
|
368
|
+
createLiveTransportAdapter("bird", birdFetch),
|
|
369
|
+
];
|
|
370
|
+
const syncResult = yield* runCachedLiveSyncEffect({
|
|
371
|
+
db,
|
|
372
|
+
cacheKey,
|
|
373
|
+
refresh,
|
|
374
|
+
cacheTtlMs: normalizeCacheTtlMs(
|
|
375
|
+
cacheTtlMs,
|
|
376
|
+
DEFAULT_COLLECTION_CACHE_TTL_MS,
|
|
560
377
|
),
|
|
561
|
-
|
|
562
|
-
|
|
378
|
+
transports,
|
|
379
|
+
persistLive: (writeDb, livePayload, liveSource) =>
|
|
380
|
+
mergeTimelineCollectionIntoLocalStore(
|
|
381
|
+
writeDb,
|
|
382
|
+
resolvedAccount.accountId,
|
|
383
|
+
kind,
|
|
384
|
+
livePayload,
|
|
385
|
+
liveSource,
|
|
386
|
+
),
|
|
387
|
+
});
|
|
388
|
+
const { source, payload } = syncResult;
|
|
563
389
|
const saturatedAtPage = readSaturatedAtPage(payload);
|
|
564
390
|
|
|
565
391
|
return {
|