@fedify/botkit-sqlite 0.5.0-dev.210 → 0.5.0-dev.225

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/dist/mod.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Temporal, toTemporalInstant } from "@js-temporal/polyfill";
2
2
  Date.prototype.toTemporalInstant = toTemporalInstant;
3
+ import { ActorScopedRepository, Repository, RepositoryGetFollowersOptions, RepositoryGetMessagesOptions, Uuid } from "@fedify/botkit/repository";
3
4
  import { Actor, Announce, Create, Follow } from "@fedify/vocab";
4
- import { Repository, RepositoryGetFollowersOptions, RepositoryGetMessagesOptions, Uuid } from "@fedify/botkit/repository";
5
5
 
6
6
  //#region src/mod.d.ts
7
7
  /**
@@ -36,29 +36,54 @@ declare class SqliteRepository implements Repository, Disposable {
36
36
  * Closes the database connection.
37
37
  */
38
38
  close(): void;
39
+ private tableExists;
40
+ private hasBotIdColumn;
41
+ /**
42
+ * Rebuilds tables created by \@fedify/botkit-sqlite 0.4 or earlier, which
43
+ * had no `bot_id` column, into the bot-scoped schema. Existing rows get
44
+ * the empty-string bot ID; use {@link SqliteRepository.migrate} to assign
45
+ * them to a bot actor identifier.
46
+ */
47
+ private rebuildLegacyTables;
48
+ /**
49
+ * Migrates data stored by \@fedify/botkit-sqlite 0.4 or earlier, which was
50
+ * not scoped by bot actor identifiers, so that it belongs to the given
51
+ * identifier. Rows carried over from a legacy database have the
52
+ * empty-string bot ID; this method assigns them to the identifier in
53
+ * a single transaction. It only acts when the database was actually
54
+ * rebuilt from a legacy schema, so data legitimately stored under an
55
+ * empty-string identifier is never touched, and calling it again is
56
+ * a no-op.
57
+ * @param identifier The identifier of the bot actor that adopts the legacy
58
+ * data.
59
+ * @since 0.5.0
60
+ */
61
+ migrate(identifier: string): Promise<void>;
39
62
  private initializeTables;
40
- setKeyPairs(keyPairs: CryptoKeyPair[]): Promise<void>;
41
- getKeyPairs(): Promise<CryptoKeyPair[] | undefined>;
42
- addMessage(id: Uuid, activity: Create | Announce): Promise<void>;
43
- updateMessage(id: Uuid, updater: (existing: Create | Announce) => Create | Announce | undefined | Promise<Create | Announce | undefined>): Promise<boolean>;
44
- removeMessage(id: Uuid): Promise<Create | Announce | undefined>;
45
- getMessages(options?: RepositoryGetMessagesOptions): AsyncIterable<Create | Announce>;
46
- getMessage(id: Uuid): Promise<Create | Announce | undefined>;
47
- countMessages(): Promise<number>;
48
- addFollower(followRequestId: URL, follower: Actor): Promise<void>;
49
- removeFollower(followRequestId: URL, actorId: URL): Promise<Actor | undefined>;
50
- hasFollower(followerId: URL): Promise<boolean>;
51
- getFollowers(options?: RepositoryGetFollowersOptions): AsyncIterable<Actor>;
52
- countFollowers(): Promise<number>;
53
- addSentFollow(id: Uuid, follow: Follow): Promise<void>;
54
- removeSentFollow(id: Uuid): Promise<Follow | undefined>;
55
- getSentFollow(id: Uuid): Promise<Follow | undefined>;
56
- addFollowee(followeeId: URL, follow: Follow): Promise<void>;
57
- removeFollowee(followeeId: URL): Promise<Follow | undefined>;
58
- getFollowee(followeeId: URL): Promise<Follow | undefined>;
59
- vote(messageId: Uuid, voterId: URL, option: string): Promise<void>;
60
- countVoters(messageId: Uuid): Promise<number>;
61
- countVotes(messageId: Uuid): Promise<Readonly<Record<string, number>>>;
63
+ setKeyPairs(identifier: string, keyPairs: CryptoKeyPair[]): Promise<void>;
64
+ getKeyPairs(identifier: string): Promise<CryptoKeyPair[] | undefined>;
65
+ addMessage(identifier: string, id: Uuid, activity: Create | Announce): Promise<void>;
66
+ updateMessage(identifier: string, id: Uuid, updater: (existing: Create | Announce) => Create | Announce | undefined | Promise<Create | Announce | undefined>): Promise<boolean>;
67
+ removeMessage(identifier: string, id: Uuid): Promise<Create | Announce | undefined>;
68
+ getMessages(identifier: string, options?: RepositoryGetMessagesOptions): AsyncIterable<Create | Announce>;
69
+ getMessage(identifier: string, id: Uuid): Promise<Create | Announce | undefined>;
70
+ countMessages(identifier: string): Promise<number>;
71
+ addFollower(identifier: string, followRequestId: URL, follower: Actor): Promise<void>;
72
+ removeFollower(identifier: string, followRequestId: URL, actorId: URL): Promise<Actor | undefined>;
73
+ hasFollower(identifier: string, followerId: URL): Promise<boolean>;
74
+ getFollowers(identifier: string, options?: RepositoryGetFollowersOptions): AsyncIterable<Actor>;
75
+ countFollowers(identifier: string): Promise<number>;
76
+ addSentFollow(identifier: string, id: Uuid, follow: Follow): Promise<void>;
77
+ removeSentFollow(identifier: string, id: Uuid): Promise<Follow | undefined>;
78
+ getSentFollow(identifier: string, id: Uuid): Promise<Follow | undefined>;
79
+ addFollowee(identifier: string, followeeId: URL, follow: Follow): Promise<void>;
80
+ removeFollowee(identifier: string, followeeId: URL): Promise<Follow | undefined>;
81
+ getFollowee(identifier: string, followeeId: URL): Promise<Follow | undefined>;
82
+ findFollowedBots(followeeId: URL): AsyncIterable<string>;
83
+ vote(identifier: string, messageId: Uuid, voterId: URL, option: string): Promise<void>;
84
+ countVoters(identifier: string, messageId: Uuid): Promise<number>;
85
+ countVotes(identifier: string, messageId: Uuid): Promise<Readonly<Record<string, number>>>;
86
+ forIdentifier(identifier: string): ActorScopedRepository;
62
87
  }
63
88
  //# sourceMappingURL=mod.d.ts.map
64
89
 
package/dist/mod.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"mod.d.ts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/mod.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { Temporal, toTemporalInstant } from "@js-temporal/polyfill";
3
3
  Date.prototype.toTemporalInstant = toTemporalInstant;
4
4
 
5
+ import { ActorScopedRepository } from "@fedify/botkit/repository";
5
6
  import { exportJwk, importJwk } from "@fedify/fedify/sig";
6
7
  import { Activity, Announce, Create, Follow, Object as Object$1, isActor } from "@fedify/vocab";
7
8
  import { getLogger } from "@logtape/logtape";
@@ -35,75 +36,294 @@ var SqliteRepository = class {
35
36
  close() {
36
37
  this.db.close();
37
38
  }
39
+ tableExists(table) {
40
+ const stmt = this.db.prepare(`
41
+ SELECT COUNT(*) AS count FROM sqlite_master
42
+ WHERE type = 'table' AND name = ?
43
+ `);
44
+ const row = stmt.get(table);
45
+ return row.count > 0;
46
+ }
47
+ hasBotIdColumn(table) {
48
+ const stmt = this.db.prepare(`
49
+ SELECT COUNT(*) AS count FROM pragma_table_info(?)
50
+ WHERE name = 'bot_id'
51
+ `);
52
+ const row = stmt.get(table);
53
+ return row.count > 0;
54
+ }
55
+ /**
56
+ * Rebuilds tables created by \@fedify/botkit-sqlite 0.4 or earlier, which
57
+ * had no `bot_id` column, into the bot-scoped schema. Existing rows get
58
+ * the empty-string bot ID; use {@link SqliteRepository.migrate} to assign
59
+ * them to a bot actor identifier.
60
+ */
61
+ rebuildLegacyTables() {
62
+ const tables = [
63
+ "key_pairs",
64
+ "messages",
65
+ "followers",
66
+ "follow_requests",
67
+ "sent_follows",
68
+ "followees",
69
+ "poll_votes"
70
+ ].filter((table) => this.tableExists(table) && !this.hasBotIdColumn(table));
71
+ if (tables.length < 1) return;
72
+ logger.info("Rebuilding legacy tables without a bot_id column: {tables}.", { tables });
73
+ this.db.exec(`
74
+ CREATE TABLE IF NOT EXISTS botkit_metadata (
75
+ key TEXT PRIMARY KEY,
76
+ value TEXT NOT NULL
77
+ )
78
+ `);
79
+ this.db.exec("PRAGMA foreign_keys = OFF;");
80
+ this.db.exec("BEGIN TRANSACTION");
81
+ try {
82
+ if (tables.includes("key_pairs")) this.db.exec(`
83
+ ALTER TABLE key_pairs ADD COLUMN bot_id TEXT NOT NULL DEFAULT ''
84
+ `);
85
+ if (tables.includes("messages")) {
86
+ this.db.exec(`
87
+ CREATE TABLE messages_new (
88
+ bot_id TEXT NOT NULL,
89
+ id TEXT NOT NULL,
90
+ activity_json TEXT NOT NULL,
91
+ published INTEGER,
92
+ PRIMARY KEY (bot_id, id)
93
+ )
94
+ `);
95
+ this.db.exec(`
96
+ INSERT INTO messages_new (bot_id, id, activity_json, published)
97
+ SELECT '', id, activity_json, published FROM messages
98
+ `);
99
+ this.db.exec("DROP TABLE messages");
100
+ this.db.exec("ALTER TABLE messages_new RENAME TO messages");
101
+ }
102
+ if (tables.includes("followers")) {
103
+ this.db.exec(`
104
+ CREATE TABLE followers_new (
105
+ bot_id TEXT NOT NULL,
106
+ follower_id TEXT NOT NULL,
107
+ actor_json TEXT NOT NULL,
108
+ PRIMARY KEY (bot_id, follower_id)
109
+ )
110
+ `);
111
+ this.db.exec(`
112
+ INSERT INTO followers_new (bot_id, follower_id, actor_json)
113
+ SELECT '', follower_id, actor_json FROM followers
114
+ `);
115
+ this.db.exec("DROP TABLE followers");
116
+ this.db.exec("ALTER TABLE followers_new RENAME TO followers");
117
+ }
118
+ if (tables.includes("follow_requests")) {
119
+ this.db.exec(`
120
+ CREATE TABLE follow_requests_new (
121
+ bot_id TEXT NOT NULL,
122
+ follow_request_id TEXT NOT NULL,
123
+ follower_id TEXT NOT NULL,
124
+ PRIMARY KEY (bot_id, follow_request_id),
125
+ FOREIGN KEY (bot_id, follower_id)
126
+ REFERENCES followers(bot_id, follower_id)
127
+ )
128
+ `);
129
+ this.db.exec(`
130
+ INSERT INTO follow_requests_new
131
+ (bot_id, follow_request_id, follower_id)
132
+ SELECT '', follow_request_id, follower_id FROM follow_requests
133
+ `);
134
+ this.db.exec("DROP TABLE follow_requests");
135
+ this.db.exec("ALTER TABLE follow_requests_new RENAME TO follow_requests");
136
+ }
137
+ if (tables.includes("sent_follows")) {
138
+ this.db.exec(`
139
+ CREATE TABLE sent_follows_new (
140
+ bot_id TEXT NOT NULL,
141
+ id TEXT NOT NULL,
142
+ follow_json TEXT NOT NULL,
143
+ PRIMARY KEY (bot_id, id)
144
+ )
145
+ `);
146
+ this.db.exec(`
147
+ INSERT INTO sent_follows_new (bot_id, id, follow_json)
148
+ SELECT '', id, follow_json FROM sent_follows
149
+ `);
150
+ this.db.exec("DROP TABLE sent_follows");
151
+ this.db.exec("ALTER TABLE sent_follows_new RENAME TO sent_follows");
152
+ }
153
+ if (tables.includes("followees")) {
154
+ this.db.exec(`
155
+ CREATE TABLE followees_new (
156
+ bot_id TEXT NOT NULL,
157
+ followee_id TEXT NOT NULL,
158
+ follow_json TEXT NOT NULL,
159
+ PRIMARY KEY (bot_id, followee_id)
160
+ )
161
+ `);
162
+ this.db.exec(`
163
+ INSERT INTO followees_new (bot_id, followee_id, follow_json)
164
+ SELECT '', followee_id, follow_json FROM followees
165
+ `);
166
+ this.db.exec("DROP TABLE followees");
167
+ this.db.exec("ALTER TABLE followees_new RENAME TO followees");
168
+ }
169
+ if (tables.includes("poll_votes")) {
170
+ this.db.exec(`
171
+ CREATE TABLE poll_votes_new (
172
+ bot_id TEXT NOT NULL,
173
+ message_id TEXT NOT NULL,
174
+ voter_id TEXT NOT NULL,
175
+ option TEXT NOT NULL,
176
+ PRIMARY KEY (bot_id, message_id, voter_id, option)
177
+ )
178
+ `);
179
+ this.db.exec(`
180
+ INSERT INTO poll_votes_new (bot_id, message_id, voter_id, option)
181
+ SELECT '', message_id, voter_id, option FROM poll_votes
182
+ `);
183
+ this.db.exec("DROP TABLE poll_votes");
184
+ this.db.exec("ALTER TABLE poll_votes_new RENAME TO poll_votes");
185
+ }
186
+ this.db.exec(`
187
+ INSERT OR REPLACE INTO botkit_metadata (key, value)
188
+ VALUES ('legacy_data', '1')
189
+ `);
190
+ this.db.exec("COMMIT");
191
+ } catch (error) {
192
+ this.db.exec("ROLLBACK");
193
+ this.db.exec("PRAGMA foreign_keys = ON;");
194
+ throw error;
195
+ }
196
+ this.db.exec("PRAGMA foreign_keys = ON;");
197
+ logger.info("Finished rebuilding legacy tables.");
198
+ }
199
+ /**
200
+ * Migrates data stored by \@fedify/botkit-sqlite 0.4 or earlier, which was
201
+ * not scoped by bot actor identifiers, so that it belongs to the given
202
+ * identifier. Rows carried over from a legacy database have the
203
+ * empty-string bot ID; this method assigns them to the identifier in
204
+ * a single transaction. It only acts when the database was actually
205
+ * rebuilt from a legacy schema, so data legitimately stored under an
206
+ * empty-string identifier is never touched, and calling it again is
207
+ * a no-op.
208
+ * @param identifier The identifier of the bot actor that adopts the legacy
209
+ * data.
210
+ * @since 0.5.0
211
+ */
212
+ migrate(identifier) {
213
+ if (!this.tableExists("botkit_metadata")) return Promise.resolve();
214
+ const marker = this.db.prepare("SELECT value FROM botkit_metadata WHERE key = 'legacy_data'").get();
215
+ if (marker == null) return Promise.resolve();
216
+ this.db.exec("BEGIN TRANSACTION");
217
+ this.db.exec("PRAGMA defer_foreign_keys = ON");
218
+ try {
219
+ for (const table of [
220
+ "key_pairs",
221
+ "messages",
222
+ "followers",
223
+ "follow_requests",
224
+ "sent_follows",
225
+ "followees",
226
+ "poll_votes"
227
+ ]) this.db.prepare(`UPDATE ${table} SET bot_id = ? WHERE bot_id = ''`).run(identifier);
228
+ this.db.exec("DELETE FROM botkit_metadata WHERE key = 'legacy_data'");
229
+ this.db.exec("COMMIT");
230
+ } catch (error) {
231
+ this.db.exec("ROLLBACK");
232
+ throw error;
233
+ }
234
+ return Promise.resolve();
235
+ }
38
236
  initializeTables() {
237
+ this.rebuildLegacyTables();
39
238
  this.db.exec(`
40
239
  CREATE TABLE IF NOT EXISTS key_pairs (
41
240
  id INTEGER PRIMARY KEY,
241
+ bot_id TEXT NOT NULL,
42
242
  private_key_jwk TEXT NOT NULL,
43
243
  public_key_jwk TEXT NOT NULL
44
244
  )
245
+ `);
246
+ this.db.exec(`
247
+ CREATE INDEX IF NOT EXISTS idx_key_pairs_bot_id ON key_pairs(bot_id)
45
248
  `);
46
249
  this.db.exec(`
47
250
  CREATE TABLE IF NOT EXISTS messages (
48
- id TEXT PRIMARY KEY,
251
+ bot_id TEXT NOT NULL,
252
+ id TEXT NOT NULL,
49
253
  activity_json TEXT NOT NULL,
50
- published INTEGER
254
+ published INTEGER,
255
+ PRIMARY KEY (bot_id, id)
51
256
  )
52
257
  `);
53
258
  this.db.exec(`
54
- CREATE INDEX IF NOT EXISTS idx_messages_published ON messages(published)
259
+ CREATE INDEX IF NOT EXISTS idx_messages_bot_published
260
+ ON messages(bot_id, published)
55
261
  `);
56
262
  this.db.exec(`
57
263
  CREATE TABLE IF NOT EXISTS followers (
58
- follower_id TEXT PRIMARY KEY,
59
- actor_json TEXT NOT NULL
264
+ bot_id TEXT NOT NULL,
265
+ follower_id TEXT NOT NULL,
266
+ actor_json TEXT NOT NULL,
267
+ PRIMARY KEY (bot_id, follower_id)
60
268
  )
61
269
  `);
62
270
  this.db.exec(`
63
271
  CREATE TABLE IF NOT EXISTS follow_requests (
64
- follow_request_id TEXT PRIMARY KEY,
272
+ bot_id TEXT NOT NULL,
273
+ follow_request_id TEXT NOT NULL,
65
274
  follower_id TEXT NOT NULL,
66
- FOREIGN KEY (follower_id) REFERENCES followers(follower_id)
275
+ PRIMARY KEY (bot_id, follow_request_id),
276
+ FOREIGN KEY (bot_id, follower_id)
277
+ REFERENCES followers(bot_id, follower_id)
67
278
  )
68
279
  `);
69
280
  this.db.exec(`
70
281
  CREATE TABLE IF NOT EXISTS sent_follows (
71
- id TEXT PRIMARY KEY,
72
- follow_json TEXT NOT NULL
282
+ bot_id TEXT NOT NULL,
283
+ id TEXT NOT NULL,
284
+ follow_json TEXT NOT NULL,
285
+ PRIMARY KEY (bot_id, id)
73
286
  )
74
287
  `);
75
288
  this.db.exec(`
76
289
  CREATE TABLE IF NOT EXISTS followees (
77
- followee_id TEXT PRIMARY KEY,
78
- follow_json TEXT NOT NULL
290
+ bot_id TEXT NOT NULL,
291
+ followee_id TEXT NOT NULL,
292
+ follow_json TEXT NOT NULL,
293
+ PRIMARY KEY (bot_id, followee_id)
79
294
  )
295
+ `);
296
+ this.db.exec(`
297
+ CREATE INDEX IF NOT EXISTS idx_followees_followee_id
298
+ ON followees(followee_id)
80
299
  `);
81
300
  this.db.exec(`
82
301
  CREATE TABLE IF NOT EXISTS poll_votes (
302
+ bot_id TEXT NOT NULL,
83
303
  message_id TEXT NOT NULL,
84
304
  voter_id TEXT NOT NULL,
85
305
  option TEXT NOT NULL,
86
- PRIMARY KEY (message_id, voter_id, option)
306
+ PRIMARY KEY (bot_id, message_id, voter_id, option)
87
307
  )
88
308
  `);
89
309
  this.db.exec(`
90
- CREATE INDEX IF NOT EXISTS idx_poll_votes_message_option
91
- ON poll_votes(message_id, option)
310
+ CREATE INDEX IF NOT EXISTS idx_poll_votes_bot_message_option
311
+ ON poll_votes(bot_id, message_id, option)
92
312
  `);
93
313
  }
94
- async setKeyPairs(keyPairs) {
95
- const deleteStmt = this.db.prepare("DELETE FROM key_pairs");
314
+ async setKeyPairs(identifier, keyPairs) {
315
+ const deleteStmt = this.db.prepare("DELETE FROM key_pairs WHERE bot_id = ?");
96
316
  const insertStmt = this.db.prepare(`
97
- INSERT INTO key_pairs (private_key_jwk, public_key_jwk)
98
- VALUES (?, ?)
317
+ INSERT INTO key_pairs (bot_id, private_key_jwk, public_key_jwk)
318
+ VALUES (?, ?, ?)
99
319
  `);
100
320
  this.db.exec("BEGIN TRANSACTION");
101
321
  try {
102
- deleteStmt.run();
322
+ deleteStmt.run(identifier);
103
323
  for (const keyPair of keyPairs) {
104
324
  const privateJwk = await exportJwk(keyPair.privateKey);
105
325
  const publicJwk = await exportJwk(keyPair.publicKey);
106
- insertStmt.run(JSON.stringify(privateJwk), JSON.stringify(publicJwk));
326
+ insertStmt.run(identifier, JSON.stringify(privateJwk), JSON.stringify(publicJwk));
107
327
  }
108
328
  this.db.exec("COMMIT");
109
329
  } catch (error) {
@@ -111,11 +331,12 @@ var SqliteRepository = class {
111
331
  throw error;
112
332
  }
113
333
  }
114
- async getKeyPairs() {
334
+ async getKeyPairs(identifier) {
115
335
  const stmt = this.db.prepare(`
116
336
  SELECT private_key_jwk, public_key_jwk FROM key_pairs
337
+ WHERE bot_id = ? ORDER BY id
117
338
  `);
118
- const rows = stmt.all();
339
+ const rows = stmt.all(identifier);
119
340
  if (rows.length === 0) return void 0;
120
341
  const keyPairs = [];
121
342
  for (const row of rows) {
@@ -128,20 +349,20 @@ var SqliteRepository = class {
128
349
  }
129
350
  return keyPairs;
130
351
  }
131
- async addMessage(id, activity) {
352
+ async addMessage(identifier, id, activity) {
132
353
  const stmt = this.db.prepare(`
133
- INSERT INTO messages (id, activity_json, published)
134
- VALUES (?, ?, ?)
354
+ INSERT INTO messages (bot_id, id, activity_json, published)
355
+ VALUES (?, ?, ?, ?)
135
356
  `);
136
357
  const activityJson = JSON.stringify(await activity.toJsonLd({ format: "compact" }));
137
358
  const published = activity.published?.epochMilliseconds ?? null;
138
- stmt.run(id, activityJson, published);
359
+ stmt.run(identifier, id, activityJson, published);
139
360
  }
140
- async updateMessage(id, updater) {
361
+ async updateMessage(identifier, id, updater) {
141
362
  const selectStmt = this.db.prepare(`
142
- SELECT activity_json FROM messages WHERE id = ?
363
+ SELECT activity_json FROM messages WHERE bot_id = ? AND id = ?
143
364
  `);
144
- const row = selectStmt.get(id);
365
+ const row = selectStmt.get(identifier, id);
145
366
  if (!row) return false;
146
367
  const activityData = JSON.parse(row.activity_json);
147
368
  const activity = await Activity.fromJsonLd(activityData);
@@ -149,25 +370,25 @@ var SqliteRepository = class {
149
370
  const newActivity = await updater(activity);
150
371
  if (newActivity == null) return false;
151
372
  const updateStmt = this.db.prepare(`
152
- UPDATE messages
153
- SET activity_json = ?, published = ?
154
- WHERE id = ?
373
+ UPDATE messages
374
+ SET activity_json = ?, published = ?
375
+ WHERE bot_id = ? AND id = ?
155
376
  `);
156
377
  const newActivityJson = JSON.stringify(await newActivity.toJsonLd({ format: "compact" }));
157
378
  const published = newActivity.published?.epochMilliseconds ?? null;
158
- updateStmt.run(newActivityJson, published, id);
379
+ updateStmt.run(newActivityJson, published, identifier, id);
159
380
  return true;
160
381
  }
161
- async removeMessage(id) {
382
+ async removeMessage(identifier, id) {
162
383
  const selectStmt = this.db.prepare(`
163
- SELECT activity_json FROM messages WHERE id = ?
384
+ SELECT activity_json FROM messages WHERE bot_id = ? AND id = ?
164
385
  `);
165
- const row = selectStmt.get(id);
386
+ const row = selectStmt.get(identifier, id);
166
387
  if (!row) return void 0;
167
388
  const deleteStmt = this.db.prepare(`
168
- DELETE FROM messages WHERE id = ?
389
+ DELETE FROM messages WHERE bot_id = ? AND id = ?
169
390
  `);
170
- deleteStmt.run(id);
391
+ deleteStmt.run(identifier, id);
171
392
  try {
172
393
  const activityData = JSON.parse(row.activity_json);
173
394
  const activity = await Activity.fromJsonLd(activityData);
@@ -180,10 +401,10 @@ var SqliteRepository = class {
180
401
  }
181
402
  return void 0;
182
403
  }
183
- async *getMessages(options = {}) {
404
+ async *getMessages(identifier, options = {}) {
184
405
  const { order = "newest", until, since, limit } = options;
185
- let sql = "SELECT activity_json FROM messages WHERE 1=1";
186
- const params = [];
406
+ let sql = "SELECT activity_json FROM messages WHERE bot_id = ?";
407
+ const params = [identifier];
187
408
  if (since != null) {
188
409
  sql += " AND published >= ?";
189
410
  params.push(since.epochMilliseconds);
@@ -208,11 +429,11 @@ var SqliteRepository = class {
208
429
  continue;
209
430
  }
210
431
  }
211
- async getMessage(id) {
432
+ async getMessage(identifier, id) {
212
433
  const stmt = this.db.prepare(`
213
- SELECT activity_json FROM messages WHERE id = ?
434
+ SELECT activity_json FROM messages WHERE bot_id = ? AND id = ?
214
435
  `);
215
- const row = stmt.get(id);
436
+ const row = stmt.get(identifier, id);
216
437
  if (!row) return void 0;
217
438
  try {
218
439
  const activityData = JSON.parse(row.activity_json);
@@ -226,51 +447,53 @@ var SqliteRepository = class {
226
447
  }
227
448
  return void 0;
228
449
  }
229
- countMessages() {
230
- const stmt = this.db.prepare("SELECT COUNT(*) as count FROM messages");
231
- const row = stmt.get();
450
+ countMessages(identifier) {
451
+ const stmt = this.db.prepare("SELECT COUNT(*) as count FROM messages WHERE bot_id = ?");
452
+ const row = stmt.get(identifier);
232
453
  return Promise.resolve(row.count);
233
454
  }
234
- async addFollower(followRequestId, follower) {
455
+ async addFollower(identifier, followRequestId, follower) {
235
456
  if (follower.id == null) throw new TypeError("The follower ID is missing.");
236
457
  const followerJson = JSON.stringify(await follower.toJsonLd({ format: "compact" }));
237
458
  const insertFollowerStmt = this.db.prepare(`
238
- INSERT OR REPLACE INTO followers (follower_id, actor_json)
239
- VALUES (?, ?)
459
+ INSERT OR REPLACE INTO followers (bot_id, follower_id, actor_json)
460
+ VALUES (?, ?, ?)
240
461
  `);
241
462
  const insertRequestStmt = this.db.prepare(`
242
- INSERT OR REPLACE INTO follow_requests (follow_request_id, follower_id)
243
- VALUES (?, ?)
463
+ INSERT OR REPLACE INTO follow_requests
464
+ (bot_id, follow_request_id, follower_id)
465
+ VALUES (?, ?, ?)
244
466
  `);
245
467
  this.db.exec("BEGIN TRANSACTION");
246
468
  try {
247
- insertFollowerStmt.run(follower.id.href, followerJson);
248
- insertRequestStmt.run(followRequestId.href, follower.id.href);
469
+ insertFollowerStmt.run(identifier, follower.id.href, followerJson);
470
+ insertRequestStmt.run(identifier, followRequestId.href, follower.id.href);
249
471
  this.db.exec("COMMIT");
250
472
  } catch (error) {
251
473
  this.db.exec("ROLLBACK");
252
474
  throw error;
253
475
  }
254
476
  }
255
- async removeFollower(followRequestId, actorId) {
477
+ async removeFollower(identifier, followRequestId, actorId) {
256
478
  const checkStmt = this.db.prepare(`
257
- SELECT fr.follower_id, f.actor_json
258
- FROM follow_requests fr
259
- JOIN followers f ON fr.follower_id = f.follower_id
260
- WHERE fr.follow_request_id = ? AND fr.follower_id = ?
479
+ SELECT fr.follower_id, f.actor_json
480
+ FROM follow_requests fr
481
+ JOIN followers f
482
+ ON fr.bot_id = f.bot_id AND fr.follower_id = f.follower_id
483
+ WHERE fr.bot_id = ? AND fr.follow_request_id = ? AND fr.follower_id = ?
261
484
  `);
262
- const row = checkStmt.get(followRequestId.href, actorId.href);
485
+ const row = checkStmt.get(identifier, followRequestId.href, actorId.href);
263
486
  if (!row) return void 0;
264
487
  const deleteRequestStmt = this.db.prepare(`
265
- DELETE FROM follow_requests WHERE follow_request_id = ?
488
+ DELETE FROM follow_requests WHERE bot_id = ? AND follow_request_id = ?
266
489
  `);
267
490
  const deleteFollowerStmt = this.db.prepare(`
268
- DELETE FROM followers WHERE follower_id = ?
491
+ DELETE FROM followers WHERE bot_id = ? AND follower_id = ?
269
492
  `);
270
493
  this.db.exec("BEGIN TRANSACTION");
271
494
  try {
272
- deleteRequestStmt.run(followRequestId.href);
273
- deleteFollowerStmt.run(actorId.href);
495
+ deleteRequestStmt.run(identifier, followRequestId.href);
496
+ deleteFollowerStmt.run(identifier, actorId.href);
274
497
  this.db.exec("COMMIT");
275
498
  } catch (error) {
276
499
  this.db.exec("ROLLBACK");
@@ -285,17 +508,17 @@ var SqliteRepository = class {
285
508
  }
286
509
  return void 0;
287
510
  }
288
- hasFollower(followerId) {
511
+ hasFollower(identifier, followerId) {
289
512
  const stmt = this.db.prepare(`
290
- SELECT 1 FROM followers WHERE follower_id = ?
513
+ SELECT 1 FROM followers WHERE bot_id = ? AND follower_id = ?
291
514
  `);
292
- const row = stmt.get(followerId.href);
515
+ const row = stmt.get(identifier, followerId.href);
293
516
  return Promise.resolve(row != null);
294
517
  }
295
- async *getFollowers(options = {}) {
518
+ async *getFollowers(identifier, options = {}) {
296
519
  const { offset = 0, limit } = options;
297
- let sql = "SELECT actor_json FROM followers ORDER BY follower_id";
298
- const params = [];
520
+ let sql = "SELECT actor_json FROM followers WHERE bot_id = ? ORDER BY follower_id";
521
+ const params = [identifier];
299
522
  if (limit != null) {
300
523
  sql += " LIMIT ? OFFSET ?";
301
524
  params.push(limit, offset);
@@ -314,31 +537,31 @@ var SqliteRepository = class {
314
537
  continue;
315
538
  }
316
539
  }
317
- countFollowers() {
318
- const stmt = this.db.prepare("SELECT COUNT(*) as count FROM followers");
319
- const row = stmt.get();
540
+ countFollowers(identifier) {
541
+ const stmt = this.db.prepare("SELECT COUNT(*) as count FROM followers WHERE bot_id = ?");
542
+ const row = stmt.get(identifier);
320
543
  return Promise.resolve(row.count);
321
544
  }
322
- async addSentFollow(id, follow) {
545
+ async addSentFollow(identifier, id, follow) {
323
546
  const stmt = this.db.prepare(`
324
- INSERT OR REPLACE INTO sent_follows (id, follow_json)
325
- VALUES (?, ?)
547
+ INSERT OR REPLACE INTO sent_follows (bot_id, id, follow_json)
548
+ VALUES (?, ?, ?)
326
549
  `);
327
550
  const followJson = JSON.stringify(await follow.toJsonLd({ format: "compact" }));
328
- stmt.run(id, followJson);
551
+ stmt.run(identifier, id, followJson);
329
552
  }
330
- async removeSentFollow(id) {
331
- const follow = await this.getSentFollow(id);
553
+ async removeSentFollow(identifier, id) {
554
+ const follow = await this.getSentFollow(identifier, id);
332
555
  if (follow == null) return void 0;
333
- const stmt = this.db.prepare("DELETE FROM sent_follows WHERE id = ?");
334
- stmt.run(id);
556
+ const stmt = this.db.prepare("DELETE FROM sent_follows WHERE bot_id = ? AND id = ?");
557
+ stmt.run(identifier, id);
335
558
  return follow;
336
559
  }
337
- async getSentFollow(id) {
560
+ async getSentFollow(identifier, id) {
338
561
  const stmt = this.db.prepare(`
339
- SELECT follow_json FROM sent_follows WHERE id = ?
562
+ SELECT follow_json FROM sent_follows WHERE bot_id = ? AND id = ?
340
563
  `);
341
- const row = stmt.get(id);
564
+ const row = stmt.get(identifier, id);
342
565
  if (!row) return void 0;
343
566
  try {
344
567
  const followData = JSON.parse(row.follow_json);
@@ -351,26 +574,26 @@ var SqliteRepository = class {
351
574
  return void 0;
352
575
  }
353
576
  }
354
- async addFollowee(followeeId, follow) {
577
+ async addFollowee(identifier, followeeId, follow) {
355
578
  const stmt = this.db.prepare(`
356
- INSERT OR REPLACE INTO followees (followee_id, follow_json)
357
- VALUES (?, ?)
579
+ INSERT OR REPLACE INTO followees (bot_id, followee_id, follow_json)
580
+ VALUES (?, ?, ?)
358
581
  `);
359
582
  const followJson = JSON.stringify(await follow.toJsonLd({ format: "compact" }));
360
- stmt.run(followeeId.href, followJson);
583
+ stmt.run(identifier, followeeId.href, followJson);
361
584
  }
362
- async removeFollowee(followeeId) {
363
- const follow = await this.getFollowee(followeeId);
585
+ async removeFollowee(identifier, followeeId) {
586
+ const follow = await this.getFollowee(identifier, followeeId);
364
587
  if (follow == null) return void 0;
365
- const stmt = this.db.prepare("DELETE FROM followees WHERE followee_id = ?");
366
- stmt.run(followeeId.href);
588
+ const stmt = this.db.prepare("DELETE FROM followees WHERE bot_id = ? AND followee_id = ?");
589
+ stmt.run(identifier, followeeId.href);
367
590
  return follow;
368
591
  }
369
- async getFollowee(followeeId) {
592
+ async getFollowee(identifier, followeeId) {
370
593
  const stmt = this.db.prepare(`
371
- SELECT follow_json FROM followees WHERE followee_id = ?
594
+ SELECT follow_json FROM followees WHERE bot_id = ? AND followee_id = ?
372
595
  `);
373
- const row = stmt.get(followeeId.href);
596
+ const row = stmt.get(identifier, followeeId.href);
374
597
  if (!row) return void 0;
375
598
  try {
376
599
  const followData = JSON.parse(row.follow_json);
@@ -383,35 +606,45 @@ var SqliteRepository = class {
383
606
  return void 0;
384
607
  }
385
608
  }
386
- vote(messageId, voterId, option) {
609
+ async *findFollowedBots(followeeId) {
387
610
  const stmt = this.db.prepare(`
388
- INSERT OR IGNORE INTO poll_votes (message_id, voter_id, option)
389
- VALUES (?, ?, ?)
611
+ SELECT bot_id FROM followees WHERE followee_id = ? ORDER BY bot_id
612
+ `);
613
+ const rows = stmt.all(followeeId.href);
614
+ for (const row of rows) yield row.bot_id;
615
+ }
616
+ vote(identifier, messageId, voterId, option) {
617
+ const stmt = this.db.prepare(`
618
+ INSERT OR IGNORE INTO poll_votes (bot_id, message_id, voter_id, option)
619
+ VALUES (?, ?, ?, ?)
390
620
  `);
391
- stmt.run(messageId, voterId.href, option);
621
+ stmt.run(identifier, messageId, voterId.href, option);
392
622
  return Promise.resolve();
393
623
  }
394
- countVoters(messageId) {
624
+ countVoters(identifier, messageId) {
395
625
  const stmt = this.db.prepare(`
396
- SELECT COUNT(DISTINCT voter_id) as count
397
- FROM poll_votes
398
- WHERE message_id = ?
626
+ SELECT COUNT(DISTINCT voter_id) as count
627
+ FROM poll_votes
628
+ WHERE bot_id = ? AND message_id = ?
399
629
  `);
400
- const row = stmt.get(messageId);
630
+ const row = stmt.get(identifier, messageId);
401
631
  return Promise.resolve(row.count);
402
632
  }
403
- countVotes(messageId) {
633
+ countVotes(identifier, messageId) {
404
634
  const stmt = this.db.prepare(`
405
- SELECT option, COUNT(*) as count
406
- FROM poll_votes
407
- WHERE message_id = ?
635
+ SELECT option, COUNT(*) as count
636
+ FROM poll_votes
637
+ WHERE bot_id = ? AND message_id = ?
408
638
  GROUP BY option
409
639
  `);
410
- const rows = stmt.all(messageId);
640
+ const rows = stmt.all(identifier, messageId);
411
641
  const result = {};
412
642
  for (const row of rows) result[row.option] = row.count;
413
643
  return Promise.resolve(result);
414
644
  }
645
+ forIdentifier(identifier) {
646
+ return new ActorScopedRepository(this, identifier);
647
+ }
415
648
  };
416
649
 
417
650
  //#endregion
package/dist/mod.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mod.js","names":["options: SqliteRepositoryOptions","keyPairs: CryptoKeyPair[]","id: Uuid","activity: Create | Announce","updater: (\n existing: Create | Announce,\n ) => Create | Announce | undefined | Promise<Create | Announce | undefined>","options: RepositoryGetMessagesOptions","params: (number | string)[]","followRequestId: URL","follower: Actor","actorId: URL","followerId: URL","options: RepositoryGetFollowersOptions","params: number[]","follow: Follow","followeeId: URL","messageId: Uuid","voterId: URL","option: string","result: Record<string, number>"],"sources":["../src/mod.ts"],"sourcesContent":["// BotKit by Fedify: A framework for creating ActivityPub bots\n// Copyright (C) 2025–2026 Hong Minhee <https://hongminhee.org/>\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as\n// published by the Free Software Foundation, either version 3 of the\n// License, or (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\nimport type {\n Repository,\n RepositoryGetFollowersOptions,\n RepositoryGetMessagesOptions,\n Uuid,\n} from \"@fedify/botkit/repository\";\nimport { exportJwk, importJwk } from \"@fedify/fedify/sig\";\nimport {\n Activity,\n type Actor,\n Announce,\n Create,\n Follow,\n isActor,\n Object,\n} from \"@fedify/vocab\";\nimport { getLogger } from \"@logtape/logtape\";\nimport { DatabaseSync } from \"node:sqlite\";\n\nconst logger = getLogger([\"botkit\", \"sqlite\"]);\n\n/**\n * Options for creating a SQLite repository.\n * @since 0.3.0\n */\nexport interface SqliteRepositoryOptions {\n /**\n * The path to the SQLite database file.\n * If not provided, an in-memory database will be used.\n */\n readonly path?: string;\n\n /**\n * Whether to enable Write-Ahead Logging (WAL) mode.\n * @default true\n */\n readonly wal?: boolean;\n}\n\n/**\n * A repository for storing bot data using SQLite.\n * @since 0.3.0\n */\nexport class SqliteRepository implements Repository, Disposable {\n private readonly db: DatabaseSync;\n\n /**\n * Creates a new SQLite repository.\n * @param options The options for creating the repository.\n */\n constructor(options: SqliteRepositoryOptions = {}) {\n const { path = \":memory:\", wal = true } = options;\n\n this.db = new DatabaseSync(path);\n\n // Enable foreign key constraints\n this.db.exec(\"PRAGMA foreign_keys = ON;\");\n\n if (wal && path !== \":memory:\") {\n this.db.exec(\"PRAGMA journal_mode = WAL;\");\n }\n\n this.initializeTables();\n }\n\n [Symbol.dispose]() {\n this.close();\n }\n\n /**\n * Closes the database connection.\n */\n close(): void {\n this.db.close();\n }\n\n private initializeTables(): void {\n // Key pairs table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS key_pairs (\n id INTEGER PRIMARY KEY,\n private_key_jwk TEXT NOT NULL,\n public_key_jwk TEXT NOT NULL\n )\n `);\n\n // Messages table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS messages (\n id TEXT PRIMARY KEY,\n activity_json TEXT NOT NULL,\n published INTEGER\n )\n `);\n\n // Create index on published timestamp for efficient ordering\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_messages_published ON messages(published)\n `);\n\n // Followers table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS followers (\n follower_id TEXT PRIMARY KEY,\n actor_json TEXT NOT NULL\n )\n `);\n\n // Follow requests mapping table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS follow_requests (\n follow_request_id TEXT PRIMARY KEY,\n follower_id TEXT NOT NULL,\n FOREIGN KEY (follower_id) REFERENCES followers(follower_id)\n )\n `);\n\n // Sent follows table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS sent_follows (\n id TEXT PRIMARY KEY,\n follow_json TEXT NOT NULL\n )\n `);\n\n // Followees table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS followees (\n followee_id TEXT PRIMARY KEY,\n follow_json TEXT NOT NULL\n )\n `);\n\n // Poll votes table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS poll_votes (\n message_id TEXT NOT NULL,\n voter_id TEXT NOT NULL,\n option TEXT NOT NULL,\n PRIMARY KEY (message_id, voter_id, option)\n )\n `);\n\n // Create index for efficient vote counting\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_poll_votes_message_option \n ON poll_votes(message_id, option)\n `);\n }\n\n async setKeyPairs(keyPairs: CryptoKeyPair[]): Promise<void> {\n const deleteStmt = this.db.prepare(\"DELETE FROM key_pairs\");\n const insertStmt = this.db.prepare(`\n INSERT INTO key_pairs (private_key_jwk, public_key_jwk) \n VALUES (?, ?)\n `);\n\n this.db.exec(\"BEGIN TRANSACTION\");\n try {\n deleteStmt.run();\n\n for (const keyPair of keyPairs) {\n const privateJwk = await exportJwk(keyPair.privateKey);\n const publicJwk = await exportJwk(keyPair.publicKey);\n insertStmt.run(JSON.stringify(privateJwk), JSON.stringify(publicJwk));\n }\n\n this.db.exec(\"COMMIT\");\n } catch (error) {\n this.db.exec(\"ROLLBACK\");\n throw error;\n }\n }\n\n async getKeyPairs(): Promise<CryptoKeyPair[] | undefined> {\n const stmt = this.db.prepare(`\n SELECT private_key_jwk, public_key_jwk FROM key_pairs\n `);\n const rows = stmt.all() as Array<{\n private_key_jwk: string;\n public_key_jwk: string;\n }>;\n\n if (rows.length === 0) return undefined;\n\n const keyPairs: CryptoKeyPair[] = [];\n for (const row of rows) {\n const privateJwk = JSON.parse(row.private_key_jwk);\n const publicJwk = JSON.parse(row.public_key_jwk);\n\n keyPairs.push({\n privateKey: await importJwk(privateJwk, \"private\"),\n publicKey: await importJwk(publicJwk, \"public\"),\n });\n }\n\n return keyPairs;\n }\n\n async addMessage(id: Uuid, activity: Create | Announce): Promise<void> {\n const stmt = this.db.prepare(`\n INSERT INTO messages (id, activity_json, published) \n VALUES (?, ?, ?)\n `);\n\n const activityJson = JSON.stringify(\n await activity.toJsonLd({ format: \"compact\" }),\n );\n const published = activity.published?.epochMilliseconds ?? null;\n\n stmt.run(id, activityJson, published);\n }\n\n async updateMessage(\n id: Uuid,\n updater: (\n existing: Create | Announce,\n ) => Create | Announce | undefined | Promise<Create | Announce | undefined>,\n ): Promise<boolean> {\n const selectStmt = this.db.prepare(`\n SELECT activity_json FROM messages WHERE id = ?\n `);\n const row = selectStmt.get(id) as { activity_json: string } | undefined;\n\n if (!row) return false;\n\n const activityData = JSON.parse(row.activity_json);\n const activity = await Activity.fromJsonLd(activityData);\n\n if (!(activity instanceof Create || activity instanceof Announce)) {\n return false;\n }\n\n const newActivity = await updater(activity);\n if (newActivity == null) return false;\n\n const updateStmt = this.db.prepare(`\n UPDATE messages \n SET activity_json = ?, published = ? \n WHERE id = ?\n `);\n\n const newActivityJson = JSON.stringify(\n await newActivity.toJsonLd({ format: \"compact\" }),\n );\n const published = newActivity.published?.epochMilliseconds ?? null;\n\n updateStmt.run(newActivityJson, published, id);\n return true;\n }\n\n async removeMessage(id: Uuid): Promise<Create | Announce | undefined> {\n const selectStmt = this.db.prepare(`\n SELECT activity_json FROM messages WHERE id = ?\n `);\n const row = selectStmt.get(id) as { activity_json: string } | undefined;\n\n if (!row) return undefined;\n\n const deleteStmt = this.db.prepare(`\n DELETE FROM messages WHERE id = ?\n `);\n deleteStmt.run(id);\n\n try {\n const activityData = JSON.parse(row.activity_json);\n const activity = await Activity.fromJsonLd(activityData);\n\n if (activity instanceof Create || activity instanceof Announce) {\n return activity;\n }\n } catch (error) {\n logger.warn(\"Failed to parse removed message activity\", { id, error });\n }\n\n return undefined;\n }\n\n async *getMessages(\n options: RepositoryGetMessagesOptions = {},\n ): AsyncIterable<Create | Announce> {\n const { order = \"newest\", until, since, limit } = options;\n\n let sql = \"SELECT activity_json FROM messages WHERE 1=1\";\n const params: (number | string)[] = [];\n\n if (since != null) {\n sql += \" AND published >= ?\";\n params.push(since.epochMilliseconds);\n }\n\n if (until != null) {\n sql += \" AND published <= ?\";\n params.push(until.epochMilliseconds);\n }\n\n sql += order === \"oldest\"\n ? \" ORDER BY published ASC\"\n : \" ORDER BY published DESC\";\n\n if (limit != null) {\n sql += \" LIMIT ?\";\n params.push(limit);\n }\n\n const stmt = this.db.prepare(sql);\n const rows = stmt.all(...params) as Array<{ activity_json: string }>;\n\n for (const row of rows) {\n try {\n const activityData = JSON.parse(row.activity_json);\n const activity = await Activity.fromJsonLd(activityData);\n\n if (activity instanceof Create || activity instanceof Announce) {\n yield activity;\n }\n } catch (error) {\n logger.warn(\"Failed to parse message activity\", { error });\n continue;\n }\n }\n }\n\n async getMessage(id: Uuid): Promise<Create | Announce | undefined> {\n const stmt = this.db.prepare(`\n SELECT activity_json FROM messages WHERE id = ?\n `);\n const row = stmt.get(id) as { activity_json: string } | undefined;\n\n if (!row) return undefined;\n\n try {\n const activityData = JSON.parse(row.activity_json);\n const activity = await Activity.fromJsonLd(activityData);\n\n if (activity instanceof Create || activity instanceof Announce) {\n return activity;\n }\n } catch (error) {\n logger.warn(\"Failed to parse message activity\", { id, error });\n }\n\n return undefined;\n }\n\n countMessages(): Promise<number> {\n const stmt = this.db.prepare(\"SELECT COUNT(*) as count FROM messages\");\n const row = stmt.get() as { count: number };\n return Promise.resolve(row.count);\n }\n\n async addFollower(followRequestId: URL, follower: Actor): Promise<void> {\n if (follower.id == null) {\n throw new TypeError(\"The follower ID is missing.\");\n }\n\n const followerJson = JSON.stringify(\n await follower.toJsonLd({ format: \"compact\" }),\n );\n\n const insertFollowerStmt = this.db.prepare(`\n INSERT OR REPLACE INTO followers (follower_id, actor_json) \n VALUES (?, ?)\n `);\n\n const insertRequestStmt = this.db.prepare(`\n INSERT OR REPLACE INTO follow_requests (follow_request_id, follower_id) \n VALUES (?, ?)\n `);\n\n this.db.exec(\"BEGIN TRANSACTION\");\n try {\n insertFollowerStmt.run(follower.id.href, followerJson);\n insertRequestStmt.run(followRequestId.href, follower.id.href);\n this.db.exec(\"COMMIT\");\n } catch (error) {\n this.db.exec(\"ROLLBACK\");\n throw error;\n }\n }\n\n async removeFollower(\n followRequestId: URL,\n actorId: URL,\n ): Promise<Actor | undefined> {\n // Check if the follow request exists and matches the actor\n const checkStmt = this.db.prepare(`\n SELECT fr.follower_id, f.actor_json \n FROM follow_requests fr \n JOIN followers f ON fr.follower_id = f.follower_id \n WHERE fr.follow_request_id = ? AND fr.follower_id = ?\n `);\n\n const row = checkStmt.get(followRequestId.href, actorId.href) as {\n follower_id: string;\n actor_json: string;\n } | undefined;\n\n if (!row) return undefined;\n\n // Remove the follower and follow request\n const deleteRequestStmt = this.db.prepare(`\n DELETE FROM follow_requests WHERE follow_request_id = ?\n `);\n\n const deleteFollowerStmt = this.db.prepare(`\n DELETE FROM followers WHERE follower_id = ?\n `);\n\n this.db.exec(\"BEGIN TRANSACTION\");\n try {\n deleteRequestStmt.run(followRequestId.href);\n deleteFollowerStmt.run(actorId.href);\n this.db.exec(\"COMMIT\");\n } catch (error) {\n this.db.exec(\"ROLLBACK\");\n throw error;\n }\n\n try {\n const actorData = JSON.parse(row.actor_json);\n const actor = await Object.fromJsonLd(actorData);\n\n if (isActor(actor)) {\n return actor;\n }\n } catch (error) {\n logger.warn(\"Failed to parse removed follower actor\", { error });\n }\n\n return undefined;\n }\n\n hasFollower(followerId: URL): Promise<boolean> {\n const stmt = this.db.prepare(`\n SELECT 1 FROM followers WHERE follower_id = ?\n `);\n const row = stmt.get(followerId.href);\n return Promise.resolve(row != null);\n }\n\n async *getFollowers(\n options: RepositoryGetFollowersOptions = {},\n ): AsyncIterable<Actor> {\n const { offset = 0, limit } = options;\n\n let sql = \"SELECT actor_json FROM followers ORDER BY follower_id\";\n const params: number[] = [];\n\n if (limit != null) {\n sql += \" LIMIT ? OFFSET ?\";\n params.push(limit, offset);\n } else if (offset > 0) {\n sql += \" LIMIT -1 OFFSET ?\";\n params.push(offset);\n }\n\n const stmt = this.db.prepare(sql);\n const rows = stmt.all(...params) as { actor_json: string }[];\n\n for (const row of rows) {\n try {\n const actorData = JSON.parse(row.actor_json);\n const actor = await Object.fromJsonLd(actorData);\n\n if (isActor(actor)) {\n yield actor;\n }\n } catch (error) {\n logger.warn(\"Failed to parse follower actor\", { error });\n continue;\n }\n }\n }\n\n countFollowers(): Promise<number> {\n const stmt = this.db.prepare(\"SELECT COUNT(*) as count FROM followers\");\n const row = stmt.get() as { count: number };\n return Promise.resolve(row.count);\n }\n\n async addSentFollow(id: Uuid, follow: Follow): Promise<void> {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO sent_follows (id, follow_json) \n VALUES (?, ?)\n `);\n\n const followJson = JSON.stringify(\n await follow.toJsonLd({ format: \"compact\" }),\n );\n\n stmt.run(id, followJson);\n }\n\n async removeSentFollow(id: Uuid): Promise<Follow | undefined> {\n const follow = await this.getSentFollow(id);\n if (follow == null) return undefined;\n\n const stmt = this.db.prepare(\"DELETE FROM sent_follows WHERE id = ?\");\n stmt.run(id);\n\n return follow;\n }\n\n async getSentFollow(id: Uuid): Promise<Follow | undefined> {\n const stmt = this.db.prepare(`\n SELECT follow_json FROM sent_follows WHERE id = ?\n `);\n const row = stmt.get(id) as { follow_json: string } | undefined;\n\n if (!row) return undefined;\n\n try {\n const followData = JSON.parse(row.follow_json);\n return await Follow.fromJsonLd(followData);\n } catch (error) {\n logger.warn(\"Failed to parse sent follow activity\", { id, error });\n return undefined;\n }\n }\n\n async addFollowee(followeeId: URL, follow: Follow): Promise<void> {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO followees (followee_id, follow_json) \n VALUES (?, ?)\n `);\n\n const followJson = JSON.stringify(\n await follow.toJsonLd({ format: \"compact\" }),\n );\n\n stmt.run(followeeId.href, followJson);\n }\n\n async removeFollowee(followeeId: URL): Promise<Follow | undefined> {\n const follow = await this.getFollowee(followeeId);\n if (follow == null) return undefined;\n\n const stmt = this.db.prepare(\"DELETE FROM followees WHERE followee_id = ?\");\n stmt.run(followeeId.href);\n\n return follow;\n }\n\n async getFollowee(followeeId: URL): Promise<Follow | undefined> {\n const stmt = this.db.prepare(`\n SELECT follow_json FROM followees WHERE followee_id = ?\n `);\n const row = stmt.get(followeeId.href) as\n | { follow_json: string }\n | undefined;\n\n if (!row) return undefined;\n\n try {\n const followData = JSON.parse(row.follow_json);\n return await Follow.fromJsonLd(followData);\n } catch (error) {\n logger.warn(\"Failed to parse followee activity\", {\n followeeId: followeeId.href,\n error,\n });\n return undefined;\n }\n }\n\n vote(messageId: Uuid, voterId: URL, option: string): Promise<void> {\n const stmt = this.db.prepare(`\n INSERT OR IGNORE INTO poll_votes (message_id, voter_id, option) \n VALUES (?, ?, ?)\n `);\n\n stmt.run(messageId, voterId.href, option);\n return Promise.resolve();\n }\n\n countVoters(messageId: Uuid): Promise<number> {\n const stmt = this.db.prepare(`\n SELECT COUNT(DISTINCT voter_id) as count \n FROM poll_votes \n WHERE message_id = ?\n `);\n const row = stmt.get(messageId) as { count: number };\n return Promise.resolve(row.count);\n }\n\n countVotes(messageId: Uuid): Promise<Readonly<Record<string, number>>> {\n const stmt = this.db.prepare(`\n SELECT option, COUNT(*) as count \n FROM poll_votes \n WHERE message_id = ? \n GROUP BY option\n `);\n const rows = stmt.all(messageId) as Array<{\n option: string;\n count: number;\n }>;\n\n const result: Record<string, number> = {};\n for (const row of rows) {\n result[row.option] = row.count;\n }\n\n return Promise.resolve(result);\n }\n}\n"],"mappings":";;;;;;;;;;AAkCA,MAAM,SAAS,UAAU,CAAC,UAAU,QAAS,EAAC;;;;;AAwB9C,IAAa,mBAAb,MAAgE;CAC9D,AAAiB;;;;;CAMjB,YAAYA,UAAmC,CAAE,GAAE;EACjD,MAAM,EAAE,OAAO,YAAY,MAAM,MAAM,GAAG;AAE1C,OAAK,KAAK,IAAI,aAAa;AAG3B,OAAK,GAAG,KAAK,4BAA4B;AAEzC,MAAI,OAAO,SAAS,WAClB,MAAK,GAAG,KAAK,6BAA6B;AAG5C,OAAK,kBAAkB;CACxB;CAED,CAAC,OAAO,WAAW;AACjB,OAAK,OAAO;CACb;;;;CAKD,QAAc;AACZ,OAAK,GAAG,OAAO;CAChB;CAED,AAAQ,mBAAyB;AAE/B,OAAK,GAAG,MAAM;;;;;;MAMZ;AAGF,OAAK,GAAG,MAAM;;;;;;MAMZ;AAGF,OAAK,GAAG,MAAM;;MAEZ;AAGF,OAAK,GAAG,MAAM;;;;;MAKZ;AAGF,OAAK,GAAG,MAAM;;;;;;MAMZ;AAGF,OAAK,GAAG,MAAM;;;;;MAKZ;AAGF,OAAK,GAAG,MAAM;;;;;MAKZ;AAGF,OAAK,GAAG,MAAM;;;;;;;MAOZ;AAGF,OAAK,GAAG,MAAM;;;MAGZ;CACH;CAED,MAAM,YAAYC,UAA0C;EAC1D,MAAM,aAAa,KAAK,GAAG,QAAQ,wBAAwB;EAC3D,MAAM,aAAa,KAAK,GAAG,SAAS;;;MAGlC;AAEF,OAAK,GAAG,KAAK,oBAAoB;AACjC,MAAI;AACF,cAAW,KAAK;AAEhB,QAAK,MAAM,WAAW,UAAU;IAC9B,MAAM,aAAa,MAAM,UAAU,QAAQ,WAAW;IACtD,MAAM,YAAY,MAAM,UAAU,QAAQ,UAAU;AACpD,eAAW,IAAI,KAAK,UAAU,WAAW,EAAE,KAAK,UAAU,UAAU,CAAC;GACtE;AAED,QAAK,GAAG,KAAK,SAAS;EACvB,SAAQ,OAAO;AACd,QAAK,GAAG,KAAK,WAAW;AACxB,SAAM;EACP;CACF;CAED,MAAM,cAAoD;EACxD,MAAM,OAAO,KAAK,GAAG,SAAS;;MAE5B;EACF,MAAM,OAAO,KAAK,KAAK;AAKvB,MAAI,KAAK,WAAW,EAAG;EAEvB,MAAMA,WAA4B,CAAE;AACpC,OAAK,MAAM,OAAO,MAAM;GACtB,MAAM,aAAa,KAAK,MAAM,IAAI,gBAAgB;GAClD,MAAM,YAAY,KAAK,MAAM,IAAI,eAAe;AAEhD,YAAS,KAAK;IACZ,YAAY,MAAM,UAAU,YAAY,UAAU;IAClD,WAAW,MAAM,UAAU,WAAW,SAAS;GAChD,EAAC;EACH;AAED,SAAO;CACR;CAED,MAAM,WAAWC,IAAUC,UAA4C;EACrE,MAAM,OAAO,KAAK,GAAG,SAAS;;;MAG5B;EAEF,MAAM,eAAe,KAAK,UACxB,MAAM,SAAS,SAAS,EAAE,QAAQ,UAAW,EAAC,CAC/C;EACD,MAAM,YAAY,SAAS,WAAW,qBAAqB;AAE3D,OAAK,IAAI,IAAI,cAAc,UAAU;CACtC;CAED,MAAM,cACJD,IACAE,SAGkB;EAClB,MAAM,aAAa,KAAK,GAAG,SAAS;;MAElC;EACF,MAAM,MAAM,WAAW,IAAI,GAAG;AAE9B,OAAK,IAAK,QAAO;EAEjB,MAAM,eAAe,KAAK,MAAM,IAAI,cAAc;EAClD,MAAM,WAAW,MAAM,SAAS,WAAW,aAAa;AAExD,QAAM,oBAAoB,UAAU,oBAAoB,UACtD,QAAO;EAGT,MAAM,cAAc,MAAM,QAAQ,SAAS;AAC3C,MAAI,eAAe,KAAM,QAAO;EAEhC,MAAM,aAAa,KAAK,GAAG,SAAS;;;;MAIlC;EAEF,MAAM,kBAAkB,KAAK,UAC3B,MAAM,YAAY,SAAS,EAAE,QAAQ,UAAW,EAAC,CAClD;EACD,MAAM,YAAY,YAAY,WAAW,qBAAqB;AAE9D,aAAW,IAAI,iBAAiB,WAAW,GAAG;AAC9C,SAAO;CACR;CAED,MAAM,cAAcF,IAAkD;EACpE,MAAM,aAAa,KAAK,GAAG,SAAS;;MAElC;EACF,MAAM,MAAM,WAAW,IAAI,GAAG;AAE9B,OAAK,IAAK;EAEV,MAAM,aAAa,KAAK,GAAG,SAAS;;MAElC;AACF,aAAW,IAAI,GAAG;AAElB,MAAI;GACF,MAAM,eAAe,KAAK,MAAM,IAAI,cAAc;GAClD,MAAM,WAAW,MAAM,SAAS,WAAW,aAAa;AAExD,OAAI,oBAAoB,UAAU,oBAAoB,SACpD,QAAO;EAEV,SAAQ,OAAO;AACd,UAAO,KAAK,4CAA4C;IAAE;IAAI;GAAO,EAAC;EACvE;AAED;CACD;CAED,OAAO,YACLG,UAAwC,CAAE,GACR;EAClC,MAAM,EAAE,QAAQ,UAAU,OAAO,OAAO,OAAO,GAAG;EAElD,IAAI,MAAM;EACV,MAAMC,SAA8B,CAAE;AAEtC,MAAI,SAAS,MAAM;AACjB,UAAO;AACP,UAAO,KAAK,MAAM,kBAAkB;EACrC;AAED,MAAI,SAAS,MAAM;AACjB,UAAO;AACP,UAAO,KAAK,MAAM,kBAAkB;EACrC;AAED,SAAO,UAAU,WACb,4BACA;AAEJ,MAAI,SAAS,MAAM;AACjB,UAAO;AACP,UAAO,KAAK,MAAM;EACnB;EAED,MAAM,OAAO,KAAK,GAAG,QAAQ,IAAI;EACjC,MAAM,OAAO,KAAK,IAAI,GAAG,OAAO;AAEhC,OAAK,MAAM,OAAO,KAChB,KAAI;GACF,MAAM,eAAe,KAAK,MAAM,IAAI,cAAc;GAClD,MAAM,WAAW,MAAM,SAAS,WAAW,aAAa;AAExD,OAAI,oBAAoB,UAAU,oBAAoB,SACpD,OAAM;EAET,SAAQ,OAAO;AACd,UAAO,KAAK,oCAAoC,EAAE,MAAO,EAAC;AAC1D;EACD;CAEJ;CAED,MAAM,WAAWJ,IAAkD;EACjE,MAAM,OAAO,KAAK,GAAG,SAAS;;MAE5B;EACF,MAAM,MAAM,KAAK,IAAI,GAAG;AAExB,OAAK,IAAK;AAEV,MAAI;GACF,MAAM,eAAe,KAAK,MAAM,IAAI,cAAc;GAClD,MAAM,WAAW,MAAM,SAAS,WAAW,aAAa;AAExD,OAAI,oBAAoB,UAAU,oBAAoB,SACpD,QAAO;EAEV,SAAQ,OAAO;AACd,UAAO,KAAK,oCAAoC;IAAE;IAAI;GAAO,EAAC;EAC/D;AAED;CACD;CAED,gBAAiC;EAC/B,MAAM,OAAO,KAAK,GAAG,QAAQ,yCAAyC;EACtE,MAAM,MAAM,KAAK,KAAK;AACtB,SAAO,QAAQ,QAAQ,IAAI,MAAM;CAClC;CAED,MAAM,YAAYK,iBAAsBC,UAAgC;AACtE,MAAI,SAAS,MAAM,KACjB,OAAM,IAAI,UAAU;EAGtB,MAAM,eAAe,KAAK,UACxB,MAAM,SAAS,SAAS,EAAE,QAAQ,UAAW,EAAC,CAC/C;EAED,MAAM,qBAAqB,KAAK,GAAG,SAAS;;;MAG1C;EAEF,MAAM,oBAAoB,KAAK,GAAG,SAAS;;;MAGzC;AAEF,OAAK,GAAG,KAAK,oBAAoB;AACjC,MAAI;AACF,sBAAmB,IAAI,SAAS,GAAG,MAAM,aAAa;AACtD,qBAAkB,IAAI,gBAAgB,MAAM,SAAS,GAAG,KAAK;AAC7D,QAAK,GAAG,KAAK,SAAS;EACvB,SAAQ,OAAO;AACd,QAAK,GAAG,KAAK,WAAW;AACxB,SAAM;EACP;CACF;CAED,MAAM,eACJD,iBACAE,SAC4B;EAE5B,MAAM,YAAY,KAAK,GAAG,SAAS;;;;;MAKjC;EAEF,MAAM,MAAM,UAAU,IAAI,gBAAgB,MAAM,QAAQ,KAAK;AAK7D,OAAK,IAAK;EAGV,MAAM,oBAAoB,KAAK,GAAG,SAAS;;MAEzC;EAEF,MAAM,qBAAqB,KAAK,GAAG,SAAS;;MAE1C;AAEF,OAAK,GAAG,KAAK,oBAAoB;AACjC,MAAI;AACF,qBAAkB,IAAI,gBAAgB,KAAK;AAC3C,sBAAmB,IAAI,QAAQ,KAAK;AACpC,QAAK,GAAG,KAAK,SAAS;EACvB,SAAQ,OAAO;AACd,QAAK,GAAG,KAAK,WAAW;AACxB,SAAM;EACP;AAED,MAAI;GACF,MAAM,YAAY,KAAK,MAAM,IAAI,WAAW;GAC5C,MAAM,QAAQ,MAAM,SAAO,WAAW,UAAU;AAEhD,OAAI,QAAQ,MAAM,CAChB,QAAO;EAEV,SAAQ,OAAO;AACd,UAAO,KAAK,0CAA0C,EAAE,MAAO,EAAC;EACjE;AAED;CACD;CAED,YAAYC,YAAmC;EAC7C,MAAM,OAAO,KAAK,GAAG,SAAS;;MAE5B;EACF,MAAM,MAAM,KAAK,IAAI,WAAW,KAAK;AACrC,SAAO,QAAQ,QAAQ,OAAO,KAAK;CACpC;CAED,OAAO,aACLC,UAAyC,CAAE,GACrB;EACtB,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG;EAE9B,IAAI,MAAM;EACV,MAAMC,SAAmB,CAAE;AAE3B,MAAI,SAAS,MAAM;AACjB,UAAO;AACP,UAAO,KAAK,OAAO,OAAO;EAC3B,WAAU,SAAS,GAAG;AACrB,UAAO;AACP,UAAO,KAAK,OAAO;EACpB;EAED,MAAM,OAAO,KAAK,GAAG,QAAQ,IAAI;EACjC,MAAM,OAAO,KAAK,IAAI,GAAG,OAAO;AAEhC,OAAK,MAAM,OAAO,KAChB,KAAI;GACF,MAAM,YAAY,KAAK,MAAM,IAAI,WAAW;GAC5C,MAAM,QAAQ,MAAM,SAAO,WAAW,UAAU;AAEhD,OAAI,QAAQ,MAAM,CAChB,OAAM;EAET,SAAQ,OAAO;AACd,UAAO,KAAK,kCAAkC,EAAE,MAAO,EAAC;AACxD;EACD;CAEJ;CAED,iBAAkC;EAChC,MAAM,OAAO,KAAK,GAAG,QAAQ,0CAA0C;EACvE,MAAM,MAAM,KAAK,KAAK;AACtB,SAAO,QAAQ,QAAQ,IAAI,MAAM;CAClC;CAED,MAAM,cAAcV,IAAUW,QAA+B;EAC3D,MAAM,OAAO,KAAK,GAAG,SAAS;;;MAG5B;EAEF,MAAM,aAAa,KAAK,UACtB,MAAM,OAAO,SAAS,EAAE,QAAQ,UAAW,EAAC,CAC7C;AAED,OAAK,IAAI,IAAI,WAAW;CACzB;CAED,MAAM,iBAAiBX,IAAuC;EAC5D,MAAM,SAAS,MAAM,KAAK,cAAc,GAAG;AAC3C,MAAI,UAAU,KAAM;EAEpB,MAAM,OAAO,KAAK,GAAG,QAAQ,wCAAwC;AACrE,OAAK,IAAI,GAAG;AAEZ,SAAO;CACR;CAED,MAAM,cAAcA,IAAuC;EACzD,MAAM,OAAO,KAAK,GAAG,SAAS;;MAE5B;EACF,MAAM,MAAM,KAAK,IAAI,GAAG;AAExB,OAAK,IAAK;AAEV,MAAI;GACF,MAAM,aAAa,KAAK,MAAM,IAAI,YAAY;AAC9C,UAAO,MAAM,OAAO,WAAW,WAAW;EAC3C,SAAQ,OAAO;AACd,UAAO,KAAK,wCAAwC;IAAE;IAAI;GAAO,EAAC;AAClE;EACD;CACF;CAED,MAAM,YAAYY,YAAiBD,QAA+B;EAChE,MAAM,OAAO,KAAK,GAAG,SAAS;;;MAG5B;EAEF,MAAM,aAAa,KAAK,UACtB,MAAM,OAAO,SAAS,EAAE,QAAQ,UAAW,EAAC,CAC7C;AAED,OAAK,IAAI,WAAW,MAAM,WAAW;CACtC;CAED,MAAM,eAAeC,YAA8C;EACjE,MAAM,SAAS,MAAM,KAAK,YAAY,WAAW;AACjD,MAAI,UAAU,KAAM;EAEpB,MAAM,OAAO,KAAK,GAAG,QAAQ,8CAA8C;AAC3E,OAAK,IAAI,WAAW,KAAK;AAEzB,SAAO;CACR;CAED,MAAM,YAAYA,YAA8C;EAC9D,MAAM,OAAO,KAAK,GAAG,SAAS;;MAE5B;EACF,MAAM,MAAM,KAAK,IAAI,WAAW,KAAK;AAIrC,OAAK,IAAK;AAEV,MAAI;GACF,MAAM,aAAa,KAAK,MAAM,IAAI,YAAY;AAC9C,UAAO,MAAM,OAAO,WAAW,WAAW;EAC3C,SAAQ,OAAO;AACd,UAAO,KAAK,qCAAqC;IAC/C,YAAY,WAAW;IACvB;GACD,EAAC;AACF;EACD;CACF;CAED,KAAKC,WAAiBC,SAAcC,QAA+B;EACjE,MAAM,OAAO,KAAK,GAAG,SAAS;;;MAG5B;AAEF,OAAK,IAAI,WAAW,QAAQ,MAAM,OAAO;AACzC,SAAO,QAAQ,SAAS;CACzB;CAED,YAAYF,WAAkC;EAC5C,MAAM,OAAO,KAAK,GAAG,SAAS;;;;MAI5B;EACF,MAAM,MAAM,KAAK,IAAI,UAAU;AAC/B,SAAO,QAAQ,QAAQ,IAAI,MAAM;CAClC;CAED,WAAWA,WAA4D;EACrE,MAAM,OAAO,KAAK,GAAG,SAAS;;;;;MAK5B;EACF,MAAM,OAAO,KAAK,IAAI,UAAU;EAKhC,MAAMG,SAAiC,CAAE;AACzC,OAAK,MAAM,OAAO,KAChB,QAAO,IAAI,UAAU,IAAI;AAG3B,SAAO,QAAQ,QAAQ,OAAO;CAC/B;AACF"}
1
+ {"version":3,"file":"mod.js","names":["options: SqliteRepositoryOptions","table: string","identifier: string","keyPairs: CryptoKeyPair[]","id: Uuid","activity: Create | Announce","updater: (\n existing: Create | Announce,\n ) => Create | Announce | undefined | Promise<Create | Announce | undefined>","options: RepositoryGetMessagesOptions","params: (number | string)[]","followRequestId: URL","follower: Actor","actorId: URL","followerId: URL","options: RepositoryGetFollowersOptions","follow: Follow","followeeId: URL","messageId: Uuid","voterId: URL","option: string","result: Record<string, number>"],"sources":["../src/mod.ts"],"sourcesContent":["// BotKit by Fedify: A framework for creating ActivityPub bots\n// Copyright (C) 2025–2026 Hong Minhee <https://hongminhee.org/>\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as\n// published by the Free Software Foundation, either version 3 of the\n// License, or (at your option) any later version.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU Affero General Public License for more details.\n//\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see <https://www.gnu.org/licenses/>.\nimport {\n ActorScopedRepository,\n type Repository,\n type RepositoryGetFollowersOptions,\n type RepositoryGetMessagesOptions,\n type Uuid,\n} from \"@fedify/botkit/repository\";\nimport { exportJwk, importJwk } from \"@fedify/fedify/sig\";\nimport {\n Activity,\n type Actor,\n Announce,\n Create,\n Follow,\n isActor,\n Object,\n} from \"@fedify/vocab\";\nimport { getLogger } from \"@logtape/logtape\";\nimport { DatabaseSync } from \"node:sqlite\";\n\nconst logger = getLogger([\"botkit\", \"sqlite\"]);\n\n/**\n * Options for creating a SQLite repository.\n * @since 0.3.0\n */\nexport interface SqliteRepositoryOptions {\n /**\n * The path to the SQLite database file.\n * If not provided, an in-memory database will be used.\n */\n readonly path?: string;\n\n /**\n * Whether to enable Write-Ahead Logging (WAL) mode.\n * @default true\n */\n readonly wal?: boolean;\n}\n\n/**\n * A repository for storing bot data using SQLite.\n * @since 0.3.0\n */\nexport class SqliteRepository implements Repository, Disposable {\n private readonly db: DatabaseSync;\n\n /**\n * Creates a new SQLite repository.\n * @param options The options for creating the repository.\n */\n constructor(options: SqliteRepositoryOptions = {}) {\n const { path = \":memory:\", wal = true } = options;\n\n this.db = new DatabaseSync(path);\n\n // Enable foreign key constraints\n this.db.exec(\"PRAGMA foreign_keys = ON;\");\n\n if (wal && path !== \":memory:\") {\n this.db.exec(\"PRAGMA journal_mode = WAL;\");\n }\n\n this.initializeTables();\n }\n\n [Symbol.dispose]() {\n this.close();\n }\n\n /**\n * Closes the database connection.\n */\n close(): void {\n this.db.close();\n }\n\n private tableExists(table: string): boolean {\n const stmt = this.db.prepare(`\n SELECT COUNT(*) AS count FROM sqlite_master\n WHERE type = 'table' AND name = ?\n `);\n const row = stmt.get(table) as { count: number };\n return row.count > 0;\n }\n\n private hasBotIdColumn(table: string): boolean {\n const stmt = this.db.prepare(`\n SELECT COUNT(*) AS count FROM pragma_table_info(?)\n WHERE name = 'bot_id'\n `);\n const row = stmt.get(table) as { count: number };\n return row.count > 0;\n }\n\n /**\n * Rebuilds tables created by \\@fedify/botkit-sqlite 0.4 or earlier, which\n * had no `bot_id` column, into the bot-scoped schema. Existing rows get\n * the empty-string bot ID; use {@link SqliteRepository.migrate} to assign\n * them to a bot actor identifier.\n */\n private rebuildLegacyTables(): void {\n const tables = [\n \"key_pairs\",\n \"messages\",\n \"followers\",\n \"follow_requests\",\n \"sent_follows\",\n \"followees\",\n \"poll_votes\",\n ].filter((table) => this.tableExists(table) && !this.hasBotIdColumn(table));\n if (tables.length < 1) return;\n logger.info(\n \"Rebuilding legacy tables without a bot_id column: {tables}.\",\n { tables },\n );\n // The marker lets migrate() distinguish rows carried over from a legacy\n // database (bot_id = '') from data legitimately stored under an\n // empty-string identifier:\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS botkit_metadata (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n )\n `);\n // SQLite cannot add a column to a composite primary key, so the tables\n // are rebuilt (create new, copy, drop, rename) in a single transaction.\n // Foreign key enforcement is turned off during the rebuild since\n // follow_requests references followers.\n this.db.exec(\"PRAGMA foreign_keys = OFF;\");\n this.db.exec(\"BEGIN TRANSACTION\");\n try {\n if (tables.includes(\"key_pairs\")) {\n // The primary key does not change; adding the column suffices:\n this.db.exec(`\n ALTER TABLE key_pairs ADD COLUMN bot_id TEXT NOT NULL DEFAULT ''\n `);\n }\n if (tables.includes(\"messages\")) {\n this.db.exec(`\n CREATE TABLE messages_new (\n bot_id TEXT NOT NULL,\n id TEXT NOT NULL,\n activity_json TEXT NOT NULL,\n published INTEGER,\n PRIMARY KEY (bot_id, id)\n )\n `);\n this.db.exec(`\n INSERT INTO messages_new (bot_id, id, activity_json, published)\n SELECT '', id, activity_json, published FROM messages\n `);\n this.db.exec(\"DROP TABLE messages\");\n this.db.exec(\"ALTER TABLE messages_new RENAME TO messages\");\n }\n if (tables.includes(\"followers\")) {\n this.db.exec(`\n CREATE TABLE followers_new (\n bot_id TEXT NOT NULL,\n follower_id TEXT NOT NULL,\n actor_json TEXT NOT NULL,\n PRIMARY KEY (bot_id, follower_id)\n )\n `);\n this.db.exec(`\n INSERT INTO followers_new (bot_id, follower_id, actor_json)\n SELECT '', follower_id, actor_json FROM followers\n `);\n this.db.exec(\"DROP TABLE followers\");\n this.db.exec(\"ALTER TABLE followers_new RENAME TO followers\");\n }\n if (tables.includes(\"follow_requests\")) {\n this.db.exec(`\n CREATE TABLE follow_requests_new (\n bot_id TEXT NOT NULL,\n follow_request_id TEXT NOT NULL,\n follower_id TEXT NOT NULL,\n PRIMARY KEY (bot_id, follow_request_id),\n FOREIGN KEY (bot_id, follower_id)\n REFERENCES followers(bot_id, follower_id)\n )\n `);\n this.db.exec(`\n INSERT INTO follow_requests_new\n (bot_id, follow_request_id, follower_id)\n SELECT '', follow_request_id, follower_id FROM follow_requests\n `);\n this.db.exec(\"DROP TABLE follow_requests\");\n this.db.exec(\n \"ALTER TABLE follow_requests_new RENAME TO follow_requests\",\n );\n }\n if (tables.includes(\"sent_follows\")) {\n this.db.exec(`\n CREATE TABLE sent_follows_new (\n bot_id TEXT NOT NULL,\n id TEXT NOT NULL,\n follow_json TEXT NOT NULL,\n PRIMARY KEY (bot_id, id)\n )\n `);\n this.db.exec(`\n INSERT INTO sent_follows_new (bot_id, id, follow_json)\n SELECT '', id, follow_json FROM sent_follows\n `);\n this.db.exec(\"DROP TABLE sent_follows\");\n this.db.exec(\"ALTER TABLE sent_follows_new RENAME TO sent_follows\");\n }\n if (tables.includes(\"followees\")) {\n this.db.exec(`\n CREATE TABLE followees_new (\n bot_id TEXT NOT NULL,\n followee_id TEXT NOT NULL,\n follow_json TEXT NOT NULL,\n PRIMARY KEY (bot_id, followee_id)\n )\n `);\n this.db.exec(`\n INSERT INTO followees_new (bot_id, followee_id, follow_json)\n SELECT '', followee_id, follow_json FROM followees\n `);\n this.db.exec(\"DROP TABLE followees\");\n this.db.exec(\"ALTER TABLE followees_new RENAME TO followees\");\n }\n if (tables.includes(\"poll_votes\")) {\n this.db.exec(`\n CREATE TABLE poll_votes_new (\n bot_id TEXT NOT NULL,\n message_id TEXT NOT NULL,\n voter_id TEXT NOT NULL,\n option TEXT NOT NULL,\n PRIMARY KEY (bot_id, message_id, voter_id, option)\n )\n `);\n this.db.exec(`\n INSERT INTO poll_votes_new (bot_id, message_id, voter_id, option)\n SELECT '', message_id, voter_id, option FROM poll_votes\n `);\n this.db.exec(\"DROP TABLE poll_votes\");\n this.db.exec(\"ALTER TABLE poll_votes_new RENAME TO poll_votes\");\n }\n this.db.exec(`\n INSERT OR REPLACE INTO botkit_metadata (key, value)\n VALUES ('legacy_data', '1')\n `);\n this.db.exec(\"COMMIT\");\n } catch (error) {\n this.db.exec(\"ROLLBACK\");\n this.db.exec(\"PRAGMA foreign_keys = ON;\");\n throw error;\n }\n this.db.exec(\"PRAGMA foreign_keys = ON;\");\n logger.info(\"Finished rebuilding legacy tables.\");\n }\n\n /**\n * Migrates data stored by \\@fedify/botkit-sqlite 0.4 or earlier, which was\n * not scoped by bot actor identifiers, so that it belongs to the given\n * identifier. Rows carried over from a legacy database have the\n * empty-string bot ID; this method assigns them to the identifier in\n * a single transaction. It only acts when the database was actually\n * rebuilt from a legacy schema, so data legitimately stored under an\n * empty-string identifier is never touched, and calling it again is\n * a no-op.\n * @param identifier The identifier of the bot actor that adopts the legacy\n * data.\n * @since 0.5.0\n */\n migrate(identifier: string): Promise<void> {\n if (!this.tableExists(\"botkit_metadata\")) return Promise.resolve();\n const marker = this.db.prepare(\n \"SELECT value FROM botkit_metadata WHERE key = 'legacy_data'\",\n ).get() as { value: string } | undefined;\n if (marker == null) return Promise.resolve();\n this.db.exec(\"BEGIN TRANSACTION\");\n // Updating followers and follow_requests rows in tandem temporarily\n // breaks the foreign key between them; defer the check to the commit:\n this.db.exec(\"PRAGMA defer_foreign_keys = ON\");\n try {\n for (\n const table of [\n \"key_pairs\",\n \"messages\",\n \"followers\",\n \"follow_requests\",\n \"sent_follows\",\n \"followees\",\n \"poll_votes\",\n ]\n ) {\n this.db.prepare(`UPDATE ${table} SET bot_id = ? WHERE bot_id = ''`)\n .run(identifier);\n }\n this.db.exec(\"DELETE FROM botkit_metadata WHERE key = 'legacy_data'\");\n this.db.exec(\"COMMIT\");\n } catch (error) {\n this.db.exec(\"ROLLBACK\");\n throw error;\n }\n return Promise.resolve();\n }\n\n private initializeTables(): void {\n this.rebuildLegacyTables();\n\n // Key pairs table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS key_pairs (\n id INTEGER PRIMARY KEY,\n bot_id TEXT NOT NULL,\n private_key_jwk TEXT NOT NULL,\n public_key_jwk TEXT NOT NULL\n )\n `);\n\n // Create index on bot_id for efficient per-bot lookup\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_key_pairs_bot_id ON key_pairs(bot_id)\n `);\n\n // Messages table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS messages (\n bot_id TEXT NOT NULL,\n id TEXT NOT NULL,\n activity_json TEXT NOT NULL,\n published INTEGER,\n PRIMARY KEY (bot_id, id)\n )\n `);\n\n // Create index on published timestamp for efficient ordering\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_messages_bot_published\n ON messages(bot_id, published)\n `);\n\n // Followers table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS followers (\n bot_id TEXT NOT NULL,\n follower_id TEXT NOT NULL,\n actor_json TEXT NOT NULL,\n PRIMARY KEY (bot_id, follower_id)\n )\n `);\n\n // Follow requests mapping table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS follow_requests (\n bot_id TEXT NOT NULL,\n follow_request_id TEXT NOT NULL,\n follower_id TEXT NOT NULL,\n PRIMARY KEY (bot_id, follow_request_id),\n FOREIGN KEY (bot_id, follower_id)\n REFERENCES followers(bot_id, follower_id)\n )\n `);\n\n // Sent follows table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS sent_follows (\n bot_id TEXT NOT NULL,\n id TEXT NOT NULL,\n follow_json TEXT NOT NULL,\n PRIMARY KEY (bot_id, id)\n )\n `);\n\n // Followees table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS followees (\n bot_id TEXT NOT NULL,\n followee_id TEXT NOT NULL,\n follow_json TEXT NOT NULL,\n PRIMARY KEY (bot_id, followee_id)\n )\n `);\n\n // Create index for reverse lookup of bots following an actor\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_followees_followee_id\n ON followees(followee_id)\n `);\n\n // Poll votes table\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS poll_votes (\n bot_id TEXT NOT NULL,\n message_id TEXT NOT NULL,\n voter_id TEXT NOT NULL,\n option TEXT NOT NULL,\n PRIMARY KEY (bot_id, message_id, voter_id, option)\n )\n `);\n\n // Create index for efficient vote counting\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_poll_votes_bot_message_option\n ON poll_votes(bot_id, message_id, option)\n `);\n }\n\n async setKeyPairs(\n identifier: string,\n keyPairs: CryptoKeyPair[],\n ): Promise<void> {\n const deleteStmt = this.db.prepare(\n \"DELETE FROM key_pairs WHERE bot_id = ?\",\n );\n const insertStmt = this.db.prepare(`\n INSERT INTO key_pairs (bot_id, private_key_jwk, public_key_jwk)\n VALUES (?, ?, ?)\n `);\n\n this.db.exec(\"BEGIN TRANSACTION\");\n try {\n deleteStmt.run(identifier);\n\n for (const keyPair of keyPairs) {\n const privateJwk = await exportJwk(keyPair.privateKey);\n const publicJwk = await exportJwk(keyPair.publicKey);\n insertStmt.run(\n identifier,\n JSON.stringify(privateJwk),\n JSON.stringify(publicJwk),\n );\n }\n\n this.db.exec(\"COMMIT\");\n } catch (error) {\n this.db.exec(\"ROLLBACK\");\n throw error;\n }\n }\n\n async getKeyPairs(identifier: string): Promise<CryptoKeyPair[] | undefined> {\n const stmt = this.db.prepare(`\n SELECT private_key_jwk, public_key_jwk FROM key_pairs\n WHERE bot_id = ? ORDER BY id\n `);\n const rows = stmt.all(identifier) as Array<{\n private_key_jwk: string;\n public_key_jwk: string;\n }>;\n\n if (rows.length === 0) return undefined;\n\n const keyPairs: CryptoKeyPair[] = [];\n for (const row of rows) {\n const privateJwk = JSON.parse(row.private_key_jwk);\n const publicJwk = JSON.parse(row.public_key_jwk);\n\n keyPairs.push({\n privateKey: await importJwk(privateJwk, \"private\"),\n publicKey: await importJwk(publicJwk, \"public\"),\n });\n }\n\n return keyPairs;\n }\n\n async addMessage(\n identifier: string,\n id: Uuid,\n activity: Create | Announce,\n ): Promise<void> {\n const stmt = this.db.prepare(`\n INSERT INTO messages (bot_id, id, activity_json, published)\n VALUES (?, ?, ?, ?)\n `);\n\n const activityJson = JSON.stringify(\n await activity.toJsonLd({ format: \"compact\" }),\n );\n const published = activity.published?.epochMilliseconds ?? null;\n\n stmt.run(identifier, id, activityJson, published);\n }\n\n async updateMessage(\n identifier: string,\n id: Uuid,\n updater: (\n existing: Create | Announce,\n ) => Create | Announce | undefined | Promise<Create | Announce | undefined>,\n ): Promise<boolean> {\n const selectStmt = this.db.prepare(`\n SELECT activity_json FROM messages WHERE bot_id = ? AND id = ?\n `);\n const row = selectStmt.get(identifier, id) as\n | { activity_json: string }\n | undefined;\n\n if (!row) return false;\n\n const activityData = JSON.parse(row.activity_json);\n const activity = await Activity.fromJsonLd(activityData);\n\n if (!(activity instanceof Create || activity instanceof Announce)) {\n return false;\n }\n\n const newActivity = await updater(activity);\n if (newActivity == null) return false;\n\n const updateStmt = this.db.prepare(`\n UPDATE messages\n SET activity_json = ?, published = ?\n WHERE bot_id = ? AND id = ?\n `);\n\n const newActivityJson = JSON.stringify(\n await newActivity.toJsonLd({ format: \"compact\" }),\n );\n const published = newActivity.published?.epochMilliseconds ?? null;\n\n updateStmt.run(newActivityJson, published, identifier, id);\n return true;\n }\n\n async removeMessage(\n identifier: string,\n id: Uuid,\n ): Promise<Create | Announce | undefined> {\n const selectStmt = this.db.prepare(`\n SELECT activity_json FROM messages WHERE bot_id = ? AND id = ?\n `);\n const row = selectStmt.get(identifier, id) as\n | { activity_json: string }\n | undefined;\n\n if (!row) return undefined;\n\n const deleteStmt = this.db.prepare(`\n DELETE FROM messages WHERE bot_id = ? AND id = ?\n `);\n deleteStmt.run(identifier, id);\n\n try {\n const activityData = JSON.parse(row.activity_json);\n const activity = await Activity.fromJsonLd(activityData);\n\n if (activity instanceof Create || activity instanceof Announce) {\n return activity;\n }\n } catch (error) {\n logger.warn(\"Failed to parse removed message activity\", { id, error });\n }\n\n return undefined;\n }\n\n async *getMessages(\n identifier: string,\n options: RepositoryGetMessagesOptions = {},\n ): AsyncIterable<Create | Announce> {\n const { order = \"newest\", until, since, limit } = options;\n\n let sql = \"SELECT activity_json FROM messages WHERE bot_id = ?\";\n const params: (number | string)[] = [identifier];\n\n if (since != null) {\n sql += \" AND published >= ?\";\n params.push(since.epochMilliseconds);\n }\n\n if (until != null) {\n sql += \" AND published <= ?\";\n params.push(until.epochMilliseconds);\n }\n\n sql += order === \"oldest\"\n ? \" ORDER BY published ASC\"\n : \" ORDER BY published DESC\";\n\n if (limit != null) {\n sql += \" LIMIT ?\";\n params.push(limit);\n }\n\n const stmt = this.db.prepare(sql);\n const rows = stmt.all(...params) as Array<{ activity_json: string }>;\n\n for (const row of rows) {\n try {\n const activityData = JSON.parse(row.activity_json);\n const activity = await Activity.fromJsonLd(activityData);\n\n if (activity instanceof Create || activity instanceof Announce) {\n yield activity;\n }\n } catch (error) {\n logger.warn(\"Failed to parse message activity\", { error });\n continue;\n }\n }\n }\n\n async getMessage(\n identifier: string,\n id: Uuid,\n ): Promise<Create | Announce | undefined> {\n const stmt = this.db.prepare(`\n SELECT activity_json FROM messages WHERE bot_id = ? AND id = ?\n `);\n const row = stmt.get(identifier, id) as\n | { activity_json: string }\n | undefined;\n\n if (!row) return undefined;\n\n try {\n const activityData = JSON.parse(row.activity_json);\n const activity = await Activity.fromJsonLd(activityData);\n\n if (activity instanceof Create || activity instanceof Announce) {\n return activity;\n }\n } catch (error) {\n logger.warn(\"Failed to parse message activity\", { id, error });\n }\n\n return undefined;\n }\n\n countMessages(identifier: string): Promise<number> {\n const stmt = this.db.prepare(\n \"SELECT COUNT(*) as count FROM messages WHERE bot_id = ?\",\n );\n const row = stmt.get(identifier) as { count: number };\n return Promise.resolve(row.count);\n }\n\n async addFollower(\n identifier: string,\n followRequestId: URL,\n follower: Actor,\n ): Promise<void> {\n if (follower.id == null) {\n throw new TypeError(\"The follower ID is missing.\");\n }\n\n const followerJson = JSON.stringify(\n await follower.toJsonLd({ format: \"compact\" }),\n );\n\n const insertFollowerStmt = this.db.prepare(`\n INSERT OR REPLACE INTO followers (bot_id, follower_id, actor_json)\n VALUES (?, ?, ?)\n `);\n\n const insertRequestStmt = this.db.prepare(`\n INSERT OR REPLACE INTO follow_requests\n (bot_id, follow_request_id, follower_id)\n VALUES (?, ?, ?)\n `);\n\n this.db.exec(\"BEGIN TRANSACTION\");\n try {\n insertFollowerStmt.run(identifier, follower.id.href, followerJson);\n insertRequestStmt.run(\n identifier,\n followRequestId.href,\n follower.id.href,\n );\n this.db.exec(\"COMMIT\");\n } catch (error) {\n this.db.exec(\"ROLLBACK\");\n throw error;\n }\n }\n\n async removeFollower(\n identifier: string,\n followRequestId: URL,\n actorId: URL,\n ): Promise<Actor | undefined> {\n // Check if the follow request exists and matches the actor\n const checkStmt = this.db.prepare(`\n SELECT fr.follower_id, f.actor_json\n FROM follow_requests fr\n JOIN followers f\n ON fr.bot_id = f.bot_id AND fr.follower_id = f.follower_id\n WHERE fr.bot_id = ? AND fr.follow_request_id = ? AND fr.follower_id = ?\n `);\n\n const row = checkStmt.get(\n identifier,\n followRequestId.href,\n actorId.href,\n ) as\n | {\n follower_id: string;\n actor_json: string;\n }\n | undefined;\n\n if (!row) return undefined;\n\n // Remove the follower and follow request\n const deleteRequestStmt = this.db.prepare(`\n DELETE FROM follow_requests WHERE bot_id = ? AND follow_request_id = ?\n `);\n\n const deleteFollowerStmt = this.db.prepare(`\n DELETE FROM followers WHERE bot_id = ? AND follower_id = ?\n `);\n\n this.db.exec(\"BEGIN TRANSACTION\");\n try {\n deleteRequestStmt.run(identifier, followRequestId.href);\n deleteFollowerStmt.run(identifier, actorId.href);\n this.db.exec(\"COMMIT\");\n } catch (error) {\n this.db.exec(\"ROLLBACK\");\n throw error;\n }\n\n try {\n const actorData = JSON.parse(row.actor_json);\n const actor = await Object.fromJsonLd(actorData);\n\n if (isActor(actor)) {\n return actor;\n }\n } catch (error) {\n logger.warn(\"Failed to parse removed follower actor\", { error });\n }\n\n return undefined;\n }\n\n hasFollower(identifier: string, followerId: URL): Promise<boolean> {\n const stmt = this.db.prepare(`\n SELECT 1 FROM followers WHERE bot_id = ? AND follower_id = ?\n `);\n const row = stmt.get(identifier, followerId.href);\n return Promise.resolve(row != null);\n }\n\n async *getFollowers(\n identifier: string,\n options: RepositoryGetFollowersOptions = {},\n ): AsyncIterable<Actor> {\n const { offset = 0, limit } = options;\n\n let sql =\n \"SELECT actor_json FROM followers WHERE bot_id = ? ORDER BY follower_id\";\n const params: (number | string)[] = [identifier];\n\n if (limit != null) {\n sql += \" LIMIT ? OFFSET ?\";\n params.push(limit, offset);\n } else if (offset > 0) {\n sql += \" LIMIT -1 OFFSET ?\";\n params.push(offset);\n }\n\n const stmt = this.db.prepare(sql);\n const rows = stmt.all(...params) as { actor_json: string }[];\n\n for (const row of rows) {\n try {\n const actorData = JSON.parse(row.actor_json);\n const actor = await Object.fromJsonLd(actorData);\n\n if (isActor(actor)) {\n yield actor;\n }\n } catch (error) {\n logger.warn(\"Failed to parse follower actor\", { error });\n continue;\n }\n }\n }\n\n countFollowers(identifier: string): Promise<number> {\n const stmt = this.db.prepare(\n \"SELECT COUNT(*) as count FROM followers WHERE bot_id = ?\",\n );\n const row = stmt.get(identifier) as { count: number };\n return Promise.resolve(row.count);\n }\n\n async addSentFollow(\n identifier: string,\n id: Uuid,\n follow: Follow,\n ): Promise<void> {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO sent_follows (bot_id, id, follow_json)\n VALUES (?, ?, ?)\n `);\n\n const followJson = JSON.stringify(\n await follow.toJsonLd({ format: \"compact\" }),\n );\n\n stmt.run(identifier, id, followJson);\n }\n\n async removeSentFollow(\n identifier: string,\n id: Uuid,\n ): Promise<Follow | undefined> {\n const follow = await this.getSentFollow(identifier, id);\n if (follow == null) return undefined;\n\n const stmt = this.db.prepare(\n \"DELETE FROM sent_follows WHERE bot_id = ? AND id = ?\",\n );\n stmt.run(identifier, id);\n\n return follow;\n }\n\n async getSentFollow(\n identifier: string,\n id: Uuid,\n ): Promise<Follow | undefined> {\n const stmt = this.db.prepare(`\n SELECT follow_json FROM sent_follows WHERE bot_id = ? AND id = ?\n `);\n const row = stmt.get(identifier, id) as\n | { follow_json: string }\n | undefined;\n\n if (!row) return undefined;\n\n try {\n const followData = JSON.parse(row.follow_json);\n return await Follow.fromJsonLd(followData);\n } catch (error) {\n logger.warn(\"Failed to parse sent follow activity\", { id, error });\n return undefined;\n }\n }\n\n async addFollowee(\n identifier: string,\n followeeId: URL,\n follow: Follow,\n ): Promise<void> {\n const stmt = this.db.prepare(`\n INSERT OR REPLACE INTO followees (bot_id, followee_id, follow_json)\n VALUES (?, ?, ?)\n `);\n\n const followJson = JSON.stringify(\n await follow.toJsonLd({ format: \"compact\" }),\n );\n\n stmt.run(identifier, followeeId.href, followJson);\n }\n\n async removeFollowee(\n identifier: string,\n followeeId: URL,\n ): Promise<Follow | undefined> {\n const follow = await this.getFollowee(identifier, followeeId);\n if (follow == null) return undefined;\n\n const stmt = this.db.prepare(\n \"DELETE FROM followees WHERE bot_id = ? AND followee_id = ?\",\n );\n stmt.run(identifier, followeeId.href);\n\n return follow;\n }\n\n async getFollowee(\n identifier: string,\n followeeId: URL,\n ): Promise<Follow | undefined> {\n const stmt = this.db.prepare(`\n SELECT follow_json FROM followees WHERE bot_id = ? AND followee_id = ?\n `);\n const row = stmt.get(identifier, followeeId.href) as\n | { follow_json: string }\n | undefined;\n\n if (!row) return undefined;\n\n try {\n const followData = JSON.parse(row.follow_json);\n return await Follow.fromJsonLd(followData);\n } catch (error) {\n logger.warn(\"Failed to parse followee activity\", {\n followeeId: followeeId.href,\n error,\n });\n return undefined;\n }\n }\n\n async *findFollowedBots(followeeId: URL): AsyncIterable<string> {\n const stmt = this.db.prepare(`\n SELECT bot_id FROM followees WHERE followee_id = ? ORDER BY bot_id\n `);\n const rows = stmt.all(followeeId.href) as { bot_id: string }[];\n for (const row of rows) yield row.bot_id;\n }\n\n vote(\n identifier: string,\n messageId: Uuid,\n voterId: URL,\n option: string,\n ): Promise<void> {\n const stmt = this.db.prepare(`\n INSERT OR IGNORE INTO poll_votes (bot_id, message_id, voter_id, option)\n VALUES (?, ?, ?, ?)\n `);\n\n stmt.run(identifier, messageId, voterId.href, option);\n return Promise.resolve();\n }\n\n countVoters(identifier: string, messageId: Uuid): Promise<number> {\n const stmt = this.db.prepare(`\n SELECT COUNT(DISTINCT voter_id) as count\n FROM poll_votes\n WHERE bot_id = ? AND message_id = ?\n `);\n const row = stmt.get(identifier, messageId) as { count: number };\n return Promise.resolve(row.count);\n }\n\n countVotes(\n identifier: string,\n messageId: Uuid,\n ): Promise<Readonly<Record<string, number>>> {\n const stmt = this.db.prepare(`\n SELECT option, COUNT(*) as count\n FROM poll_votes\n WHERE bot_id = ? AND message_id = ?\n GROUP BY option\n `);\n const rows = stmt.all(identifier, messageId) as Array<{\n option: string;\n count: number;\n }>;\n\n const result: Record<string, number> = {};\n for (const row of rows) {\n result[row.option] = row.count;\n }\n\n return Promise.resolve(result);\n }\n\n forIdentifier(identifier: string): ActorScopedRepository {\n return new ActorScopedRepository(this, identifier);\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/botkit-sqlite",
3
- "version": "0.5.0-dev.210",
3
+ "version": "0.5.0-dev.225",
4
4
  "description": "SQLite-based repository for BotKit",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": {
@@ -43,7 +43,7 @@
43
43
  "README.md"
44
44
  ],
45
45
  "peerDependencies": {
46
- "@fedify/botkit": "0.5.0-dev.210+b2371c7b"
46
+ "@fedify/botkit": "^0.5.0-dev.225+cfc4181c"
47
47
  },
48
48
  "dependencies": {
49
49
  "@fedify/fedify": "~2.1.15",