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/db.ts
CHANGED
|
@@ -4,322 +4,22 @@ import NativeSqliteDatabase, {
|
|
|
4
4
|
} from "./sqlite";
|
|
5
5
|
import { Kysely, SqliteDialect } from "kysely";
|
|
6
6
|
import { ensureBirdclawDirs, getBirdclawPaths } from "./config";
|
|
7
|
+
import {
|
|
8
|
+
type DatabaseMigration,
|
|
9
|
+
runDatabaseMigrations,
|
|
10
|
+
} from "./database-migrations";
|
|
7
11
|
import { seedDemoData } from "./seed";
|
|
12
|
+
import {
|
|
13
|
+
type DatabaseConnectionRole,
|
|
14
|
+
recordDatabaseStatement,
|
|
15
|
+
} from "./database-metrics";
|
|
8
16
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
name: string;
|
|
12
|
-
handle: string;
|
|
13
|
-
external_user_id: string | null;
|
|
14
|
-
transport: string;
|
|
15
|
-
is_default: number;
|
|
16
|
-
created_at: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface ProfilesTable {
|
|
20
|
-
id: string;
|
|
21
|
-
handle: string;
|
|
22
|
-
display_name: string;
|
|
23
|
-
bio: string;
|
|
24
|
-
followers_count: number;
|
|
25
|
-
following_count: number;
|
|
26
|
-
public_metrics_json: string;
|
|
27
|
-
avatar_hue: number;
|
|
28
|
-
avatar_url: string | null;
|
|
29
|
-
location: string | null;
|
|
30
|
-
url: string | null;
|
|
31
|
-
verified_type: string | null;
|
|
32
|
-
entities_json: string;
|
|
33
|
-
raw_json: string;
|
|
34
|
-
created_at: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface ProfileAffiliationsTable {
|
|
38
|
-
subject_profile_id: string;
|
|
39
|
-
organization_profile_id: string;
|
|
40
|
-
organization_name: string | null;
|
|
41
|
-
organization_handle: string | null;
|
|
42
|
-
badge_url: string | null;
|
|
43
|
-
url: string | null;
|
|
44
|
-
label: string | null;
|
|
45
|
-
source: string;
|
|
46
|
-
is_active: number;
|
|
47
|
-
first_seen_at: string;
|
|
48
|
-
last_seen_at: string;
|
|
49
|
-
raw_json: string;
|
|
50
|
-
updated_at: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface ProfileSnapshotsTable {
|
|
54
|
-
profile_id: string;
|
|
55
|
-
snapshot_hash: string;
|
|
56
|
-
observed_at: string;
|
|
57
|
-
last_seen_at: string;
|
|
58
|
-
source: string;
|
|
59
|
-
handle: string;
|
|
60
|
-
display_name: string;
|
|
61
|
-
bio: string;
|
|
62
|
-
location: string | null;
|
|
63
|
-
url: string | null;
|
|
64
|
-
verified_type: string | null;
|
|
65
|
-
followers_count: number;
|
|
66
|
-
following_count: number;
|
|
67
|
-
affiliations_json: string;
|
|
68
|
-
raw_json: string;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export interface ProfileBioEntitiesTable {
|
|
72
|
-
profile_id: string;
|
|
73
|
-
kind: string;
|
|
74
|
-
value: string;
|
|
75
|
-
source: string;
|
|
76
|
-
is_active: number;
|
|
77
|
-
first_seen_at: string;
|
|
78
|
-
last_seen_at: string;
|
|
79
|
-
raw_json: string;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface IdentitySearchIndexTable {
|
|
83
|
-
profile_id: string;
|
|
84
|
-
kind: string;
|
|
85
|
-
value: string;
|
|
86
|
-
normalized_value: string;
|
|
87
|
-
source: string;
|
|
88
|
-
weight: number;
|
|
89
|
-
updated_at: string;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export interface TweetsTable {
|
|
93
|
-
id: string;
|
|
94
|
-
account_id: string;
|
|
95
|
-
author_profile_id: string;
|
|
96
|
-
kind: string;
|
|
97
|
-
text: string;
|
|
98
|
-
created_at: string;
|
|
99
|
-
is_replied: number;
|
|
100
|
-
reply_to_id: string | null;
|
|
101
|
-
like_count: number;
|
|
102
|
-
media_count: number;
|
|
103
|
-
bookmarked: number;
|
|
104
|
-
liked: number;
|
|
105
|
-
entities_json: string;
|
|
106
|
-
media_json: string;
|
|
107
|
-
quoted_tweet_id: string | null;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export interface TweetCollectionsTable {
|
|
111
|
-
account_id: string;
|
|
112
|
-
tweet_id: string;
|
|
113
|
-
kind: "likes" | "bookmarks";
|
|
114
|
-
collected_at: string | null;
|
|
115
|
-
source: string;
|
|
116
|
-
raw_json: string;
|
|
117
|
-
updated_at: string;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export interface TweetAccountEdgesTable {
|
|
121
|
-
account_id: string;
|
|
122
|
-
tweet_id: string;
|
|
123
|
-
kind:
|
|
124
|
-
| "home"
|
|
125
|
-
| "mention"
|
|
126
|
-
| "authored"
|
|
127
|
-
| "search"
|
|
128
|
-
| "profile"
|
|
129
|
-
| "thread_context";
|
|
130
|
-
first_seen_at: string;
|
|
131
|
-
last_seen_at: string;
|
|
132
|
-
seen_count: number;
|
|
133
|
-
source: string;
|
|
134
|
-
raw_json: string;
|
|
135
|
-
updated_at: string;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export interface DmConversationsTable {
|
|
139
|
-
id: string;
|
|
140
|
-
account_id: string;
|
|
141
|
-
participant_profile_id: string;
|
|
142
|
-
title: string;
|
|
143
|
-
inbox_kind: "accepted" | "request";
|
|
144
|
-
last_message_at: string;
|
|
145
|
-
unread_count: number;
|
|
146
|
-
needs_reply: number;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export interface DmMessagesTable {
|
|
150
|
-
id: string;
|
|
151
|
-
conversation_id: string;
|
|
152
|
-
sender_profile_id: string;
|
|
153
|
-
text: string;
|
|
154
|
-
created_at: string;
|
|
155
|
-
direction: "inbound" | "outbound";
|
|
156
|
-
is_replied: number;
|
|
157
|
-
media_count: number;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export interface TweetActionsTable {
|
|
161
|
-
id: string;
|
|
162
|
-
account_id: string;
|
|
163
|
-
tweet_id: string | null;
|
|
164
|
-
kind: string;
|
|
165
|
-
body: string;
|
|
166
|
-
created_at: string;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export interface BlocksTable {
|
|
170
|
-
account_id: string;
|
|
171
|
-
profile_id: string;
|
|
172
|
-
source: string;
|
|
173
|
-
created_at: string;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export interface MutesTable {
|
|
177
|
-
account_id: string;
|
|
178
|
-
profile_id: string;
|
|
179
|
-
source: string;
|
|
180
|
-
created_at: string;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export interface AiScoresTable {
|
|
184
|
-
entity_kind: string;
|
|
185
|
-
entity_id: string;
|
|
186
|
-
model: string;
|
|
187
|
-
score: number;
|
|
188
|
-
summary: string;
|
|
189
|
-
reasoning: string;
|
|
190
|
-
updated_at: string;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export interface SyncCacheTable {
|
|
194
|
-
cache_key: string;
|
|
195
|
-
value_json: string;
|
|
196
|
-
updated_at: string;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export interface UrlExpansionsTable {
|
|
200
|
-
short_url: string;
|
|
201
|
-
expanded_url: string;
|
|
202
|
-
final_url: string;
|
|
203
|
-
status: string;
|
|
204
|
-
expanded_tweet_id: string | null;
|
|
205
|
-
expanded_handle: string | null;
|
|
206
|
-
title: string | null;
|
|
207
|
-
description: string | null;
|
|
208
|
-
image_url: string | null;
|
|
209
|
-
site_name: string | null;
|
|
210
|
-
error: string | null;
|
|
211
|
-
source: string;
|
|
212
|
-
updated_at: string;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export interface LinkOccurrencesTable {
|
|
216
|
-
source_kind: "dm" | "tweet";
|
|
217
|
-
source_id: string;
|
|
218
|
-
source_position: number;
|
|
219
|
-
short_url: string;
|
|
220
|
-
account_id: string | null;
|
|
221
|
-
conversation_id: string | null;
|
|
222
|
-
direction: string | null;
|
|
223
|
-
created_at: string;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export interface FollowEdgesTable {
|
|
227
|
-
account_id: string;
|
|
228
|
-
direction: "followers" | "following";
|
|
229
|
-
profile_id: string;
|
|
230
|
-
external_user_id: string;
|
|
231
|
-
source: string;
|
|
232
|
-
current: number;
|
|
233
|
-
first_seen_at: string;
|
|
234
|
-
last_seen_at: string;
|
|
235
|
-
ended_at: string | null;
|
|
236
|
-
updated_at: string;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export interface FollowSnapshotsTable {
|
|
240
|
-
id: string;
|
|
241
|
-
account_id: string;
|
|
242
|
-
direction: "followers" | "following";
|
|
243
|
-
source: string;
|
|
244
|
-
status: "complete" | "incomplete";
|
|
245
|
-
page_count: number;
|
|
246
|
-
result_count: number;
|
|
247
|
-
started_at: string;
|
|
248
|
-
completed_at: string;
|
|
249
|
-
raw_meta_json: string;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export interface FollowSnapshotMembersTable {
|
|
253
|
-
snapshot_id: string;
|
|
254
|
-
profile_id: string;
|
|
255
|
-
external_user_id: string;
|
|
256
|
-
position: number;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export interface FollowEventsTable {
|
|
260
|
-
id: string;
|
|
261
|
-
account_id: string;
|
|
262
|
-
direction: "followers" | "following";
|
|
263
|
-
profile_id: string;
|
|
264
|
-
external_user_id: string;
|
|
265
|
-
kind: "started" | "ended";
|
|
266
|
-
event_at: string;
|
|
267
|
-
snapshot_id: string;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export interface GeocodedLocationsTable {
|
|
271
|
-
normalized_key: string;
|
|
272
|
-
original: string;
|
|
273
|
-
lat: number;
|
|
274
|
-
lng: number;
|
|
275
|
-
formatted: string | null;
|
|
276
|
-
country_code: string | null;
|
|
277
|
-
confidence: number | null;
|
|
278
|
-
provider: string;
|
|
279
|
-
approx_radius_m: number | null;
|
|
280
|
-
bounds_json: string;
|
|
281
|
-
components_json: string;
|
|
282
|
-
hits: number;
|
|
283
|
-
created_at: string;
|
|
284
|
-
last_used_at: string;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
export interface GeocodedLocationsUnresolvedTable {
|
|
288
|
-
normalized_key: string;
|
|
289
|
-
original: string;
|
|
290
|
-
reason: string;
|
|
291
|
-
last_attempted_at: string;
|
|
292
|
-
ttl_until: string | null;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
export interface BirdclawDatabase {
|
|
296
|
-
accounts: AccountsTable;
|
|
297
|
-
profiles: ProfilesTable;
|
|
298
|
-
profile_affiliations: ProfileAffiliationsTable;
|
|
299
|
-
profile_snapshots: ProfileSnapshotsTable;
|
|
300
|
-
profile_bio_entities: ProfileBioEntitiesTable;
|
|
301
|
-
identity_search_index: IdentitySearchIndexTable;
|
|
302
|
-
tweets: TweetsTable;
|
|
303
|
-
tweet_collections: TweetCollectionsTable;
|
|
304
|
-
tweet_account_edges: TweetAccountEdgesTable;
|
|
305
|
-
dm_conversations: DmConversationsTable;
|
|
306
|
-
dm_messages: DmMessagesTable;
|
|
307
|
-
tweet_actions: TweetActionsTable;
|
|
308
|
-
blocks: BlocksTable;
|
|
309
|
-
mutes: MutesTable;
|
|
310
|
-
ai_scores: AiScoresTable;
|
|
311
|
-
sync_cache: SyncCacheTable;
|
|
312
|
-
url_expansions: UrlExpansionsTable;
|
|
313
|
-
link_occurrences: LinkOccurrencesTable;
|
|
314
|
-
follow_edges: FollowEdgesTable;
|
|
315
|
-
follow_snapshots: FollowSnapshotsTable;
|
|
316
|
-
follow_snapshot_members: FollowSnapshotMembersTable;
|
|
317
|
-
follow_events: FollowEventsTable;
|
|
318
|
-
geocoded_locations: GeocodedLocationsTable;
|
|
319
|
-
geocoded_locations_unresolved: GeocodedLocationsUnresolvedTable;
|
|
320
|
-
}
|
|
17
|
+
import type { BirdclawDatabase } from "./database-schema";
|
|
18
|
+
export * from "./database-schema";
|
|
321
19
|
|
|
322
20
|
let nativeDb: Database | undefined;
|
|
21
|
+
let readDbs: Database[] = [];
|
|
22
|
+
let readDbIndex = 0;
|
|
323
23
|
let kyselyDb: Kysely<BirdclawDatabase> | undefined;
|
|
324
24
|
let demoSeedAttempted = false;
|
|
325
25
|
|
|
@@ -1047,6 +747,31 @@ function ensureSchemaIndexes(db: Database) {
|
|
|
1047
747
|
db.exec(INDEX_SQL);
|
|
1048
748
|
}
|
|
1049
749
|
|
|
750
|
+
const DATABASE_MIGRATIONS: readonly DatabaseMigration[] = [
|
|
751
|
+
{
|
|
752
|
+
version: 1,
|
|
753
|
+
name: "canonical schema baseline",
|
|
754
|
+
up: (db) => {
|
|
755
|
+
db.exec(BASE_SCHEMA_SQL);
|
|
756
|
+
ensureAccountExternalUserIdColumn(db);
|
|
757
|
+
ensureDmConversationInboxColumns(db);
|
|
758
|
+
ensureTweetMetadataColumns(db);
|
|
759
|
+
ensureProfileAvatarColumns(db);
|
|
760
|
+
ensureTweetCollectionsTable(db);
|
|
761
|
+
ensureTweetAccountEdgesTable(db);
|
|
762
|
+
ensureProfileAffiliationsTable(db);
|
|
763
|
+
ensureProfileSnapshotsTable(db);
|
|
764
|
+
ensureProfileBioEntitiesTable(db);
|
|
765
|
+
ensureIdentitySearchIndexTable(db);
|
|
766
|
+
ensureLinkIndexTables(db);
|
|
767
|
+
ensureFollowGraphTables(db);
|
|
768
|
+
ensureSchemaIndexes(db);
|
|
769
|
+
backfillTweetCollections(db);
|
|
770
|
+
backfillTweetAccountEdges(db);
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
];
|
|
774
|
+
|
|
1050
775
|
function ensureDemoData(db: Database) {
|
|
1051
776
|
if (demoSeedAttempted) {
|
|
1052
777
|
return;
|
|
@@ -1063,26 +788,14 @@ function initDatabase(options: InitDatabaseOptions = {}) {
|
|
|
1063
788
|
|
|
1064
789
|
if (!nativeDb) {
|
|
1065
790
|
const { dbPath } = getBirdclawPaths();
|
|
1066
|
-
nativeDb =
|
|
1067
|
-
nativeDb.exec(
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
ensureTweetCollectionsTable(nativeDb);
|
|
1073
|
-
ensureTweetAccountEdgesTable(nativeDb);
|
|
1074
|
-
ensureProfileAffiliationsTable(nativeDb);
|
|
1075
|
-
ensureProfileSnapshotsTable(nativeDb);
|
|
1076
|
-
ensureProfileBioEntitiesTable(nativeDb);
|
|
1077
|
-
ensureIdentitySearchIndexTable(nativeDb);
|
|
1078
|
-
ensureLinkIndexTables(nativeDb);
|
|
1079
|
-
ensureFollowGraphTables(nativeDb);
|
|
1080
|
-
ensureSchemaIndexes(nativeDb);
|
|
791
|
+
nativeDb = createDatabaseConnection(dbPath, "writer");
|
|
792
|
+
nativeDb.exec(`
|
|
793
|
+
pragma busy_timeout = ${SQLITE_BUSY_TIMEOUT_MS};
|
|
794
|
+
pragma foreign_keys = on;
|
|
795
|
+
`);
|
|
796
|
+
runDatabaseMigrations(nativeDb, DATABASE_MIGRATIONS);
|
|
1081
797
|
if (options.seedDemoData !== false) {
|
|
1082
798
|
ensureDemoData(nativeDb);
|
|
1083
|
-
} else {
|
|
1084
|
-
backfillTweetCollections(nativeDb);
|
|
1085
|
-
backfillTweetAccountEdges(nativeDb);
|
|
1086
799
|
}
|
|
1087
800
|
} else if (options.seedDemoData !== false) {
|
|
1088
801
|
ensureDemoData(nativeDb);
|
|
@@ -1097,11 +810,44 @@ function initDatabase(options: InitDatabaseOptions = {}) {
|
|
|
1097
810
|
}
|
|
1098
811
|
}
|
|
1099
812
|
|
|
813
|
+
function createDatabaseConnection(
|
|
814
|
+
dbPath: string,
|
|
815
|
+
role: DatabaseConnectionRole,
|
|
816
|
+
options: { readonly?: boolean } = {},
|
|
817
|
+
) {
|
|
818
|
+
return new NativeSqliteDatabase(dbPath, {
|
|
819
|
+
...options,
|
|
820
|
+
onStatement: (sql, durationMs) =>
|
|
821
|
+
recordDatabaseStatement(role, sql, durationMs),
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
1100
825
|
export function getNativeDb(options: InitDatabaseOptions = {}) {
|
|
1101
826
|
initDatabase(options);
|
|
1102
827
|
return nativeDb as Database;
|
|
1103
828
|
}
|
|
1104
829
|
|
|
830
|
+
export function getReadDb(options: InitDatabaseOptions = {}) {
|
|
831
|
+
initDatabase(options);
|
|
832
|
+
if (readDbs.length === 0) {
|
|
833
|
+
const { dbPath } = getBirdclawPaths();
|
|
834
|
+
readDbs = Array.from({ length: 2 }, () => {
|
|
835
|
+
const db = createDatabaseConnection(dbPath, "reader", {
|
|
836
|
+
readonly: true,
|
|
837
|
+
});
|
|
838
|
+
db.exec(`
|
|
839
|
+
pragma busy_timeout = ${SQLITE_BUSY_TIMEOUT_MS};
|
|
840
|
+
pragma foreign_keys = on;
|
|
841
|
+
pragma query_only = on;
|
|
842
|
+
`);
|
|
843
|
+
return db;
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
const db = readDbs[readDbIndex % readDbs.length] as Database;
|
|
847
|
+
readDbIndex = (readDbIndex + 1) % readDbs.length;
|
|
848
|
+
return db;
|
|
849
|
+
}
|
|
850
|
+
|
|
1105
851
|
export function getDb() {
|
|
1106
852
|
initDatabase();
|
|
1107
853
|
return kyselyDb as Kysely<BirdclawDatabase>;
|
|
@@ -1110,10 +856,14 @@ export function getDb() {
|
|
|
1110
856
|
export async function closeDatabase() {
|
|
1111
857
|
const db = kyselyDb;
|
|
1112
858
|
const native = nativeDb;
|
|
859
|
+
const readers = readDbs;
|
|
1113
860
|
kyselyDb = undefined;
|
|
1114
861
|
nativeDb = undefined;
|
|
862
|
+
readDbs = [];
|
|
863
|
+
readDbIndex = 0;
|
|
1115
864
|
demoSeedAttempted = false;
|
|
1116
865
|
|
|
866
|
+
for (const reader of readers) reader.close();
|
|
1117
867
|
if (db) {
|
|
1118
868
|
await db.destroy();
|
|
1119
869
|
} else {
|
|
@@ -1124,9 +874,13 @@ export async function closeDatabase() {
|
|
|
1124
874
|
export function resetDatabaseForTests() {
|
|
1125
875
|
const db = kyselyDb;
|
|
1126
876
|
const native = nativeDb;
|
|
877
|
+
const readers = readDbs;
|
|
1127
878
|
kyselyDb = undefined;
|
|
1128
879
|
nativeDb = undefined;
|
|
880
|
+
readDbs = [];
|
|
881
|
+
readDbIndex = 0;
|
|
1129
882
|
demoSeedAttempted = false;
|
|
883
|
+
for (const reader of readers) reader.close();
|
|
1130
884
|
if (db) {
|
|
1131
885
|
void db.destroy();
|
|
1132
886
|
} else {
|