birdclaw 0.2.1 → 0.4.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.
- package/CHANGELOG.md +44 -1
- package/README.md +54 -15
- package/package.json +14 -15
- package/playwright.config.ts +5 -2
- package/src/cli.ts +289 -20
- package/src/lib/actions-transport.ts +58 -1
- package/src/lib/archive-import.ts +34 -2
- package/src/lib/backup.ts +271 -33
- package/src/lib/bird-actions.ts +21 -0
- package/src/lib/bird.ts +332 -17
- package/src/lib/blocks.ts +3 -3
- package/src/lib/bookmark-sync-job.ts +3 -3
- package/src/lib/config.ts +34 -1
- package/src/lib/db.ts +321 -14
- package/src/lib/dms-live.ts +3 -3
- package/src/lib/identity-search-index.ts +369 -0
- package/src/lib/mention-threads-live.ts +267 -0
- package/src/lib/mentions-live.ts +18 -7
- package/src/lib/moderation-target.ts +7 -5
- package/src/lib/moderation-write.ts +3 -3
- package/src/lib/profile-affiliation-hydration.ts +189 -0
- package/src/lib/profile-affiliations.ts +262 -0
- package/src/lib/profile-bio-entities.ts +318 -0
- package/src/lib/profile-history.ts +164 -0
- package/src/lib/profile-hydration.ts +8 -24
- package/src/lib/profile-resolver.ts +428 -0
- package/src/lib/queries.ts +522 -68
- package/src/lib/research.ts +607 -0
- package/src/lib/seed.ts +10 -4
- package/src/lib/sqlite.ts +143 -0
- package/src/lib/timeline-collections-live.ts +22 -8
- package/src/lib/timeline-live.ts +182 -0
- package/src/lib/tweet-account-edges.ts +39 -0
- package/src/lib/tweet-lookup.ts +35 -0
- package/src/lib/types.ts +103 -1
- package/src/lib/url-expansion.ts +147 -0
- package/src/lib/whois.ts +1060 -0
- package/src/lib/x-profile.ts +174 -17
- package/src/lib/xurl.ts +41 -6
package/src/lib/db.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import NativeSqliteDatabase, { type Database } from "./sqlite";
|
|
2
2
|
import { Kysely, SqliteDialect } from "kysely";
|
|
3
3
|
import { ensureBirdclawDirs, getBirdclawPaths } from "./config";
|
|
4
4
|
import { seedDemoData } from "./seed";
|
|
@@ -19,11 +19,72 @@ export interface ProfilesTable {
|
|
|
19
19
|
display_name: string;
|
|
20
20
|
bio: string;
|
|
21
21
|
followers_count: number;
|
|
22
|
+
following_count: number;
|
|
22
23
|
avatar_hue: number;
|
|
23
24
|
avatar_url: string | null;
|
|
25
|
+
location: string | null;
|
|
26
|
+
url: string | null;
|
|
27
|
+
verified_type: string | null;
|
|
28
|
+
entities_json: string;
|
|
29
|
+
raw_json: string;
|
|
24
30
|
created_at: string;
|
|
25
31
|
}
|
|
26
32
|
|
|
33
|
+
export interface ProfileAffiliationsTable {
|
|
34
|
+
subject_profile_id: string;
|
|
35
|
+
organization_profile_id: string;
|
|
36
|
+
organization_name: string | null;
|
|
37
|
+
organization_handle: string | null;
|
|
38
|
+
badge_url: string | null;
|
|
39
|
+
url: string | null;
|
|
40
|
+
label: string | null;
|
|
41
|
+
source: string;
|
|
42
|
+
is_active: number;
|
|
43
|
+
first_seen_at: string;
|
|
44
|
+
last_seen_at: string;
|
|
45
|
+
raw_json: string;
|
|
46
|
+
updated_at: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ProfileSnapshotsTable {
|
|
50
|
+
profile_id: string;
|
|
51
|
+
snapshot_hash: string;
|
|
52
|
+
observed_at: string;
|
|
53
|
+
last_seen_at: string;
|
|
54
|
+
source: string;
|
|
55
|
+
handle: string;
|
|
56
|
+
display_name: string;
|
|
57
|
+
bio: string;
|
|
58
|
+
location: string | null;
|
|
59
|
+
url: string | null;
|
|
60
|
+
verified_type: string | null;
|
|
61
|
+
followers_count: number;
|
|
62
|
+
following_count: number;
|
|
63
|
+
affiliations_json: string;
|
|
64
|
+
raw_json: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ProfileBioEntitiesTable {
|
|
68
|
+
profile_id: string;
|
|
69
|
+
kind: string;
|
|
70
|
+
value: string;
|
|
71
|
+
source: string;
|
|
72
|
+
is_active: number;
|
|
73
|
+
first_seen_at: string;
|
|
74
|
+
last_seen_at: string;
|
|
75
|
+
raw_json: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface IdentitySearchIndexTable {
|
|
79
|
+
profile_id: string;
|
|
80
|
+
kind: string;
|
|
81
|
+
value: string;
|
|
82
|
+
normalized_value: string;
|
|
83
|
+
source: string;
|
|
84
|
+
weight: number;
|
|
85
|
+
updated_at: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
27
88
|
export interface TweetsTable {
|
|
28
89
|
id: string;
|
|
29
90
|
account_id: string;
|
|
@@ -52,6 +113,18 @@ export interface TweetCollectionsTable {
|
|
|
52
113
|
updated_at: string;
|
|
53
114
|
}
|
|
54
115
|
|
|
116
|
+
export interface TweetAccountEdgesTable {
|
|
117
|
+
account_id: string;
|
|
118
|
+
tweet_id: string;
|
|
119
|
+
kind: "home" | "mention";
|
|
120
|
+
first_seen_at: string;
|
|
121
|
+
last_seen_at: string;
|
|
122
|
+
seen_count: number;
|
|
123
|
+
source: string;
|
|
124
|
+
raw_json: string;
|
|
125
|
+
updated_at: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
55
128
|
export interface DmConversationsTable {
|
|
56
129
|
id: string;
|
|
57
130
|
account_id: string;
|
|
@@ -115,8 +188,13 @@ export interface SyncCacheTable {
|
|
|
115
188
|
export interface BirdclawDatabase {
|
|
116
189
|
accounts: AccountsTable;
|
|
117
190
|
profiles: ProfilesTable;
|
|
191
|
+
profile_affiliations: ProfileAffiliationsTable;
|
|
192
|
+
profile_snapshots: ProfileSnapshotsTable;
|
|
193
|
+
profile_bio_entities: ProfileBioEntitiesTable;
|
|
194
|
+
identity_search_index: IdentitySearchIndexTable;
|
|
118
195
|
tweets: TweetsTable;
|
|
119
196
|
tweet_collections: TweetCollectionsTable;
|
|
197
|
+
tweet_account_edges: TweetAccountEdgesTable;
|
|
120
198
|
dm_conversations: DmConversationsTable;
|
|
121
199
|
dm_messages: DmMessagesTable;
|
|
122
200
|
tweet_actions: TweetActionsTable;
|
|
@@ -126,7 +204,7 @@ export interface BirdclawDatabase {
|
|
|
126
204
|
sync_cache: SyncCacheTable;
|
|
127
205
|
}
|
|
128
206
|
|
|
129
|
-
let nativeDb:
|
|
207
|
+
let nativeDb: Database | undefined;
|
|
130
208
|
let kyselyDb: Kysely<BirdclawDatabase> | undefined;
|
|
131
209
|
|
|
132
210
|
export interface InitDatabaseOptions {
|
|
@@ -154,11 +232,76 @@ const BASE_SCHEMA_SQL = `
|
|
|
154
232
|
display_name text not null,
|
|
155
233
|
bio text not null,
|
|
156
234
|
followers_count integer not null default 0,
|
|
235
|
+
following_count integer not null default 0,
|
|
157
236
|
avatar_hue integer not null default 0,
|
|
158
237
|
avatar_url text,
|
|
238
|
+
location text,
|
|
239
|
+
url text,
|
|
240
|
+
verified_type text,
|
|
241
|
+
entities_json text not null default '{}',
|
|
242
|
+
raw_json text not null default '{}',
|
|
159
243
|
created_at text not null
|
|
160
244
|
);
|
|
161
245
|
|
|
246
|
+
create table if not exists profile_affiliations (
|
|
247
|
+
subject_profile_id text not null,
|
|
248
|
+
organization_profile_id text not null,
|
|
249
|
+
organization_name text,
|
|
250
|
+
organization_handle text,
|
|
251
|
+
badge_url text,
|
|
252
|
+
url text,
|
|
253
|
+
label text,
|
|
254
|
+
source text not null,
|
|
255
|
+
is_active integer not null default 1,
|
|
256
|
+
first_seen_at text not null,
|
|
257
|
+
last_seen_at text not null,
|
|
258
|
+
raw_json text not null default '{}',
|
|
259
|
+
updated_at text not null,
|
|
260
|
+
primary key (subject_profile_id, organization_profile_id)
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
create table if not exists profile_snapshots (
|
|
264
|
+
profile_id text not null,
|
|
265
|
+
snapshot_hash text not null,
|
|
266
|
+
observed_at text not null,
|
|
267
|
+
last_seen_at text not null,
|
|
268
|
+
source text not null,
|
|
269
|
+
handle text not null,
|
|
270
|
+
display_name text not null,
|
|
271
|
+
bio text not null,
|
|
272
|
+
location text,
|
|
273
|
+
url text,
|
|
274
|
+
verified_type text,
|
|
275
|
+
followers_count integer not null default 0,
|
|
276
|
+
following_count integer not null default 0,
|
|
277
|
+
affiliations_json text not null default '[]',
|
|
278
|
+
raw_json text not null default '{}',
|
|
279
|
+
primary key (profile_id, snapshot_hash)
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
create table if not exists profile_bio_entities (
|
|
283
|
+
profile_id text not null,
|
|
284
|
+
kind text not null,
|
|
285
|
+
value text not null,
|
|
286
|
+
source text not null,
|
|
287
|
+
is_active integer not null default 1,
|
|
288
|
+
first_seen_at text not null,
|
|
289
|
+
last_seen_at text not null,
|
|
290
|
+
raw_json text not null default '{}',
|
|
291
|
+
primary key (profile_id, kind, value)
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
create table if not exists identity_search_index (
|
|
295
|
+
profile_id text not null,
|
|
296
|
+
kind text not null,
|
|
297
|
+
value text not null,
|
|
298
|
+
normalized_value text not null,
|
|
299
|
+
source text not null,
|
|
300
|
+
weight integer not null,
|
|
301
|
+
updated_at text not null,
|
|
302
|
+
primary key (profile_id, kind, value, source)
|
|
303
|
+
);
|
|
304
|
+
|
|
162
305
|
create table if not exists tweets (
|
|
163
306
|
id text primary key,
|
|
164
307
|
account_id text not null,
|
|
@@ -188,6 +331,19 @@ const BASE_SCHEMA_SQL = `
|
|
|
188
331
|
primary key (account_id, tweet_id, kind)
|
|
189
332
|
);
|
|
190
333
|
|
|
334
|
+
create table if not exists tweet_account_edges (
|
|
335
|
+
account_id text not null,
|
|
336
|
+
tweet_id text not null,
|
|
337
|
+
kind text not null,
|
|
338
|
+
first_seen_at text not null,
|
|
339
|
+
last_seen_at text not null,
|
|
340
|
+
seen_count integer not null default 1,
|
|
341
|
+
source text not null,
|
|
342
|
+
raw_json text not null default '{}',
|
|
343
|
+
updated_at text not null,
|
|
344
|
+
primary key (account_id, tweet_id, kind)
|
|
345
|
+
);
|
|
346
|
+
|
|
191
347
|
create table if not exists dm_conversations (
|
|
192
348
|
id text primary key,
|
|
193
349
|
account_id text not null,
|
|
@@ -268,26 +424,33 @@ const INDEX_SQL = `
|
|
|
268
424
|
create index if not exists idx_tweets_quoted on tweets(quoted_tweet_id);
|
|
269
425
|
create index if not exists idx_tweet_collections_kind_account on tweet_collections(kind, account_id, collected_at desc, tweet_id);
|
|
270
426
|
create index if not exists idx_tweet_collections_tweet on tweet_collections(tweet_id);
|
|
427
|
+
create index if not exists idx_tweet_account_edges_kind_account on tweet_account_edges(kind, account_id, last_seen_at desc, tweet_id);
|
|
428
|
+
create index if not exists idx_tweet_account_edges_tweet on tweet_account_edges(tweet_id);
|
|
271
429
|
create index if not exists idx_dm_conversations_account on dm_conversations(account_id, last_message_at desc);
|
|
272
430
|
create index if not exists idx_dm_messages_conversation on dm_messages(conversation_id, created_at asc);
|
|
273
431
|
create index if not exists idx_profiles_followers on profiles(followers_count desc);
|
|
432
|
+
create index if not exists idx_profiles_following on profiles(following_count desc);
|
|
433
|
+
create index if not exists idx_profile_affiliations_subject on profile_affiliations(subject_profile_id, is_active, last_seen_at desc);
|
|
434
|
+
create index if not exists idx_profile_affiliations_org on profile_affiliations(organization_profile_id, is_active, last_seen_at desc);
|
|
435
|
+
create index if not exists idx_profile_snapshots_profile on profile_snapshots(profile_id, last_seen_at desc);
|
|
436
|
+
create index if not exists idx_profile_bio_entities_profile on profile_bio_entities(profile_id, is_active, last_seen_at desc);
|
|
437
|
+
create index if not exists idx_profile_bio_entities_value on profile_bio_entities(kind, value, is_active);
|
|
438
|
+
create index if not exists idx_identity_search_index_profile on identity_search_index(profile_id);
|
|
439
|
+
create index if not exists idx_identity_search_index_value on identity_search_index(normalized_value, kind, weight desc);
|
|
274
440
|
create index if not exists idx_blocks_account_created on blocks(account_id, created_at desc);
|
|
275
441
|
create index if not exists idx_mutes_account_created on mutes(account_id, created_at desc);
|
|
276
442
|
create index if not exists idx_ai_scores_updated on ai_scores(updated_at desc);
|
|
277
443
|
create index if not exists idx_sync_cache_updated on sync_cache(updated_at desc);
|
|
278
444
|
`;
|
|
279
445
|
|
|
280
|
-
function getColumnNames(
|
|
281
|
-
db: BetterSqlite3.Database,
|
|
282
|
-
tableName: string,
|
|
283
|
-
): Set<string> {
|
|
446
|
+
function getColumnNames(db: Database, tableName: string): Set<string> {
|
|
284
447
|
const rows = db.prepare(`pragma table_info(${tableName})`).all() as Array<{
|
|
285
448
|
name: string;
|
|
286
449
|
}>;
|
|
287
450
|
return new Set(rows.map((row) => row.name));
|
|
288
451
|
}
|
|
289
452
|
|
|
290
|
-
function ensureTweetMetadataColumns(db:
|
|
453
|
+
function ensureTweetMetadataColumns(db: Database) {
|
|
291
454
|
const columnNames = getColumnNames(db, "tweets");
|
|
292
455
|
if (!columnNames.has("entities_json")) {
|
|
293
456
|
db.exec(
|
|
@@ -304,21 +467,45 @@ function ensureTweetMetadataColumns(db: BetterSqlite3.Database) {
|
|
|
304
467
|
}
|
|
305
468
|
}
|
|
306
469
|
|
|
307
|
-
function ensureProfileAvatarColumns(db:
|
|
470
|
+
function ensureProfileAvatarColumns(db: Database) {
|
|
308
471
|
const columnNames = getColumnNames(db, "profiles");
|
|
472
|
+
if (!columnNames.has("following_count")) {
|
|
473
|
+
db.exec(
|
|
474
|
+
"alter table profiles add column following_count integer not null default 0",
|
|
475
|
+
);
|
|
476
|
+
}
|
|
309
477
|
if (!columnNames.has("avatar_url")) {
|
|
310
478
|
db.exec("alter table profiles add column avatar_url text");
|
|
311
479
|
}
|
|
480
|
+
if (!columnNames.has("location")) {
|
|
481
|
+
db.exec("alter table profiles add column location text");
|
|
482
|
+
}
|
|
483
|
+
if (!columnNames.has("url")) {
|
|
484
|
+
db.exec("alter table profiles add column url text");
|
|
485
|
+
}
|
|
486
|
+
if (!columnNames.has("verified_type")) {
|
|
487
|
+
db.exec("alter table profiles add column verified_type text");
|
|
488
|
+
}
|
|
489
|
+
if (!columnNames.has("entities_json")) {
|
|
490
|
+
db.exec(
|
|
491
|
+
"alter table profiles add column entities_json text not null default '{}'",
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
if (!columnNames.has("raw_json")) {
|
|
495
|
+
db.exec(
|
|
496
|
+
"alter table profiles add column raw_json text not null default '{}'",
|
|
497
|
+
);
|
|
498
|
+
}
|
|
312
499
|
}
|
|
313
500
|
|
|
314
|
-
function ensureAccountExternalUserIdColumn(db:
|
|
501
|
+
function ensureAccountExternalUserIdColumn(db: Database) {
|
|
315
502
|
const columnNames = getColumnNames(db, "accounts");
|
|
316
503
|
if (!columnNames.has("external_user_id")) {
|
|
317
504
|
db.exec("alter table accounts add column external_user_id text");
|
|
318
505
|
}
|
|
319
506
|
}
|
|
320
507
|
|
|
321
|
-
function ensureTweetCollectionsTable(db:
|
|
508
|
+
function ensureTweetCollectionsTable(db: Database) {
|
|
322
509
|
db.exec(`
|
|
323
510
|
create table if not exists tweet_collections (
|
|
324
511
|
account_id text not null,
|
|
@@ -333,7 +520,99 @@ function ensureTweetCollectionsTable(db: BetterSqlite3.Database) {
|
|
|
333
520
|
`);
|
|
334
521
|
}
|
|
335
522
|
|
|
336
|
-
function
|
|
523
|
+
function ensureTweetAccountEdgesTable(db: Database) {
|
|
524
|
+
db.exec(`
|
|
525
|
+
create table if not exists tweet_account_edges (
|
|
526
|
+
account_id text not null,
|
|
527
|
+
tweet_id text not null,
|
|
528
|
+
kind text not null,
|
|
529
|
+
first_seen_at text not null,
|
|
530
|
+
last_seen_at text not null,
|
|
531
|
+
seen_count integer not null default 1,
|
|
532
|
+
source text not null,
|
|
533
|
+
raw_json text not null default '{}',
|
|
534
|
+
updated_at text not null,
|
|
535
|
+
primary key (account_id, tweet_id, kind)
|
|
536
|
+
);
|
|
537
|
+
`);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function ensureProfileAffiliationsTable(db: Database) {
|
|
541
|
+
db.exec(`
|
|
542
|
+
create table if not exists profile_affiliations (
|
|
543
|
+
subject_profile_id text not null,
|
|
544
|
+
organization_profile_id text not null,
|
|
545
|
+
organization_name text,
|
|
546
|
+
organization_handle text,
|
|
547
|
+
badge_url text,
|
|
548
|
+
url text,
|
|
549
|
+
label text,
|
|
550
|
+
source text not null,
|
|
551
|
+
is_active integer not null default 1,
|
|
552
|
+
first_seen_at text not null,
|
|
553
|
+
last_seen_at text not null,
|
|
554
|
+
raw_json text not null default '{}',
|
|
555
|
+
updated_at text not null,
|
|
556
|
+
primary key (subject_profile_id, organization_profile_id)
|
|
557
|
+
);
|
|
558
|
+
`);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function ensureProfileSnapshotsTable(db: Database) {
|
|
562
|
+
db.exec(`
|
|
563
|
+
create table if not exists profile_snapshots (
|
|
564
|
+
profile_id text not null,
|
|
565
|
+
snapshot_hash text not null,
|
|
566
|
+
observed_at text not null,
|
|
567
|
+
last_seen_at text not null,
|
|
568
|
+
source text not null,
|
|
569
|
+
handle text not null,
|
|
570
|
+
display_name text not null,
|
|
571
|
+
bio text not null,
|
|
572
|
+
location text,
|
|
573
|
+
url text,
|
|
574
|
+
verified_type text,
|
|
575
|
+
followers_count integer not null default 0,
|
|
576
|
+
following_count integer not null default 0,
|
|
577
|
+
affiliations_json text not null default '[]',
|
|
578
|
+
raw_json text not null default '{}',
|
|
579
|
+
primary key (profile_id, snapshot_hash)
|
|
580
|
+
);
|
|
581
|
+
`);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function ensureProfileBioEntitiesTable(db: Database) {
|
|
585
|
+
db.exec(`
|
|
586
|
+
create table if not exists profile_bio_entities (
|
|
587
|
+
profile_id text not null,
|
|
588
|
+
kind text not null,
|
|
589
|
+
value text not null,
|
|
590
|
+
source text not null,
|
|
591
|
+
is_active integer not null default 1,
|
|
592
|
+
first_seen_at text not null,
|
|
593
|
+
last_seen_at text not null,
|
|
594
|
+
raw_json text not null default '{}',
|
|
595
|
+
primary key (profile_id, kind, value)
|
|
596
|
+
);
|
|
597
|
+
`);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function ensureIdentitySearchIndexTable(db: Database) {
|
|
601
|
+
db.exec(`
|
|
602
|
+
create table if not exists identity_search_index (
|
|
603
|
+
profile_id text not null,
|
|
604
|
+
kind text not null,
|
|
605
|
+
value text not null,
|
|
606
|
+
normalized_value text not null,
|
|
607
|
+
source text not null,
|
|
608
|
+
weight integer not null,
|
|
609
|
+
updated_at text not null,
|
|
610
|
+
primary key (profile_id, kind, value, source)
|
|
611
|
+
);
|
|
612
|
+
`);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function backfillTweetCollections(db: Database) {
|
|
337
616
|
const now = new Date().toISOString();
|
|
338
617
|
const insert = db.prepare(`
|
|
339
618
|
insert or ignore into tweet_collections (
|
|
@@ -354,7 +633,29 @@ function backfillTweetCollections(db: BetterSqlite3.Database) {
|
|
|
354
633
|
})();
|
|
355
634
|
}
|
|
356
635
|
|
|
357
|
-
function
|
|
636
|
+
function backfillTweetAccountEdges(db: Database) {
|
|
637
|
+
const now = new Date().toISOString();
|
|
638
|
+
db.prepare(`
|
|
639
|
+
insert or ignore into tweet_account_edges (
|
|
640
|
+
account_id, tweet_id, kind, first_seen_at, last_seen_at, seen_count,
|
|
641
|
+
source, raw_json, updated_at
|
|
642
|
+
)
|
|
643
|
+
select
|
|
644
|
+
account_id,
|
|
645
|
+
id,
|
|
646
|
+
kind,
|
|
647
|
+
created_at,
|
|
648
|
+
created_at,
|
|
649
|
+
1,
|
|
650
|
+
'legacy',
|
|
651
|
+
'{}',
|
|
652
|
+
?
|
|
653
|
+
from tweets
|
|
654
|
+
where kind in ('home', 'mention')
|
|
655
|
+
`).run(now);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function ensureSchemaIndexes(db: Database) {
|
|
358
659
|
db.exec(INDEX_SQL);
|
|
359
660
|
}
|
|
360
661
|
|
|
@@ -363,17 +664,23 @@ function initDatabase(options: InitDatabaseOptions = {}) {
|
|
|
363
664
|
|
|
364
665
|
if (!nativeDb) {
|
|
365
666
|
const { dbPath } = getBirdclawPaths();
|
|
366
|
-
nativeDb = new
|
|
667
|
+
nativeDb = new NativeSqliteDatabase(dbPath);
|
|
367
668
|
nativeDb.exec(BASE_SCHEMA_SQL);
|
|
368
669
|
ensureAccountExternalUserIdColumn(nativeDb);
|
|
369
670
|
ensureTweetMetadataColumns(nativeDb);
|
|
370
671
|
ensureProfileAvatarColumns(nativeDb);
|
|
371
672
|
ensureTweetCollectionsTable(nativeDb);
|
|
673
|
+
ensureTweetAccountEdgesTable(nativeDb);
|
|
674
|
+
ensureProfileAffiliationsTable(nativeDb);
|
|
675
|
+
ensureProfileSnapshotsTable(nativeDb);
|
|
676
|
+
ensureProfileBioEntitiesTable(nativeDb);
|
|
677
|
+
ensureIdentitySearchIndexTable(nativeDb);
|
|
372
678
|
ensureSchemaIndexes(nativeDb);
|
|
373
679
|
if (options.seedDemoData !== false) {
|
|
374
680
|
seedDemoData(nativeDb);
|
|
375
681
|
}
|
|
376
682
|
backfillTweetCollections(nativeDb);
|
|
683
|
+
backfillTweetAccountEdges(nativeDb);
|
|
377
684
|
}
|
|
378
685
|
|
|
379
686
|
if (!kyselyDb) {
|
|
@@ -387,7 +694,7 @@ function initDatabase(options: InitDatabaseOptions = {}) {
|
|
|
387
694
|
|
|
388
695
|
export function getNativeDb(options: InitDatabaseOptions = {}) {
|
|
389
696
|
initDatabase(options);
|
|
390
|
-
return nativeDb as
|
|
697
|
+
return nativeDb as Database;
|
|
391
698
|
}
|
|
392
699
|
|
|
393
700
|
export function getDb() {
|
package/src/lib/dms-live.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type Database from "
|
|
1
|
+
import type { Database } from "./sqlite";
|
|
2
2
|
import {
|
|
3
3
|
type BirdDmConversation,
|
|
4
4
|
type BirdDmEvent,
|
|
@@ -25,7 +25,7 @@ function assertBirdLimit(limit: number) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function resolveAccount(db: Database
|
|
28
|
+
function resolveAccount(db: Database, accountId?: string) {
|
|
29
29
|
const row = accountId
|
|
30
30
|
? (db
|
|
31
31
|
.prepare("select id, handle from accounts where id = ?")
|
|
@@ -112,7 +112,7 @@ function getLatestEvent(events: BirdDmEvent[]) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
function mergeDirectMessagesIntoLocalStore(
|
|
115
|
-
db: Database
|
|
115
|
+
db: Database,
|
|
116
116
|
accountId: string,
|
|
117
117
|
accountUsername: string,
|
|
118
118
|
payload: {
|