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/mentions-live.ts
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
import type { Database } from "./sqlite";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
|
-
import {
|
|
4
|
-
getAuthenticatedBirdAccountEffect,
|
|
5
|
-
listMentionsViaBirdEffect,
|
|
6
|
-
} from "./bird";
|
|
7
3
|
import type { MentionsDataSource } from "./config";
|
|
4
|
+
import { databaseWriteEffect } from "./database-writer";
|
|
8
5
|
import { getNativeDb } from "./db";
|
|
9
|
-
import { runEffectPromise
|
|
10
|
-
import {
|
|
6
|
+
import { runEffectPromise } from "./effect-runtime";
|
|
7
|
+
import { liveTransportGateway } from "./live-transport-gateway";
|
|
8
|
+
import {
|
|
9
|
+
assertLiveAccountMatches,
|
|
10
|
+
resolveLiveSyncAccount,
|
|
11
|
+
type LiveSyncAccount,
|
|
12
|
+
} from "./live-sync-engine";
|
|
11
13
|
import { serializeMentionItemsAsXurlCompatible } from "./mentions-export";
|
|
12
|
-
import { listTimelineItems } from "./
|
|
14
|
+
import { listTimelineItems } from "./timeline-read-model";
|
|
13
15
|
import { deleteSyncCache, readSyncCache, writeSyncCache } from "./sync-cache";
|
|
14
16
|
import type {
|
|
15
17
|
ReplyFilter,
|
|
16
|
-
TweetEntities,
|
|
17
18
|
XurlMentionData,
|
|
18
19
|
XurlMentionsResponse,
|
|
19
20
|
XurlMediaItem,
|
|
20
21
|
XurlMentionUser,
|
|
21
22
|
} from "./types";
|
|
22
|
-
import {
|
|
23
|
-
import { ensureStubProfileForXUser, upsertProfileFromXUser } from "./x-profile";
|
|
24
|
-
import { listMentionsViaXurl, lookupUsersByHandles } from "./xurl";
|
|
23
|
+
import { ingestTweetPayload } from "./tweet-repository";
|
|
25
24
|
|
|
26
25
|
export const DEFAULT_MENTIONS_CACHE_TTL_MS = 2 * 60_000;
|
|
27
26
|
const MIN_XURL_MENTIONS_LIMIT = 5;
|
|
@@ -430,71 +429,21 @@ function writeMentionHighWaterId(
|
|
|
430
429
|
writeSyncCache(getMentionHighWaterKey({ mode, accountId }), { sinceId }, db);
|
|
431
430
|
}
|
|
432
431
|
|
|
433
|
-
function
|
|
434
|
-
const row = accountId
|
|
435
|
-
? (db
|
|
436
|
-
.prepare(
|
|
437
|
-
"select id, handle, external_user_id from accounts where id = ?",
|
|
438
|
-
)
|
|
439
|
-
.get(accountId) as
|
|
440
|
-
| { id: string; handle: string; external_user_id: string | null }
|
|
441
|
-
| undefined)
|
|
442
|
-
: (db
|
|
443
|
-
.prepare(
|
|
444
|
-
`
|
|
445
|
-
select id, handle, external_user_id
|
|
446
|
-
from accounts
|
|
447
|
-
order by is_default desc, created_at asc
|
|
448
|
-
limit 1
|
|
449
|
-
`,
|
|
450
|
-
)
|
|
451
|
-
.get() as
|
|
452
|
-
| { id: string; handle: string; external_user_id: string | null }
|
|
453
|
-
| undefined);
|
|
454
|
-
|
|
455
|
-
if (!row) {
|
|
456
|
-
throw new Error(`Unknown account: ${accountId ?? "default"}`);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
return {
|
|
460
|
-
accountId: row.id,
|
|
461
|
-
username: row.handle.replace(/^@/, ""),
|
|
462
|
-
externalUserId:
|
|
463
|
-
typeof row.external_user_id === "string" &&
|
|
464
|
-
row.external_user_id.trim().length > 0
|
|
465
|
-
? row.external_user_id.trim()
|
|
466
|
-
: undefined,
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
function verifyBirdAccountMatchesEffect({
|
|
471
|
-
accountId,
|
|
472
|
-
username,
|
|
473
|
-
externalUserId,
|
|
474
|
-
}: ReturnType<typeof resolveAccount>) {
|
|
432
|
+
function verifyBirdAccountMatchesEffect(account: LiveSyncAccount) {
|
|
475
433
|
return Effect.gen(function* () {
|
|
476
|
-
const authenticated =
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
if (authenticated.username.toLowerCase() !== username.toLowerCase()) {
|
|
492
|
-
return yield* Effect.fail(
|
|
493
|
-
new Error(
|
|
494
|
-
`bird is authenticated as @${authenticated.username}; refusing to sync into ${accountId} (@${username})`,
|
|
495
|
-
),
|
|
496
|
-
);
|
|
497
|
-
}
|
|
434
|
+
const authenticated =
|
|
435
|
+
yield* liveTransportGateway.bird.getAuthenticatedAccount();
|
|
436
|
+
return yield* Effect.try({
|
|
437
|
+
try: () =>
|
|
438
|
+
assertLiveAccountMatches({
|
|
439
|
+
source: "bird",
|
|
440
|
+
account,
|
|
441
|
+
liveUsername: authenticated.username,
|
|
442
|
+
liveExternalUserId: authenticated.id,
|
|
443
|
+
}),
|
|
444
|
+
catch: (error) =>
|
|
445
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
446
|
+
});
|
|
498
447
|
});
|
|
499
448
|
}
|
|
500
449
|
|
|
@@ -520,139 +469,20 @@ function findNewestArchiveMentionId(db: Database, accountId: string) {
|
|
|
520
469
|
return row?.id;
|
|
521
470
|
}
|
|
522
471
|
|
|
523
|
-
function toLocalEntities(tweet: XurlMentionData): TweetEntities {
|
|
524
|
-
const raw = tweet.entities;
|
|
525
|
-
if (!raw || typeof raw !== "object") {
|
|
526
|
-
return {};
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
const entities = raw as Record<string, unknown>;
|
|
530
|
-
const rawMentions = Array.isArray(entities.mentions) ? entities.mentions : [];
|
|
531
|
-
const rawUrls = Array.isArray(entities.urls) ? entities.urls : [];
|
|
532
|
-
|
|
533
|
-
return {
|
|
534
|
-
...(rawMentions.length
|
|
535
|
-
? {
|
|
536
|
-
mentions: rawMentions.map((mention) => {
|
|
537
|
-
const value =
|
|
538
|
-
mention && typeof mention === "object"
|
|
539
|
-
? (mention as Record<string, unknown>)
|
|
540
|
-
: {};
|
|
541
|
-
return {
|
|
542
|
-
username: String(value.username ?? ""),
|
|
543
|
-
id: typeof value.id === "string" ? String(value.id) : undefined,
|
|
544
|
-
start: Number(value.start ?? 0),
|
|
545
|
-
end: Number(value.end ?? 0),
|
|
546
|
-
};
|
|
547
|
-
}),
|
|
548
|
-
}
|
|
549
|
-
: {}),
|
|
550
|
-
...(rawUrls.length
|
|
551
|
-
? {
|
|
552
|
-
urls: rawUrls.map((url) => {
|
|
553
|
-
const value =
|
|
554
|
-
url && typeof url === "object"
|
|
555
|
-
? (url as Record<string, unknown>)
|
|
556
|
-
: {};
|
|
557
|
-
return {
|
|
558
|
-
url: String(value.url ?? ""),
|
|
559
|
-
expandedUrl: String(value.expanded_url ?? value.url ?? ""),
|
|
560
|
-
displayUrl: String(
|
|
561
|
-
value.display_url ?? value.expanded_url ?? value.url ?? "",
|
|
562
|
-
),
|
|
563
|
-
start: Number(value.start ?? 0),
|
|
564
|
-
end: Number(value.end ?? 0),
|
|
565
|
-
};
|
|
566
|
-
}),
|
|
567
|
-
}
|
|
568
|
-
: {}),
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
function replaceTweetFts(db: Database, tweetId: string, text: string) {
|
|
573
|
-
db.prepare("delete from tweets_fts where tweet_id = ?").run(tweetId);
|
|
574
|
-
db.prepare("insert into tweets_fts (tweet_id, text) values (?, ?)").run(
|
|
575
|
-
tweetId,
|
|
576
|
-
text,
|
|
577
|
-
);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
472
|
function mergeMentionsIntoLocalStore(
|
|
581
473
|
db: Database,
|
|
582
474
|
accountId: string,
|
|
583
475
|
payload: XurlMentionsResponse,
|
|
584
476
|
source: MentionsDataSource,
|
|
585
477
|
) {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
is_replied, reply_to_id, like_count, media_count, bookmarked, liked,
|
|
594
|
-
entities_json, media_json, quoted_tweet_id
|
|
595
|
-
) values (?, ?, ?, 'mention', ?, ?, 0, null, ?, ?, 0, 0, ?, ?, null)
|
|
596
|
-
on conflict(id) do update set
|
|
597
|
-
account_id = tweets.account_id,
|
|
598
|
-
author_profile_id = excluded.author_profile_id,
|
|
599
|
-
kind = case
|
|
600
|
-
when tweets.kind in ('authored', 'home', 'mention') then tweets.kind
|
|
601
|
-
when excluded.kind in ('authored', 'home', 'mention') then excluded.kind
|
|
602
|
-
else excluded.kind
|
|
603
|
-
end,
|
|
604
|
-
text = excluded.text,
|
|
605
|
-
created_at = excluded.created_at,
|
|
606
|
-
like_count = excluded.like_count,
|
|
607
|
-
media_count = max(tweets.media_count, excluded.media_count),
|
|
608
|
-
entities_json = excluded.entities_json,
|
|
609
|
-
media_json = case
|
|
610
|
-
when excluded.media_json not in ('', '[]', 'null') then excluded.media_json
|
|
611
|
-
else tweets.media_json
|
|
612
|
-
end,
|
|
613
|
-
is_replied = max(tweets.is_replied, excluded.is_replied),
|
|
614
|
-
bookmarked = max(tweets.bookmarked, excluded.bookmarked),
|
|
615
|
-
liked = max(tweets.liked, excluded.liked)
|
|
616
|
-
`,
|
|
617
|
-
);
|
|
618
|
-
|
|
619
|
-
const writePayload = db.transaction(() => {
|
|
620
|
-
const seenAt = new Date().toISOString();
|
|
621
|
-
for (const tweet of payload.data) {
|
|
622
|
-
const author =
|
|
623
|
-
usersById.get(tweet.author_id) ??
|
|
624
|
-
({
|
|
625
|
-
id: tweet.author_id,
|
|
626
|
-
username: `user_${tweet.author_id}`,
|
|
627
|
-
name: `user_${tweet.author_id}`,
|
|
628
|
-
} as const);
|
|
629
|
-
const profile = usersById.has(tweet.author_id)
|
|
630
|
-
? upsertProfileFromXUser(db, author)
|
|
631
|
-
: ensureStubProfileForXUser(db, tweet.author_id);
|
|
632
|
-
upsertTweet.run(
|
|
633
|
-
tweet.id,
|
|
634
|
-
accountId,
|
|
635
|
-
profile.profile.id,
|
|
636
|
-
tweet.text,
|
|
637
|
-
tweet.created_at,
|
|
638
|
-
Number(tweet.public_metrics?.like_count ?? 0),
|
|
639
|
-
countTweetMedia(tweet),
|
|
640
|
-
JSON.stringify(toLocalEntities(tweet)),
|
|
641
|
-
buildMediaJsonFromIncludes(tweet, payload.includes?.media),
|
|
642
|
-
);
|
|
643
|
-
upsertTweetAccountEdge(db, {
|
|
644
|
-
accountId,
|
|
645
|
-
tweetId: tweet.id,
|
|
646
|
-
kind: "mention",
|
|
647
|
-
source,
|
|
648
|
-
seenAt,
|
|
649
|
-
rawJson: JSON.stringify(tweet),
|
|
650
|
-
});
|
|
651
|
-
replaceTweetFts(db, tweet.id, tweet.text);
|
|
652
|
-
}
|
|
478
|
+
ingestTweetPayload(db, {
|
|
479
|
+
accountId,
|
|
480
|
+
payload,
|
|
481
|
+
kind: "mention",
|
|
482
|
+
edgeKind: "mention",
|
|
483
|
+
replaceSecondaryKind: true,
|
|
484
|
+
source,
|
|
653
485
|
});
|
|
654
|
-
|
|
655
|
-
writePayload();
|
|
656
486
|
}
|
|
657
487
|
|
|
658
488
|
function shouldReturnFilteredLocalPayload({
|
|
@@ -755,7 +585,7 @@ function fetchMentionsViaXurlEffect({
|
|
|
755
585
|
startTime,
|
|
756
586
|
onProgress,
|
|
757
587
|
}: {
|
|
758
|
-
resolvedAccount:
|
|
588
|
+
resolvedAccount: LiveSyncAccount;
|
|
759
589
|
limit: number;
|
|
760
590
|
all: boolean;
|
|
761
591
|
parsedMaxPages: number | null;
|
|
@@ -767,9 +597,9 @@ function fetchMentionsViaXurlEffect({
|
|
|
767
597
|
return Effect.gen(function* () {
|
|
768
598
|
const accountUserId =
|
|
769
599
|
resolvedAccount.externalUserId ??
|
|
770
|
-
(yield*
|
|
771
|
-
lookupUsersByHandles([resolvedAccount.username])
|
|
772
|
-
|
|
600
|
+
(yield* liveTransportGateway.xurl
|
|
601
|
+
.lookupUsersByHandles([resolvedAccount.username])
|
|
602
|
+
.pipe(Effect.map((users) => users[0]?.id)));
|
|
773
603
|
if (!accountUserId) {
|
|
774
604
|
return yield* Effect.fail(
|
|
775
605
|
new Error(
|
|
@@ -782,16 +612,14 @@ function fetchMentionsViaXurlEffect({
|
|
|
782
612
|
let nextToken: string | undefined = startPaginationToken;
|
|
783
613
|
let pageCount = 0;
|
|
784
614
|
do {
|
|
785
|
-
const payload = yield*
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
}),
|
|
794
|
-
);
|
|
615
|
+
const payload = yield* liveTransportGateway.xurl.listMentions({
|
|
616
|
+
maxResults: limit,
|
|
617
|
+
username: resolvedAccount.username,
|
|
618
|
+
userId: String(accountUserId),
|
|
619
|
+
paginationToken: nextToken,
|
|
620
|
+
...(sinceId ? { sinceId } : {}),
|
|
621
|
+
...(startTime ? { startTime } : {}),
|
|
622
|
+
});
|
|
795
623
|
pages.push(payload);
|
|
796
624
|
const metaNextToken =
|
|
797
625
|
typeof payload.meta?.next_token === "string"
|
|
@@ -826,7 +654,7 @@ function fetchMentionsViaXurlEffect({
|
|
|
826
654
|
}
|
|
827
655
|
|
|
828
656
|
function fetchMentionsViaBirdEffect({ limit }: { limit: number }) {
|
|
829
|
-
return
|
|
657
|
+
return liveTransportGateway.bird.listMentions({ maxResults: limit });
|
|
830
658
|
}
|
|
831
659
|
|
|
832
660
|
function isMaxPagesPartial({
|
|
@@ -876,7 +704,9 @@ export function syncMentionsEffect({
|
|
|
876
704
|
(parsedMaxPages !== null ||
|
|
877
705
|
Boolean(explicitSinceId || explicitStartTime));
|
|
878
706
|
const db = yield* trySync(() => getNativeDb());
|
|
879
|
-
const resolvedAccount = yield* trySync(() =>
|
|
707
|
+
const resolvedAccount = yield* trySync(() =>
|
|
708
|
+
resolveLiveSyncAccount(db, account),
|
|
709
|
+
);
|
|
880
710
|
const cursorShape: MentionScanShape = {
|
|
881
711
|
endpoint: "mentions",
|
|
882
712
|
mode: primaryMode,
|
|
@@ -966,9 +796,9 @@ export function syncMentionsEffect({
|
|
|
966
796
|
cached &&
|
|
967
797
|
cacheAgeMs <= ttlMs
|
|
968
798
|
) {
|
|
969
|
-
yield*
|
|
799
|
+
yield* databaseWriteEffect((writeDb) =>
|
|
970
800
|
mergeMentionsIntoLocalStore(
|
|
971
|
-
|
|
801
|
+
writeDb,
|
|
972
802
|
resolvedAccount.accountId,
|
|
973
803
|
cached.value,
|
|
974
804
|
primaryMode,
|
|
@@ -1046,9 +876,9 @@ export function syncMentionsEffect({
|
|
|
1046
876
|
}),
|
|
1047
877
|
);
|
|
1048
878
|
}
|
|
1049
|
-
yield*
|
|
879
|
+
yield* databaseWriteEffect((writeDb) =>
|
|
1050
880
|
mergeMentionsIntoLocalStore(
|
|
1051
|
-
|
|
881
|
+
writeDb,
|
|
1052
882
|
resolvedAccount.accountId,
|
|
1053
883
|
payload,
|
|
1054
884
|
source,
|
|
@@ -1157,7 +987,9 @@ function exportMentionsViaCachedLiveSourceEffect({
|
|
|
1157
987
|
const fetchAll = primaryMode === "xurl" && (all || parsedMaxPages !== null);
|
|
1158
988
|
|
|
1159
989
|
const db = yield* trySync(() => getNativeDb());
|
|
1160
|
-
const resolvedAccount = yield* trySync(() =>
|
|
990
|
+
const resolvedAccount = yield* trySync(() =>
|
|
991
|
+
resolveLiveSyncAccount(db, account),
|
|
992
|
+
);
|
|
1161
993
|
const cacheKey = getMentionsFetchModeKey({
|
|
1162
994
|
scope: "export",
|
|
1163
995
|
mode,
|
|
@@ -1215,14 +1047,14 @@ function exportMentionsViaCachedLiveSourceEffect({
|
|
|
1215
1047
|
);
|
|
1216
1048
|
}),
|
|
1217
1049
|
Effect.flatMap((payload) =>
|
|
1218
|
-
|
|
1050
|
+
databaseWriteEffect((writeDb) => {
|
|
1219
1051
|
mergeMentionsIntoLocalStore(
|
|
1220
|
-
|
|
1052
|
+
writeDb,
|
|
1221
1053
|
resolvedAccount.accountId,
|
|
1222
1054
|
payload,
|
|
1223
1055
|
source,
|
|
1224
1056
|
);
|
|
1225
|
-
writeSyncCache(cacheKey, payload,
|
|
1057
|
+
writeSyncCache(cacheKey, payload, writeDb);
|
|
1226
1058
|
return readFilteredOrRaw(payload);
|
|
1227
1059
|
}),
|
|
1228
1060
|
),
|
|
@@ -2,6 +2,7 @@ import type { Database } from "./sqlite";
|
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
import { lookupProfileViaBird } from "./bird-actions";
|
|
4
4
|
import { getNativeDb } from "./db";
|
|
5
|
+
import { databaseWriteEffect } from "./database-writer";
|
|
5
6
|
import { runEffectPromise, tryPromise } from "./effect-runtime";
|
|
6
7
|
import type { ProfileRecord, XurlMentionUser } from "./types";
|
|
7
8
|
import { getExternalUserId, upsertProfileFromXUser } from "./x-profile";
|
|
@@ -105,9 +106,10 @@ export function resolveLocalProfile(
|
|
|
105
106
|
|
|
106
107
|
export function resolveProfileEffect(
|
|
107
108
|
query: string,
|
|
109
|
+
providedDb?: Database,
|
|
108
110
|
): Effect.Effect<ResolvedModerationProfile, unknown> {
|
|
109
111
|
return Effect.gen(function* () {
|
|
110
|
-
const db = yield* trySync(() => getNativeDb());
|
|
112
|
+
const db = providedDb ?? (yield* trySync(() => getNativeDb()));
|
|
111
113
|
const normalizedQuery = normalizeProfileQuery(query);
|
|
112
114
|
if (!normalizedQuery) {
|
|
113
115
|
return yield* Effect.fail(new Error("Missing profile handle or id"));
|
|
@@ -180,7 +182,10 @@ export function resolveProfileEffect(
|
|
|
180
182
|
}
|
|
181
183
|
|
|
182
184
|
if (local) {
|
|
183
|
-
return yield*
|
|
185
|
+
return yield* databaseWriteEffect(
|
|
186
|
+
(writeDb) => upsertProfileFromXUser(writeDb, user),
|
|
187
|
+
db,
|
|
188
|
+
);
|
|
184
189
|
}
|
|
185
190
|
|
|
186
191
|
const username = String(user.username ?? "").replace(/^@/, "");
|
|
@@ -189,11 +194,17 @@ export function resolveProfileEffect(
|
|
|
189
194
|
resolveLocalProfile(db, username),
|
|
190
195
|
);
|
|
191
196
|
if (localByHandle) {
|
|
192
|
-
return yield*
|
|
197
|
+
return yield* databaseWriteEffect(
|
|
198
|
+
(writeDb) => upsertProfileFromXUser(writeDb, user),
|
|
199
|
+
db,
|
|
200
|
+
);
|
|
193
201
|
}
|
|
194
202
|
}
|
|
195
203
|
|
|
196
|
-
return yield*
|
|
204
|
+
return yield* databaseWriteEffect(
|
|
205
|
+
(writeDb) => upsertProfileFromXUser(writeDb, user),
|
|
206
|
+
db,
|
|
207
|
+
);
|
|
197
208
|
});
|
|
198
209
|
}
|
|
199
210
|
|
|
@@ -53,7 +53,7 @@ export function resolveModerationTargetEffect({
|
|
|
53
53
|
return yield* Effect.fail(new Error(selfActionError));
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
const resolved = yield* resolveProfileEffect(query);
|
|
56
|
+
const resolved = yield* resolveProfileEffect(query, db);
|
|
57
57
|
const account = yield* trySync(
|
|
58
58
|
() =>
|
|
59
59
|
db
|
package/src/lib/mutes-write.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Effect } from "effect";
|
|
2
2
|
import { runModerationAction } from "./actions-transport";
|
|
3
|
+
import { databaseWriteEffect } from "./database-writer";
|
|
3
4
|
import { runEffectPromise, tryPromise } from "./effect-runtime";
|
|
4
5
|
import {
|
|
5
6
|
deleteModerationRow,
|
|
@@ -8,14 +9,6 @@ import {
|
|
|
8
9
|
writeModerationRow,
|
|
9
10
|
} from "./moderation-write";
|
|
10
11
|
|
|
11
|
-
function trySync<T>(try_: () => T) {
|
|
12
|
-
return Effect.try({
|
|
13
|
-
try: try_,
|
|
14
|
-
catch: (error) =>
|
|
15
|
-
error instanceof Error ? error : new Error(String(error)),
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
12
|
export function addMuteEffect(
|
|
20
13
|
accountId: string,
|
|
21
14
|
query: string,
|
|
@@ -49,14 +42,16 @@ export function addMuteEffect(
|
|
|
49
42
|
}
|
|
50
43
|
|
|
51
44
|
const mutedAt = new Date().toISOString();
|
|
52
|
-
yield*
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
45
|
+
yield* databaseWriteEffect(
|
|
46
|
+
(writeDb) =>
|
|
47
|
+
writeModerationRow(
|
|
48
|
+
writeDb,
|
|
49
|
+
"mutes",
|
|
50
|
+
resolvedAccountId,
|
|
51
|
+
resolved.profile.id,
|
|
52
|
+
mutedAt,
|
|
53
|
+
),
|
|
54
|
+
db,
|
|
60
55
|
);
|
|
61
56
|
|
|
62
57
|
return {
|
|
@@ -88,14 +83,16 @@ export function recordMuteEffect(accountId: string, query: string) {
|
|
|
88
83
|
});
|
|
89
84
|
|
|
90
85
|
const mutedAt = new Date().toISOString();
|
|
91
|
-
yield*
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
86
|
+
yield* databaseWriteEffect(
|
|
87
|
+
(writeDb) =>
|
|
88
|
+
writeModerationRow(
|
|
89
|
+
writeDb,
|
|
90
|
+
"mutes",
|
|
91
|
+
resolvedAccountId,
|
|
92
|
+
resolved.profile.id,
|
|
93
|
+
mutedAt,
|
|
94
|
+
),
|
|
95
|
+
db,
|
|
99
96
|
);
|
|
100
97
|
|
|
101
98
|
return {
|
|
@@ -144,8 +141,15 @@ export function removeMuteEffect(
|
|
|
144
141
|
};
|
|
145
142
|
}
|
|
146
143
|
|
|
147
|
-
yield*
|
|
148
|
-
|
|
144
|
+
yield* databaseWriteEffect(
|
|
145
|
+
(writeDb) =>
|
|
146
|
+
deleteModerationRow(
|
|
147
|
+
writeDb,
|
|
148
|
+
"mutes",
|
|
149
|
+
resolvedAccountId,
|
|
150
|
+
resolved.profile.id,
|
|
151
|
+
),
|
|
152
|
+
db,
|
|
149
153
|
);
|
|
150
154
|
|
|
151
155
|
return {
|