@fedify/botkit-redis 0.5.0-dev.0 → 0.5.0-dev.329
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/README.md +9 -0
- package/dist/mod.d.ts +39 -1
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +209 -79
- package/dist/mod.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -84,6 +84,15 @@ The `RedisRepository` constructor accepts the following options:
|
|
|
84
84
|
- **`clientOptions`** (optional): Additional node-redis client options.
|
|
85
85
|
This option is only valid together with `url`.
|
|
86
86
|
|
|
87
|
+
- **`lockTimeoutMs`** (optional): How long a Redis lock can live without
|
|
88
|
+
renewal, in milliseconds. Defaults to `30000`.
|
|
89
|
+
|
|
90
|
+
- **`lockPollIntervalMs`** (optional): How long to wait before retrying a
|
|
91
|
+
held Redis lock, in milliseconds. Defaults to `20`.
|
|
92
|
+
|
|
93
|
+
- **`lockRenewIntervalMs`** (optional): How often to renew a held Redis lock,
|
|
94
|
+
in milliseconds. Defaults to `10000`.
|
|
95
|
+
|
|
87
96
|
The options are mutually exclusive: use either `client` or `url`.
|
|
88
97
|
|
|
89
98
|
|
package/dist/mod.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ interface RedisClientLike {
|
|
|
8
8
|
connect?(): Promise<unknown>;
|
|
9
9
|
quit?(): Promise<unknown>;
|
|
10
10
|
close?(): Promise<unknown> | unknown;
|
|
11
|
+
on?(event: "error", listener: (error: unknown) => void): unknown;
|
|
11
12
|
sendCommand(args: readonly string[]): Promise<unknown>;
|
|
12
13
|
}
|
|
13
14
|
interface RedisRepositoryOptionsBase {
|
|
@@ -16,6 +17,21 @@ interface RedisRepositoryOptionsBase {
|
|
|
16
17
|
* @default `"botkit"`
|
|
17
18
|
*/
|
|
18
19
|
readonly prefix?: string;
|
|
20
|
+
/**
|
|
21
|
+
* How long a Redis lock can live without renewal, in milliseconds.
|
|
22
|
+
* @default `30000`
|
|
23
|
+
*/
|
|
24
|
+
readonly lockTimeoutMs?: number;
|
|
25
|
+
/**
|
|
26
|
+
* How long to wait before retrying a held Redis lock, in milliseconds.
|
|
27
|
+
* @default `20`
|
|
28
|
+
*/
|
|
29
|
+
readonly lockPollIntervalMs?: number;
|
|
30
|
+
/**
|
|
31
|
+
* How often to renew a held Redis lock, in milliseconds.
|
|
32
|
+
* @default `10000`
|
|
33
|
+
*/
|
|
34
|
+
readonly lockRenewIntervalMs?: number;
|
|
19
35
|
}
|
|
20
36
|
interface RedisRepositoryOptionsWithClient extends RedisRepositoryOptionsBase {
|
|
21
37
|
/**
|
|
@@ -57,7 +73,11 @@ type RedisRepositoryOptions = RedisRepositoryOptionsWithClient | RedisRepository
|
|
|
57
73
|
declare class RedisRepository implements Repository, AsyncDisposable {
|
|
58
74
|
private readonly client;
|
|
59
75
|
private readonly ownsClient;
|
|
60
|
-
private
|
|
76
|
+
private ready;
|
|
77
|
+
private readonly lockTimeoutMs;
|
|
78
|
+
private readonly lockPollIntervalMs;
|
|
79
|
+
private readonly lockRenewIntervalMs;
|
|
80
|
+
private closed;
|
|
61
81
|
/**
|
|
62
82
|
* The Redis key prefix under which all BotKit data is stored.
|
|
63
83
|
*/
|
|
@@ -65,6 +85,9 @@ declare class RedisRepository implements Repository, AsyncDisposable {
|
|
|
65
85
|
/**
|
|
66
86
|
* Creates a new Redis repository.
|
|
67
87
|
* @param options The options for creating the repository.
|
|
88
|
+
* @throws {TypeError} If exactly one of `client` and `url` is not provided.
|
|
89
|
+
* @throws {RangeError} If a lock timing option is not a positive number, or
|
|
90
|
+
* if `lockRenewIntervalMs` is not less than `lockTimeoutMs`.
|
|
68
91
|
*/
|
|
69
92
|
constructor(options: RedisRepositoryOptions);
|
|
70
93
|
[Symbol.asyncDispose](): Promise<void>;
|
|
@@ -74,8 +97,11 @@ declare class RedisRepository implements Repository, AsyncDisposable {
|
|
|
74
97
|
close(): Promise<void>;
|
|
75
98
|
private key;
|
|
76
99
|
private botKey;
|
|
100
|
+
private connect;
|
|
101
|
+
private ensureReady;
|
|
77
102
|
private command;
|
|
78
103
|
private get;
|
|
104
|
+
private mGet;
|
|
79
105
|
private set;
|
|
80
106
|
private del;
|
|
81
107
|
private sAdd;
|
|
@@ -96,6 +122,11 @@ declare class RedisRepository implements Repository, AsyncDisposable {
|
|
|
96
122
|
getMessages(identifier: string, options?: RepositoryGetMessagesOptions): AsyncIterable<Create | Announce>;
|
|
97
123
|
getMessage(identifier: string, id: Uuid): Promise<Create | Announce | undefined>;
|
|
98
124
|
countMessages(identifier: string): Promise<number>;
|
|
125
|
+
/**
|
|
126
|
+
* Adds a follower for a follow request.
|
|
127
|
+
*
|
|
128
|
+
* @throws {TypeError} If the follower actor has no ID.
|
|
129
|
+
*/
|
|
99
130
|
addFollower(identifier: string, followId: URL, follower: Actor): Promise<void>;
|
|
100
131
|
removeFollower(identifier: string, followId: URL, followerId: URL): Promise<Actor | undefined>;
|
|
101
132
|
private cleanupFollower;
|
|
@@ -109,10 +140,16 @@ declare class RedisRepository implements Repository, AsyncDisposable {
|
|
|
109
140
|
removeFollowee(identifier: string, followeeId: URL): Promise<Follow | undefined>;
|
|
110
141
|
getFollowee(identifier: string, followeeId: URL): Promise<Follow | undefined>;
|
|
111
142
|
findFollowedBots(followeeId: URL): AsyncIterable<string>;
|
|
143
|
+
/**
|
|
144
|
+
* Stores a quote authorization.
|
|
145
|
+
*
|
|
146
|
+
* @throws {TypeError} If the quote authorization has no interacting object.
|
|
147
|
+
*/
|
|
112
148
|
addQuoteAuthorization(identifier: string, id: Uuid, authorization: QuoteAuthorization): Promise<void>;
|
|
113
149
|
getQuoteAuthorization(identifier: string, id: Uuid): Promise<QuoteAuthorization | undefined>;
|
|
114
150
|
findQuoteAuthorization(identifier: string, interactingObject: URL): Promise<QuoteAuthorization | undefined>;
|
|
115
151
|
removeQuoteAuthorization(identifier: string, id: Uuid): Promise<QuoteAuthorization | undefined>;
|
|
152
|
+
private findStoredQuoteAuthorizationInteractingObject;
|
|
116
153
|
addQuoteAuthorizationReference(identifier: string, authorization: URL, messageId: Uuid, attribution?: URL): Promise<void>;
|
|
117
154
|
findQuoteAuthorizationReference(identifier: string, authorization: URL): Promise<Uuid | undefined>;
|
|
118
155
|
findQuoteAuthorizationReferenceIdentifiers(authorization: URL): AsyncIterable<string>;
|
|
@@ -123,6 +160,7 @@ declare class RedisRepository implements Repository, AsyncDisposable {
|
|
|
123
160
|
countVotes(identifier: string, messageId: Uuid): Promise<Readonly<Record<string, number>>>;
|
|
124
161
|
forIdentifier(identifier: string): ActorScopedRepository;
|
|
125
162
|
private quoteAuthorizationLockKey;
|
|
163
|
+
private quoteAuthorizationReferenceLockKey;
|
|
126
164
|
}
|
|
127
165
|
//#endregion
|
|
128
166
|
export { RedisRepository, RedisRepositoryOptions };
|
package/dist/mod.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mod.d.ts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"mod.d.ts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":"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"}
|
package/dist/mod.js
CHANGED
|
@@ -6,9 +6,10 @@ import { createClient } from "redis";
|
|
|
6
6
|
|
|
7
7
|
//#region src/mod.ts
|
|
8
8
|
const logger = getLogger(["botkit", "redis"]);
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
9
|
+
const defaultRedisLockTimeoutMs = 3e4;
|
|
10
|
+
const defaultRedisLockPollIntervalMs = 20;
|
|
11
|
+
const defaultRedisLockRenewIntervalMs = 1e4;
|
|
12
|
+
const redisMGetBatchSize = 100;
|
|
12
13
|
const uuidV7Pattern = /^([0-9a-f]{8})-([0-9a-f]{4})-7[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13
14
|
/**
|
|
14
15
|
* A repository for storing bot data using Redis.
|
|
@@ -18,6 +19,10 @@ var RedisRepository = class {
|
|
|
18
19
|
client;
|
|
19
20
|
ownsClient;
|
|
20
21
|
ready;
|
|
22
|
+
lockTimeoutMs;
|
|
23
|
+
lockPollIntervalMs;
|
|
24
|
+
lockRenewIntervalMs;
|
|
25
|
+
closed = false;
|
|
21
26
|
/**
|
|
22
27
|
* The Redis key prefix under which all BotKit data is stored.
|
|
23
28
|
*/
|
|
@@ -25,16 +30,26 @@ var RedisRepository = class {
|
|
|
25
30
|
/**
|
|
26
31
|
* Creates a new Redis repository.
|
|
27
32
|
* @param options The options for creating the repository.
|
|
33
|
+
* @throws {TypeError} If exactly one of `client` and `url` is not provided.
|
|
34
|
+
* @throws {RangeError} If a lock timing option is not a positive number, or
|
|
35
|
+
* if `lockRenewIntervalMs` is not less than `lockTimeoutMs`.
|
|
28
36
|
*/
|
|
29
37
|
constructor(options) {
|
|
38
|
+
if (options == null) throw new TypeError("RedisRepositoryOptions must be provided.");
|
|
30
39
|
const hasClient = "client" in options && options.client != null;
|
|
31
40
|
const hasUrl = "url" in options && options.url != null;
|
|
32
41
|
if (hasClient === hasUrl) throw new TypeError("RedisRepositoryOptions must provide exactly one of client or url.");
|
|
33
42
|
this.prefix = options.prefix ?? "botkit";
|
|
43
|
+
this.lockTimeoutMs = options.lockTimeoutMs ?? defaultRedisLockTimeoutMs;
|
|
44
|
+
this.lockPollIntervalMs = options.lockPollIntervalMs ?? defaultRedisLockPollIntervalMs;
|
|
45
|
+
this.lockRenewIntervalMs = options.lockRenewIntervalMs ?? defaultRedisLockRenewIntervalMs;
|
|
46
|
+
validatePositiveMilliseconds("lockTimeoutMs", this.lockTimeoutMs);
|
|
47
|
+
validatePositiveMilliseconds("lockPollIntervalMs", this.lockPollIntervalMs);
|
|
48
|
+
validatePositiveMilliseconds("lockRenewIntervalMs", this.lockRenewIntervalMs);
|
|
49
|
+
if (this.lockRenewIntervalMs >= this.lockTimeoutMs) throw new RangeError("lockRenewIntervalMs must be less than lockTimeoutMs.");
|
|
34
50
|
if (hasClient) {
|
|
35
51
|
this.client = options.client;
|
|
36
52
|
this.ownsClient = false;
|
|
37
|
-
this.ready = Promise.resolve();
|
|
38
53
|
} else {
|
|
39
54
|
const client = createClient({
|
|
40
55
|
...options.clientOptions,
|
|
@@ -42,7 +57,13 @@ var RedisRepository = class {
|
|
|
42
57
|
});
|
|
43
58
|
this.client = client;
|
|
44
59
|
this.ownsClient = true;
|
|
45
|
-
|
|
60
|
+
client.on?.("error", (error) => {
|
|
61
|
+
logger.warn("Owned Redis client emitted an error: {error}", { error });
|
|
62
|
+
});
|
|
63
|
+
const ready = this.connect();
|
|
64
|
+
ready.catch(() => {
|
|
65
|
+
if (this.ready === ready) this.ready = void 0;
|
|
66
|
+
});
|
|
46
67
|
}
|
|
47
68
|
}
|
|
48
69
|
async [Symbol.asyncDispose]() {
|
|
@@ -52,10 +73,22 @@ var RedisRepository = class {
|
|
|
52
73
|
* Closes the owned Redis connection.
|
|
53
74
|
*/
|
|
54
75
|
async close() {
|
|
55
|
-
|
|
76
|
+
if (this.closed) return;
|
|
77
|
+
this.closed = true;
|
|
56
78
|
if (!this.ownsClient) return;
|
|
57
|
-
|
|
58
|
-
|
|
79
|
+
const ready = this.ready;
|
|
80
|
+
try {
|
|
81
|
+
await ready;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logger.warn("Owned Redis client did not become ready before close: {error}", { error });
|
|
84
|
+
if (this.ready === ready) this.ready = void 0;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
if (this.client.quit != null) await this.client.quit();
|
|
88
|
+
else await this.client.close?.();
|
|
89
|
+
} catch (error) {
|
|
90
|
+
logger.warn("Failed to close owned Redis client: {error}", { error });
|
|
91
|
+
}
|
|
59
92
|
}
|
|
60
93
|
key(...segments) {
|
|
61
94
|
return [this.prefix, ...segments.map((segment) => encodeURIComponent(segment))].join(":");
|
|
@@ -63,8 +96,35 @@ var RedisRepository = class {
|
|
|
63
96
|
botKey(identifier, ...segments) {
|
|
64
97
|
return this.key("bots", identifier, ...segments);
|
|
65
98
|
}
|
|
99
|
+
connect() {
|
|
100
|
+
const ready = this.client.connect?.() ?? Promise.resolve();
|
|
101
|
+
this.ready = ready;
|
|
102
|
+
return ready;
|
|
103
|
+
}
|
|
104
|
+
async ensureReady() {
|
|
105
|
+
if (this.closed) throw new TypeError("RedisRepository is closed.");
|
|
106
|
+
if (!this.ownsClient) return;
|
|
107
|
+
const ready = this.ready;
|
|
108
|
+
if (ready == null) {
|
|
109
|
+
if (this.client.isOpen) return;
|
|
110
|
+
const nextReady = this.connect();
|
|
111
|
+
try {
|
|
112
|
+
await nextReady;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
if (this.ready === nextReady) this.ready = void 0;
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
await ready;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (this.ready === ready) this.ready = void 0;
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
66
126
|
async command(args) {
|
|
67
|
-
await this.
|
|
127
|
+
await this.ensureReady();
|
|
68
128
|
try {
|
|
69
129
|
return await this.client.sendCommand(args);
|
|
70
130
|
} catch (error) {
|
|
@@ -79,6 +139,13 @@ var RedisRepository = class {
|
|
|
79
139
|
const value = await this.command(["GET", key]);
|
|
80
140
|
return typeof value === "string" ? value : void 0;
|
|
81
141
|
}
|
|
142
|
+
async mGet(keys) {
|
|
143
|
+
if (keys.length < 1) return [];
|
|
144
|
+
const chunks = [];
|
|
145
|
+
for (let i = 0; i < keys.length; i += redisMGetBatchSize) chunks.push(keys.slice(i, i + redisMGetBatchSize));
|
|
146
|
+
const replies = await Promise.all(chunks.map((chunk) => this.command(["MGET", ...chunk])));
|
|
147
|
+
return replies.flatMap(toStringArray);
|
|
148
|
+
}
|
|
82
149
|
async set(key, value) {
|
|
83
150
|
await this.command([
|
|
84
151
|
"SET",
|
|
@@ -156,24 +223,30 @@ var RedisRepository = class {
|
|
|
156
223
|
token,
|
|
157
224
|
"NX",
|
|
158
225
|
"PX",
|
|
159
|
-
|
|
160
|
-
]) !== "OK") await delay(
|
|
226
|
+
this.lockTimeoutMs.toString()
|
|
227
|
+
]) !== "OK") await delay(this.lockPollIntervalMs);
|
|
161
228
|
const renew = setInterval(() => {
|
|
162
229
|
(async () => {
|
|
163
230
|
try {
|
|
164
|
-
|
|
231
|
+
if (this.closed) {
|
|
232
|
+
clearInterval(renew);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const renewed = toNumber(await this.command([
|
|
165
236
|
"EVAL",
|
|
166
237
|
"if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('PEXPIRE', KEYS[1], ARGV[2]) else return 0 end",
|
|
167
238
|
"1",
|
|
168
239
|
lockKey,
|
|
169
240
|
token,
|
|
170
|
-
|
|
171
|
-
]);
|
|
241
|
+
this.lockTimeoutMs.toString()
|
|
242
|
+
]));
|
|
243
|
+
if (renewed === 0) clearInterval(renew);
|
|
172
244
|
} catch (error) {
|
|
173
|
-
|
|
245
|
+
if (this.closed) clearInterval(renew);
|
|
246
|
+
else logger.warn("Failed to renew Redis lock: {error}", { error });
|
|
174
247
|
}
|
|
175
248
|
})();
|
|
176
|
-
},
|
|
249
|
+
}, this.lockRenewIntervalMs);
|
|
177
250
|
try {
|
|
178
251
|
return await operation();
|
|
179
252
|
} finally {
|
|
@@ -192,11 +265,10 @@ var RedisRepository = class {
|
|
|
192
265
|
}
|
|
193
266
|
}
|
|
194
267
|
async setKeyPairs(identifier, keyPairs) {
|
|
195
|
-
const pairs =
|
|
196
|
-
for (const keyPair of keyPairs) pairs.push({
|
|
268
|
+
const pairs = await Promise.all(keyPairs.map(async (keyPair) => ({
|
|
197
269
|
private: await exportJwk(keyPair.privateKey),
|
|
198
270
|
public: await exportJwk(keyPair.publicKey)
|
|
199
|
-
});
|
|
271
|
+
})));
|
|
200
272
|
await this.set(this.botKey(identifier, "keyPairs"), JSON.stringify(pairs));
|
|
201
273
|
}
|
|
202
274
|
async getKeyPairs(identifier) {
|
|
@@ -209,15 +281,22 @@ var RedisRepository = class {
|
|
|
209
281
|
})));
|
|
210
282
|
}
|
|
211
283
|
async addMessage(identifier, id, activity) {
|
|
212
|
-
await this.
|
|
213
|
-
|
|
284
|
+
await this.withRedisLock(this.botKey(identifier, "messages", id, "lock"), async () => {
|
|
285
|
+
await this.set(this.botKey(identifier, "messages", id), JSON.stringify(await activity.toJsonLd({ format: "compact" })));
|
|
286
|
+
await this.zAdd(this.botKey(identifier, "messages"), getMessageScore(id, activity), id);
|
|
287
|
+
});
|
|
214
288
|
}
|
|
215
289
|
async updateMessage(identifier, id, updater) {
|
|
216
290
|
const key = this.botKey(identifier, "messages", id);
|
|
217
291
|
return await this.withRedisLock(this.botKey(identifier, "messages", id, "lock"), async () => {
|
|
218
292
|
const json = await this.get(key);
|
|
219
293
|
if (json == null) return false;
|
|
220
|
-
|
|
294
|
+
let activity;
|
|
295
|
+
try {
|
|
296
|
+
activity = await Activity.fromJsonLd(JSON.parse(json));
|
|
297
|
+
} catch {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
221
300
|
if (!(activity instanceof Create || activity instanceof Announce)) return false;
|
|
222
301
|
const updated = await updater(activity);
|
|
223
302
|
if (updated == null) return false;
|
|
@@ -228,27 +307,32 @@ var RedisRepository = class {
|
|
|
228
307
|
}
|
|
229
308
|
async removeMessage(identifier, id) {
|
|
230
309
|
const key = this.botKey(identifier, "messages", id);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
310
|
+
return await this.withRedisLock(this.botKey(identifier, "messages", id, "lock"), async () => {
|
|
311
|
+
const json = await this.get(key);
|
|
312
|
+
await this.del(key);
|
|
313
|
+
await this.zRem(this.botKey(identifier, "messages"), id);
|
|
314
|
+
if (json == null) return void 0;
|
|
315
|
+
try {
|
|
316
|
+
const activity = await Activity.fromJsonLd(JSON.parse(json));
|
|
317
|
+
if (activity instanceof Create || activity instanceof Announce) return activity;
|
|
318
|
+
} catch {
|
|
319
|
+
return void 0;
|
|
320
|
+
}
|
|
321
|
+
return void 0;
|
|
322
|
+
});
|
|
238
323
|
}
|
|
239
324
|
async *getMessages(identifier, options = {}) {
|
|
240
325
|
const min = options.since == null ? "-inf" : options.since.epochMilliseconds.toString();
|
|
241
326
|
const max = options.until == null ? "+inf" : options.until.epochMilliseconds.toString();
|
|
242
327
|
const ids = await this.zRangeByScore(this.botKey(identifier, "messages"), min, max, options.order == null || options.order === "newest", options.limit);
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
328
|
+
if (ids.length < 1) return;
|
|
329
|
+
const keys = ids.map((id) => this.botKey(identifier, "messages", id));
|
|
330
|
+
const jsons = await this.mGet(keys);
|
|
331
|
+
for (const json of jsons) try {
|
|
332
|
+
const activity = await Activity.fromJsonLd(JSON.parse(json));
|
|
333
|
+
if (activity instanceof Create || activity instanceof Announce) yield activity;
|
|
334
|
+
} catch {
|
|
335
|
+
continue;
|
|
252
336
|
}
|
|
253
337
|
}
|
|
254
338
|
async getMessage(identifier, id) {
|
|
@@ -265,6 +349,11 @@ var RedisRepository = class {
|
|
|
265
349
|
countMessages(identifier) {
|
|
266
350
|
return this.zCard(this.botKey(identifier, "messages"));
|
|
267
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* Adds a follower for a follow request.
|
|
354
|
+
*
|
|
355
|
+
* @throws {TypeError} If the follower actor has no ID.
|
|
356
|
+
*/
|
|
268
357
|
async addFollower(identifier, followId, follower) {
|
|
269
358
|
if (follower.id == null) throw new TypeError("The follower ID is missing.");
|
|
270
359
|
const followerId = follower.id.href;
|
|
@@ -287,10 +376,10 @@ var RedisRepository = class {
|
|
|
287
376
|
const requestKey = this.botKey(identifier, "followRequests", followIdString);
|
|
288
377
|
return await this.withRedisLock(this.botKey(identifier, "followers", "lock"), async () => {
|
|
289
378
|
const currentFollowerId = await this.get(requestKey);
|
|
290
|
-
if (currentFollowerId !== followerId.href) return void 0;
|
|
379
|
+
if (currentFollowerId != null && currentFollowerId !== followerId.href) return void 0;
|
|
291
380
|
const followerKey = this.botKey(identifier, "followers", followerId.href);
|
|
292
381
|
const json = await this.get(followerKey);
|
|
293
|
-
await this.del(requestKey);
|
|
382
|
+
if (currentFollowerId === followerId.href) await this.del(requestKey);
|
|
294
383
|
await this.sRem(this.botKey(identifier, "followerRequests", followerId.href), followIdString);
|
|
295
384
|
const removed = await this.cleanupFollower(identifier, followerId.href);
|
|
296
385
|
if (!removed || json == null) return void 0;
|
|
@@ -314,6 +403,7 @@ var RedisRepository = class {
|
|
|
314
403
|
return await this.get(this.botKey(identifier, "followers", followerId.href)) != null;
|
|
315
404
|
}
|
|
316
405
|
async *getFollowers(identifier, options = {}) {
|
|
406
|
+
if (options.limit === 0) return;
|
|
317
407
|
const start = options.offset ?? 0;
|
|
318
408
|
const stop = options.limit == null ? -1 : start + options.limit - 1;
|
|
319
409
|
const ids = toStringArray(await this.command([
|
|
@@ -322,15 +412,14 @@ var RedisRepository = class {
|
|
|
322
412
|
start.toString(),
|
|
323
413
|
stop.toString()
|
|
324
414
|
]));
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
415
|
+
if (ids.length < 1) return;
|
|
416
|
+
const keys = ids.map((id) => this.botKey(identifier, "followers", id));
|
|
417
|
+
const jsons = await this.mGet(keys);
|
|
418
|
+
for (const json of jsons) try {
|
|
419
|
+
const actor = await Object$1.fromJsonLd(JSON.parse(json));
|
|
420
|
+
if (isActor(actor)) yield actor;
|
|
421
|
+
} catch {
|
|
422
|
+
continue;
|
|
334
423
|
}
|
|
335
424
|
}
|
|
336
425
|
countFollowers(identifier) {
|
|
@@ -355,14 +444,18 @@ var RedisRepository = class {
|
|
|
355
444
|
}
|
|
356
445
|
}
|
|
357
446
|
async addFollowee(identifier, followeeId, follow) {
|
|
358
|
-
await this.
|
|
359
|
-
|
|
447
|
+
await this.withRedisLock(this.botKey(identifier, "followees", followeeId.href, "lock"), async () => {
|
|
448
|
+
await this.set(this.botKey(identifier, "followees", followeeId.href), JSON.stringify(await follow.toJsonLd({ format: "compact" })));
|
|
449
|
+
await this.zAdd(this.key("index", "followees", followeeId.href), 0, identifier);
|
|
450
|
+
});
|
|
360
451
|
}
|
|
361
452
|
async removeFollowee(identifier, followeeId) {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
453
|
+
return await this.withRedisLock(this.botKey(identifier, "followees", followeeId.href, "lock"), async () => {
|
|
454
|
+
const follow = await this.getFollowee(identifier, followeeId);
|
|
455
|
+
await this.del(this.botKey(identifier, "followees", followeeId.href));
|
|
456
|
+
await this.zRem(this.key("index", "followees", followeeId.href), identifier);
|
|
457
|
+
return follow;
|
|
458
|
+
});
|
|
366
459
|
}
|
|
367
460
|
async getFollowee(identifier, followeeId) {
|
|
368
461
|
const json = await this.get(this.botKey(identifier, "followees", followeeId.href));
|
|
@@ -376,6 +469,11 @@ var RedisRepository = class {
|
|
|
376
469
|
async *findFollowedBots(followeeId) {
|
|
377
470
|
yield* await this.zRange(this.key("index", "followees", followeeId.href));
|
|
378
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Stores a quote authorization.
|
|
474
|
+
*
|
|
475
|
+
* @throws {TypeError} If the quote authorization has no interacting object.
|
|
476
|
+
*/
|
|
379
477
|
async addQuoteAuthorization(identifier, id, authorization) {
|
|
380
478
|
const interactingObject = authorization.interactingObjectId;
|
|
381
479
|
if (interactingObject == null) throw new TypeError("The quote authorization interacting object is missing.");
|
|
@@ -384,9 +482,10 @@ var RedisRepository = class {
|
|
|
384
482
|
const existing = await this.get(indexKey);
|
|
385
483
|
if (existing != null) {
|
|
386
484
|
if (await this.getQuoteAuthorization(identifier, existing) != null) return;
|
|
387
|
-
await this.del(indexKey);
|
|
485
|
+
await this.del(indexKey, this.botKey(identifier, "quoteAuthorizationInteractingObjects", existing));
|
|
388
486
|
}
|
|
389
487
|
await this.set(this.botKey(identifier, "quoteAuthorizations", id), JSON.stringify(await authorization.toJsonLd({ format: "compact" })));
|
|
488
|
+
await this.set(this.botKey(identifier, "quoteAuthorizationInteractingObjects", id), interactingObject.href);
|
|
390
489
|
await this.set(indexKey, id);
|
|
391
490
|
});
|
|
392
491
|
}
|
|
@@ -405,47 +504,67 @@ var RedisRepository = class {
|
|
|
405
504
|
const id = await this.get(indexKey);
|
|
406
505
|
if (id == null) return void 0;
|
|
407
506
|
const authorization = await this.getQuoteAuthorization(identifier, id);
|
|
408
|
-
if (authorization == null && await this.get(indexKey) === id) await this.del(indexKey);
|
|
507
|
+
if (authorization == null && await this.get(indexKey) === id) await this.del(indexKey, this.botKey(identifier, "quoteAuthorizationInteractingObjects", id));
|
|
409
508
|
return authorization;
|
|
410
509
|
});
|
|
411
510
|
}
|
|
412
511
|
async removeQuoteAuthorization(identifier, id) {
|
|
413
512
|
const key = this.botKey(identifier, "quoteAuthorizations", id);
|
|
414
|
-
const
|
|
415
|
-
await this.
|
|
416
|
-
if (
|
|
417
|
-
|
|
513
|
+
const interactingObjectKey = this.botKey(identifier, "quoteAuthorizationInteractingObjects", id);
|
|
514
|
+
const storedInteractingObject = await this.get(interactingObjectKey) ?? await this.findStoredQuoteAuthorizationInteractingObject(key);
|
|
515
|
+
if (storedInteractingObject == null) return void 0;
|
|
516
|
+
const indexKey = this.botKey(identifier, "quoteAuthorizationsByInteractingObject", storedInteractingObject);
|
|
517
|
+
const parsed = await this.withRedisLock(this.quoteAuthorizationLockKey(indexKey), async () => {
|
|
518
|
+
const json = await this.get(key);
|
|
519
|
+
if (json == null) {
|
|
520
|
+
await this.del(interactingObjectKey);
|
|
521
|
+
return void 0;
|
|
522
|
+
}
|
|
523
|
+
let parsed$1;
|
|
524
|
+
try {
|
|
525
|
+
parsed$1 = JSON.parse(json);
|
|
526
|
+
} catch {
|
|
527
|
+
return void 0;
|
|
528
|
+
}
|
|
529
|
+
const interactingObject = getRawQuoteAuthorizationInteractingObject(parsed$1);
|
|
530
|
+
if (interactingObject?.href !== storedInteractingObject) return void 0;
|
|
531
|
+
await this.del(key, interactingObjectKey);
|
|
532
|
+
if (await this.get(indexKey) === id) await this.del(indexKey);
|
|
533
|
+
return parsed$1;
|
|
534
|
+
});
|
|
535
|
+
if (parsed == null) return void 0;
|
|
418
536
|
try {
|
|
419
|
-
|
|
537
|
+
return await QuoteAuthorization.fromJsonLd(parsed);
|
|
420
538
|
} catch {
|
|
421
539
|
return void 0;
|
|
422
540
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (await this.get(indexKey) === id) await this.del(indexKey);
|
|
428
|
-
});
|
|
429
|
-
}
|
|
541
|
+
}
|
|
542
|
+
async findStoredQuoteAuthorizationInteractingObject(key) {
|
|
543
|
+
const json = await this.get(key);
|
|
544
|
+
if (json == null) return void 0;
|
|
430
545
|
try {
|
|
431
|
-
|
|
546
|
+
const interactingObject = getRawQuoteAuthorizationInteractingObject(JSON.parse(json));
|
|
547
|
+
return interactingObject?.href;
|
|
432
548
|
} catch {
|
|
433
549
|
return void 0;
|
|
434
550
|
}
|
|
435
551
|
}
|
|
436
552
|
async addQuoteAuthorizationReference(identifier, authorization, messageId, attribution) {
|
|
437
|
-
|
|
438
|
-
messageId
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
553
|
+
await this.withRedisLock(this.quoteAuthorizationReferenceLockKey(identifier, authorization), async () => {
|
|
554
|
+
const value = attribution == null ? { messageId } : {
|
|
555
|
+
messageId,
|
|
556
|
+
attribution: attribution.href
|
|
557
|
+
};
|
|
558
|
+
await this.set(this.botKey(identifier, "quoteAuthorizationRefs", authorization.href), JSON.stringify(value));
|
|
559
|
+
await this.zAdd(this.key("index", "quoteAuthorizationRefs", authorization.href), 0, identifier);
|
|
560
|
+
});
|
|
443
561
|
}
|
|
444
562
|
async findQuoteAuthorizationReference(identifier, authorization) {
|
|
445
563
|
const json = await this.get(this.botKey(identifier, "quoteAuthorizationRefs", authorization.href));
|
|
446
564
|
if (json == null) return void 0;
|
|
447
565
|
try {
|
|
448
566
|
const value = JSON.parse(json);
|
|
567
|
+
if (typeof value !== "object" || value == null) return void 0;
|
|
449
568
|
return value.messageId;
|
|
450
569
|
} catch {
|
|
451
570
|
return void 0;
|
|
@@ -463,6 +582,7 @@ var RedisRepository = class {
|
|
|
463
582
|
} catch {
|
|
464
583
|
return void 0;
|
|
465
584
|
}
|
|
585
|
+
if (typeof value !== "object" || value == null) return void 0;
|
|
466
586
|
if (value.attribution == null) return void 0;
|
|
467
587
|
try {
|
|
468
588
|
return new URL(value.attribution);
|
|
@@ -471,8 +591,10 @@ var RedisRepository = class {
|
|
|
471
591
|
}
|
|
472
592
|
}
|
|
473
593
|
async removeQuoteAuthorizationReference(identifier, authorization) {
|
|
474
|
-
await this.
|
|
475
|
-
|
|
594
|
+
await this.withRedisLock(this.quoteAuthorizationReferenceLockKey(identifier, authorization), async () => {
|
|
595
|
+
await this.del(this.botKey(identifier, "quoteAuthorizationRefs", authorization.href));
|
|
596
|
+
await this.zRem(this.key("index", "quoteAuthorizationRefs", authorization.href), identifier);
|
|
597
|
+
});
|
|
476
598
|
}
|
|
477
599
|
async vote(identifier, messageId, voterId, option) {
|
|
478
600
|
await this.sAdd(this.botKey(identifier, "polls", messageId, "voters"), voterId.href);
|
|
@@ -485,7 +607,9 @@ var RedisRepository = class {
|
|
|
485
607
|
async countVotes(identifier, messageId) {
|
|
486
608
|
const result = {};
|
|
487
609
|
const options = await this.zRange(this.botKey(identifier, "polls", messageId, "options"));
|
|
488
|
-
|
|
610
|
+
await Promise.all(options.map(async (option) => {
|
|
611
|
+
result[option] = await this.sCard(this.botKey(identifier, "polls", messageId, "options", option));
|
|
612
|
+
}));
|
|
489
613
|
return result;
|
|
490
614
|
}
|
|
491
615
|
forIdentifier(identifier) {
|
|
@@ -494,6 +618,9 @@ var RedisRepository = class {
|
|
|
494
618
|
quoteAuthorizationLockKey(indexKey) {
|
|
495
619
|
return `${indexKey}:lock`;
|
|
496
620
|
}
|
|
621
|
+
quoteAuthorizationReferenceLockKey(identifier, authorization) {
|
|
622
|
+
return this.botKey(identifier, "quoteAuthorizationRefs", authorization.href, "lock");
|
|
623
|
+
}
|
|
497
624
|
};
|
|
498
625
|
function toNumber(value) {
|
|
499
626
|
if (typeof value === "number") return value;
|
|
@@ -501,6 +628,9 @@ function toNumber(value) {
|
|
|
501
628
|
if (typeof value === "string") return Number.parseInt(value, 10);
|
|
502
629
|
return 0;
|
|
503
630
|
}
|
|
631
|
+
function validatePositiveMilliseconds(name, value) {
|
|
632
|
+
if (!Number.isFinite(value) || value <= 0) throw new RangeError(`${name} must be a positive number.`);
|
|
633
|
+
}
|
|
504
634
|
function toStringArray(value) {
|
|
505
635
|
if (!Array.isArray(value)) return [];
|
|
506
636
|
return value.filter((item) => typeof item === "string");
|
package/dist/mod.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mod.js","names":["options: RedisRepositoryOptions","identifier: string","args: readonly string[]","key: string","value: string","member: string","score: number","min: string","max: string","reverse: boolean","limit?: number","lockKey: string","operation: () => Promise<T>","keyPairs: CryptoKeyPair[]","pairs: KeyPair[]","id: Uuid","activity: Create | Announce","updater: (\n existing: Create | Announce,\n ) => Create | Announce | undefined | Promise<Create | Announce | undefined>","options: RepositoryGetMessagesOptions","followId: URL","follower: Actor","followerId: URL","followerId: string","options: RepositoryGetFollowersOptions","follow: Follow","followeeId: URL","authorization: QuoteAuthorization","interactingObject: URL","parsed: unknown","authorization: URL","messageId: Uuid","attribution?: URL","value: QuoteAuthorizationReferenceData","voterId: URL","option: string","result: Record<string, number>","indexKey: string","value: unknown","ms: number","uuid: string","json: unknown"],"sources":["../src/mod.ts"],"sourcesContent":["// BotKit by Fedify: A framework for creating ActivityPub bots\n// Copyright (C) 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 QuoteAuthorization,\n} from \"@fedify/vocab\";\nimport { getLogger } from \"@logtape/logtape\";\nimport { createClient, type RedisClientOptions } from \"redis\";\n\nconst logger = getLogger([\"botkit\", \"redis\"]);\nconst redisLockTimeoutMs = 30_000;\nconst redisLockPollIntervalMs = 20;\nconst redisLockRenewIntervalMs = 1_000;\nconst uuidV7Pattern =\n /^([0-9a-f]{8})-([0-9a-f]{4})-7[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\ninterface RedisClientLike {\n readonly isOpen?: boolean;\n connect?(): Promise<unknown>;\n quit?(): Promise<unknown>;\n close?(): Promise<unknown> | unknown;\n sendCommand(args: readonly string[]): Promise<unknown>;\n}\n\ninterface KeyPair {\n readonly private: JsonWebKey;\n readonly public: JsonWebKey;\n}\n\ninterface QuoteAuthorizationReferenceData {\n readonly messageId: Uuid;\n readonly attribution?: string;\n}\n\ninterface RedisRepositoryOptionsBase {\n /**\n * The Redis key prefix under which all BotKit data is stored.\n * @default `\"botkit\"`\n */\n readonly prefix?: string;\n}\n\ninterface RedisRepositoryOptionsWithClient extends RedisRepositoryOptionsBase {\n /**\n * A pre-configured Redis client to use. It must already be connected.\n */\n readonly client: RedisClientLike;\n\n /**\n * Disallowed when `client` is provided.\n */\n readonly url?: never;\n\n /**\n * Disallowed when `client` is provided.\n */\n readonly clientOptions?: never;\n}\n\ninterface RedisRepositoryOptionsWithUrl extends RedisRepositoryOptionsBase {\n /**\n * A Redis connection string to connect with.\n */\n readonly url: string | URL;\n\n /**\n * Disallowed when `url` is provided.\n */\n readonly client?: never;\n\n /**\n * Additional node-redis client options.\n */\n readonly clientOptions?: Omit<RedisClientOptions, \"url\">;\n}\n\n/**\n * Options for creating a Redis repository.\n * @since 0.5.0\n */\nexport type RedisRepositoryOptions =\n | RedisRepositoryOptionsWithClient\n | RedisRepositoryOptionsWithUrl;\n\n/**\n * A repository for storing bot data using Redis.\n * @since 0.5.0\n */\nexport class RedisRepository implements Repository, AsyncDisposable {\n private readonly client: RedisClientLike;\n private readonly ownsClient: boolean;\n private readonly ready: Promise<unknown>;\n\n /**\n * The Redis key prefix under which all BotKit data is stored.\n */\n readonly prefix: string;\n\n /**\n * Creates a new Redis repository.\n * @param options The options for creating the repository.\n */\n constructor(options: RedisRepositoryOptions) {\n const hasClient = \"client\" in options && options.client != null;\n const hasUrl = \"url\" in options && options.url != null;\n if (hasClient === hasUrl) {\n throw new TypeError(\n \"RedisRepositoryOptions must provide exactly one of client or url.\",\n );\n }\n this.prefix = options.prefix ?? \"botkit\";\n if (hasClient) {\n this.client = options.client;\n this.ownsClient = false;\n this.ready = Promise.resolve();\n } else {\n const client = createClient({\n ...options.clientOptions,\n url: options.url.toString(),\n }) as unknown as RedisClientLike;\n this.client = client;\n this.ownsClient = true;\n this.ready = client.connect?.() ?? Promise.resolve();\n }\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.close();\n }\n\n /**\n * Closes the owned Redis connection.\n */\n async close(): Promise<void> {\n await this.ready;\n if (!this.ownsClient) return;\n if (this.client.quit != null) {\n await this.client.quit();\n } else {\n await this.client.close?.();\n }\n }\n\n private key(...segments: readonly string[]): string {\n return [\n this.prefix,\n ...segments.map((segment) => encodeURIComponent(segment)),\n ].join(\":\");\n }\n\n private botKey(identifier: string, ...segments: readonly string[]): string {\n return this.key(\"bots\", identifier, ...segments);\n }\n\n private async command(args: readonly string[]): Promise<unknown> {\n await this.ready;\n try {\n return await this.client.sendCommand(args);\n } catch (error) {\n logger.error(\"Redis command failed: {command}.\", {\n command: args[0],\n error,\n });\n throw error;\n }\n }\n\n private async get(key: string): Promise<string | undefined> {\n const value = await this.command([\"GET\", key]);\n return typeof value === \"string\" ? value : undefined;\n }\n\n private async set(key: string, value: string): Promise<void> {\n await this.command([\"SET\", key, value]);\n }\n\n private async del(...keys: readonly string[]): Promise<void> {\n if (keys.length < 1) return;\n await this.command([\"DEL\", ...keys]);\n }\n\n private async sAdd(key: string, member: string): Promise<void> {\n await this.command([\"SADD\", key, member]);\n }\n\n private async sRem(key: string, member: string): Promise<void> {\n await this.command([\"SREM\", key, member]);\n }\n\n private async sCard(key: string): Promise<number> {\n return toNumber(await this.command([\"SCARD\", key]));\n }\n\n private async zAdd(\n key: string,\n score: number,\n member: string,\n nx = false,\n ): Promise<void> {\n await this.command([\n \"ZADD\",\n key,\n ...(nx ? [\"NX\"] : []),\n score.toString(),\n member,\n ]);\n }\n\n private async zRem(key: string, member: string): Promise<void> {\n await this.command([\"ZREM\", key, member]);\n }\n\n private async zRange(key: string): Promise<string[]> {\n return toStringArray(await this.command([\"ZRANGE\", key, \"0\", \"-1\"]));\n }\n\n private async zRangeByScore(\n key: string,\n min: string,\n max: string,\n reverse: boolean,\n limit?: number,\n ): Promise<string[]> {\n const command = reverse ? \"ZREVRANGEBYSCORE\" : \"ZRANGEBYSCORE\";\n const args = [command, key, reverse ? max : min, reverse ? min : max];\n if (limit != null) args.push(\"LIMIT\", \"0\", limit.toString());\n return toStringArray(await this.command(args));\n }\n\n private async zCard(key: string): Promise<number> {\n return toNumber(await this.command([\"ZCARD\", key]));\n }\n\n private async nextSequence(key: string): Promise<number> {\n return toNumber(await this.command([\"INCR\", key]));\n }\n\n private async withRedisLock<T>(\n lockKey: string,\n operation: () => Promise<T>,\n ): Promise<T> {\n const token = crypto.randomUUID();\n while (\n await this.command([\n \"SET\",\n lockKey,\n token,\n \"NX\",\n \"PX\",\n redisLockTimeoutMs.toString(),\n ]) !== \"OK\"\n ) {\n await delay(redisLockPollIntervalMs);\n }\n const renew = setInterval(() => {\n void (async () => {\n try {\n await this.command([\n \"EVAL\",\n \"if redis.call('GET', KEYS[1]) == ARGV[1] then \" +\n \"return redis.call('PEXPIRE', KEYS[1], ARGV[2]) else return 0 end\",\n \"1\",\n lockKey,\n token,\n redisLockTimeoutMs.toString(),\n ]);\n } catch (error) {\n logger.warn(\"Failed to renew Redis lock: {error}\", { error });\n }\n })();\n }, redisLockRenewIntervalMs);\n try {\n return await operation();\n } finally {\n clearInterval(renew);\n try {\n await this.command([\n \"EVAL\",\n \"if redis.call('GET', KEYS[1]) == ARGV[1] then \" +\n \"return redis.call('DEL', KEYS[1]) else return 0 end\",\n \"1\",\n lockKey,\n token,\n ]);\n } catch (error) {\n logger.warn(\"Failed to release Redis lock: {error}\", { error });\n }\n }\n }\n\n async setKeyPairs(\n identifier: string,\n keyPairs: CryptoKeyPair[],\n ): Promise<void> {\n const pairs: KeyPair[] = [];\n for (const keyPair of keyPairs) {\n pairs.push({\n private: await exportJwk(keyPair.privateKey),\n public: await exportJwk(keyPair.publicKey),\n });\n }\n await this.set(this.botKey(identifier, \"keyPairs\"), JSON.stringify(pairs));\n }\n\n async getKeyPairs(identifier: string): Promise<CryptoKeyPair[] | undefined> {\n const json = await this.get(this.botKey(identifier, \"keyPairs\"));\n if (json == null) return undefined;\n const pairs = JSON.parse(json) as KeyPair[];\n return await Promise.all(pairs.map(async (pair) => ({\n privateKey: await importJwk(pair.private, \"private\"),\n publicKey: await importJwk(pair.public, \"public\"),\n })));\n }\n\n async addMessage(\n identifier: string,\n id: Uuid,\n activity: Create | Announce,\n ): Promise<void> {\n await this.set(\n this.botKey(identifier, \"messages\", id),\n JSON.stringify(await activity.toJsonLd({ format: \"compact\" })),\n );\n await this.zAdd(\n this.botKey(identifier, \"messages\"),\n getMessageScore(id, activity),\n id,\n );\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 key = this.botKey(identifier, \"messages\", id);\n return await this.withRedisLock(\n this.botKey(identifier, \"messages\", id, \"lock\"),\n async () => {\n const json = await this.get(key);\n if (json == null) return false;\n const activity = await Activity.fromJsonLd(JSON.parse(json));\n if (!(activity instanceof Create || activity instanceof Announce)) {\n return false;\n }\n const updated = await updater(activity);\n if (updated == null) return false;\n await this.set(\n key,\n JSON.stringify(await updated.toJsonLd({ format: \"compact\" })),\n );\n await this.zAdd(\n this.botKey(identifier, \"messages\"),\n getMessageScore(id, updated),\n id,\n );\n return true;\n },\n );\n }\n\n async removeMessage(\n identifier: string,\n id: Uuid,\n ): Promise<Create | Announce | undefined> {\n const key = this.botKey(identifier, \"messages\", id);\n const json = await this.get(key);\n await this.del(key);\n await this.zRem(this.botKey(identifier, \"messages\"), id);\n if (json == null) return undefined;\n const activity = await Activity.fromJsonLd(JSON.parse(json));\n if (activity instanceof Create || activity instanceof Announce) {\n return activity;\n }\n return undefined;\n }\n\n async *getMessages(\n identifier: string,\n options: RepositoryGetMessagesOptions = {},\n ): AsyncIterable<Create | Announce> {\n const min = options.since == null\n ? \"-inf\"\n : options.since.epochMilliseconds.toString();\n const max = options.until == null\n ? \"+inf\"\n : options.until.epochMilliseconds.toString();\n const ids = await this.zRangeByScore(\n this.botKey(identifier, \"messages\"),\n min,\n max,\n options.order == null || options.order === \"newest\",\n options.limit,\n );\n for (const id of ids) {\n const json = await this.get(this.botKey(identifier, \"messages\", id));\n if (json == null) continue;\n try {\n const activity = await Activity.fromJsonLd(JSON.parse(json));\n if (activity instanceof Create || activity instanceof Announce) {\n yield activity;\n }\n } catch {\n continue;\n }\n }\n }\n\n async getMessage(\n identifier: string,\n id: Uuid,\n ): Promise<Create | Announce | undefined> {\n const json = await this.get(this.botKey(identifier, \"messages\", id));\n if (json == null) return undefined;\n try {\n const activity = await Activity.fromJsonLd(JSON.parse(json));\n if (activity instanceof Create || activity instanceof Announce) {\n return activity;\n }\n } catch {\n return undefined;\n }\n return undefined;\n }\n\n countMessages(identifier: string): Promise<number> {\n return this.zCard(this.botKey(identifier, \"messages\"));\n }\n\n async addFollower(\n identifier: string,\n followId: URL,\n follower: Actor,\n ): Promise<void> {\n if (follower.id == null) {\n throw new TypeError(\"The follower ID is missing.\");\n }\n const followerId = follower.id.href;\n const followIdString = followId.href;\n const requestKey = this.botKey(\n identifier,\n \"followRequests\",\n followIdString,\n );\n await this.withRedisLock(\n this.botKey(identifier, \"followers\", \"lock\"),\n async () => {\n const previousFollowerId = await this.get(requestKey);\n await this.set(\n this.botKey(identifier, \"followers\", followerId),\n JSON.stringify(await follower.toJsonLd({ format: \"compact\" })),\n );\n await this.zAdd(\n this.botKey(identifier, \"followers\"),\n await this.nextSequence(this.botKey(identifier, \"followers\", \"seq\")),\n followerId,\n true,\n );\n await this.sAdd(\n this.botKey(identifier, \"followerRequests\", followerId),\n followIdString,\n );\n await this.set(requestKey, followerId);\n if (previousFollowerId != null && previousFollowerId !== followerId) {\n await this.sRem(\n this.botKey(identifier, \"followerRequests\", previousFollowerId),\n followIdString,\n );\n await this.cleanupFollower(identifier, previousFollowerId);\n }\n },\n );\n }\n\n async removeFollower(\n identifier: string,\n followId: URL,\n followerId: URL,\n ): Promise<Actor | undefined> {\n const followIdString = followId.href;\n const requestKey = this.botKey(\n identifier,\n \"followRequests\",\n followIdString,\n );\n return await this.withRedisLock(\n this.botKey(identifier, \"followers\", \"lock\"),\n async () => {\n const currentFollowerId = await this.get(requestKey);\n if (currentFollowerId !== followerId.href) return undefined;\n const followerKey = this.botKey(\n identifier,\n \"followers\",\n followerId.href,\n );\n const json = await this.get(followerKey);\n await this.del(requestKey);\n await this.sRem(\n this.botKey(identifier, \"followerRequests\", followerId.href),\n followIdString,\n );\n const removed = await this.cleanupFollower(identifier, followerId.href);\n if (!removed || json == null) return undefined;\n try {\n const actor = await Object.fromJsonLd(JSON.parse(json));\n if (isActor(actor)) return actor;\n } catch {\n return undefined;\n }\n return undefined;\n },\n );\n }\n\n private async cleanupFollower(\n identifier: string,\n followerId: string,\n ): Promise<boolean> {\n const requestsKey = this.botKey(identifier, \"followerRequests\", followerId);\n if (await this.sCard(requestsKey) > 0) return false;\n await this.del(\n requestsKey,\n this.botKey(identifier, \"followers\", followerId),\n );\n await this.zRem(this.botKey(identifier, \"followers\"), followerId);\n return true;\n }\n\n async hasFollower(identifier: string, followerId: URL): Promise<boolean> {\n return await this.get(\n this.botKey(identifier, \"followers\", followerId.href),\n ) !=\n null;\n }\n\n async *getFollowers(\n identifier: string,\n options: RepositoryGetFollowersOptions = {},\n ): AsyncIterable<Actor> {\n const start = options.offset ?? 0;\n const stop = options.limit == null ? -1 : start + options.limit - 1;\n const ids = toStringArray(\n await this.command([\n \"ZRANGE\",\n this.botKey(identifier, \"followers\"),\n start.toString(),\n stop.toString(),\n ]),\n );\n for (const id of ids) {\n const json = await this.get(this.botKey(identifier, \"followers\", id));\n if (json == null) continue;\n try {\n const actor = await Object.fromJsonLd(JSON.parse(json));\n if (isActor(actor)) yield actor;\n } catch {\n continue;\n }\n }\n }\n\n countFollowers(identifier: string): Promise<number> {\n return this.zCard(this.botKey(identifier, \"followers\"));\n }\n\n async addSentFollow(\n identifier: string,\n id: Uuid,\n follow: Follow,\n ): Promise<void> {\n await this.set(\n this.botKey(identifier, \"follows\", id),\n JSON.stringify(await follow.toJsonLd({ format: \"compact\" })),\n );\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 await this.del(this.botKey(identifier, \"follows\", id));\n return follow;\n }\n\n async getSentFollow(\n identifier: string,\n id: Uuid,\n ): Promise<Follow | undefined> {\n const json = await this.get(this.botKey(identifier, \"follows\", id));\n if (json == null) return undefined;\n try {\n return await Follow.fromJsonLd(JSON.parse(json));\n } catch {\n return undefined;\n }\n }\n\n async addFollowee(\n identifier: string,\n followeeId: URL,\n follow: Follow,\n ): Promise<void> {\n await this.set(\n this.botKey(identifier, \"followees\", followeeId.href),\n JSON.stringify(await follow.toJsonLd({ format: \"compact\" })),\n );\n await this.zAdd(\n this.key(\"index\", \"followees\", followeeId.href),\n 0,\n identifier,\n );\n }\n\n async removeFollowee(\n identifier: string,\n followeeId: URL,\n ): Promise<Follow | undefined> {\n const follow = await this.getFollowee(identifier, followeeId);\n await this.del(this.botKey(identifier, \"followees\", followeeId.href));\n await this.zRem(\n this.key(\"index\", \"followees\", followeeId.href),\n identifier,\n );\n return follow;\n }\n\n async getFollowee(\n identifier: string,\n followeeId: URL,\n ): Promise<Follow | undefined> {\n const json = await this.get(\n this.botKey(identifier, \"followees\", followeeId.href),\n );\n if (json == null) return undefined;\n try {\n return await Follow.fromJsonLd(JSON.parse(json));\n } catch {\n return undefined;\n }\n }\n\n async *findFollowedBots(followeeId: URL): AsyncIterable<string> {\n yield* await this.zRange(this.key(\"index\", \"followees\", followeeId.href));\n }\n\n async addQuoteAuthorization(\n identifier: string,\n id: Uuid,\n authorization: QuoteAuthorization,\n ): Promise<void> {\n const interactingObject = authorization.interactingObjectId;\n if (interactingObject == null) {\n throw new TypeError(\n \"The quote authorization interacting object is missing.\",\n );\n }\n const indexKey = this.botKey(\n identifier,\n \"quoteAuthorizationsByInteractingObject\",\n interactingObject.href,\n );\n await this.withRedisLock(\n this.quoteAuthorizationLockKey(indexKey),\n async () => {\n const existing = await this.get(indexKey);\n if (existing != null) {\n if (\n await this.getQuoteAuthorization(identifier, existing as Uuid) !=\n null\n ) {\n return;\n }\n await this.del(indexKey);\n }\n await this.set(\n this.botKey(identifier, \"quoteAuthorizations\", id),\n JSON.stringify(await authorization.toJsonLd({ format: \"compact\" })),\n );\n await this.set(indexKey, id);\n },\n );\n }\n\n async getQuoteAuthorization(\n identifier: string,\n id: Uuid,\n ): Promise<QuoteAuthorization | undefined> {\n const json = await this.get(\n this.botKey(identifier, \"quoteAuthorizations\", id),\n );\n if (json == null) return undefined;\n try {\n return await QuoteAuthorization.fromJsonLd(JSON.parse(json));\n } catch {\n return undefined;\n }\n }\n\n async findQuoteAuthorization(\n identifier: string,\n interactingObject: URL,\n ): Promise<QuoteAuthorization | undefined> {\n const indexKey = this.botKey(\n identifier,\n \"quoteAuthorizationsByInteractingObject\",\n interactingObject.href,\n );\n return await this.withRedisLock(\n this.quoteAuthorizationLockKey(indexKey),\n async () => {\n const id = await this.get(indexKey);\n if (id == null) return undefined;\n const authorization = await this.getQuoteAuthorization(\n identifier,\n id as Uuid,\n );\n if (authorization == null && await this.get(indexKey) === id) {\n await this.del(indexKey);\n }\n return authorization;\n },\n );\n }\n\n async removeQuoteAuthorization(\n identifier: string,\n id: Uuid,\n ): Promise<QuoteAuthorization | undefined> {\n const key = this.botKey(identifier, \"quoteAuthorizations\", id);\n const json = await this.get(key);\n await this.del(key);\n if (json == null) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n return undefined;\n }\n const interactingObject = getRawQuoteAuthorizationInteractingObject(parsed);\n if (interactingObject != null) {\n const indexKey = this.botKey(\n identifier,\n \"quoteAuthorizationsByInteractingObject\",\n interactingObject.href,\n );\n await this.withRedisLock(\n this.quoteAuthorizationLockKey(indexKey),\n async () => {\n if (await this.get(indexKey) === id) await this.del(indexKey);\n },\n );\n }\n try {\n return await QuoteAuthorization.fromJsonLd(parsed);\n } catch {\n return undefined;\n }\n }\n\n async addQuoteAuthorizationReference(\n identifier: string,\n authorization: URL,\n messageId: Uuid,\n attribution?: URL,\n ): Promise<void> {\n const value: QuoteAuthorizationReferenceData = attribution == null\n ? { messageId }\n : { messageId, attribution: attribution.href };\n await this.set(\n this.botKey(identifier, \"quoteAuthorizationRefs\", authorization.href),\n JSON.stringify(value),\n );\n await this.zAdd(\n this.key(\"index\", \"quoteAuthorizationRefs\", authorization.href),\n 0,\n identifier,\n );\n }\n\n async findQuoteAuthorizationReference(\n identifier: string,\n authorization: URL,\n ): Promise<Uuid | undefined> {\n const json = await this.get(\n this.botKey(identifier, \"quoteAuthorizationRefs\", authorization.href),\n );\n if (json == null) return undefined;\n try {\n const value = JSON.parse(json) as QuoteAuthorizationReferenceData;\n return value.messageId;\n } catch {\n return undefined;\n }\n }\n\n async *findQuoteAuthorizationReferenceIdentifiers(\n authorization: URL,\n ): AsyncIterable<string> {\n yield* await this.zRange(\n this.key(\"index\", \"quoteAuthorizationRefs\", authorization.href),\n );\n }\n\n async findQuoteAuthorizationReferenceAttribution(\n identifier: string,\n authorization: URL,\n ): Promise<URL | undefined> {\n const json = await this.get(\n this.botKey(identifier, \"quoteAuthorizationRefs\", authorization.href),\n );\n if (json == null) return undefined;\n let value: QuoteAuthorizationReferenceData;\n try {\n value = JSON.parse(json) as QuoteAuthorizationReferenceData;\n } catch {\n return undefined;\n }\n if (value.attribution == null) return undefined;\n try {\n return new URL(value.attribution);\n } catch {\n return undefined;\n }\n }\n\n async removeQuoteAuthorizationReference(\n identifier: string,\n authorization: URL,\n ): Promise<void> {\n await this.del(\n this.botKey(identifier, \"quoteAuthorizationRefs\", authorization.href),\n );\n await this.zRem(\n this.key(\"index\", \"quoteAuthorizationRefs\", authorization.href),\n identifier,\n );\n }\n\n async vote(\n identifier: string,\n messageId: Uuid,\n voterId: URL,\n option: string,\n ): Promise<void> {\n await this.sAdd(\n this.botKey(identifier, \"polls\", messageId, \"voters\"),\n voterId.href,\n );\n await this.sAdd(\n this.botKey(identifier, \"polls\", messageId, \"options\", option),\n voterId.href,\n );\n await this.zAdd(\n this.botKey(identifier, \"polls\", messageId, \"options\"),\n 0,\n option,\n );\n }\n\n countVoters(identifier: string, messageId: Uuid): Promise<number> {\n return this.sCard(this.botKey(identifier, \"polls\", messageId, \"voters\"));\n }\n\n async countVotes(\n identifier: string,\n messageId: Uuid,\n ): Promise<Readonly<Record<string, number>>> {\n const result: Record<string, number> = {};\n const options = await this.zRange(\n this.botKey(identifier, \"polls\", messageId, \"options\"),\n );\n for (const option of options) {\n result[option] = await this.sCard(\n this.botKey(identifier, \"polls\", messageId, \"options\", option),\n );\n }\n return result;\n }\n\n forIdentifier(identifier: string): ActorScopedRepository {\n return new ActorScopedRepository(this, identifier);\n }\n\n private quoteAuthorizationLockKey(indexKey: string): string {\n return `${indexKey}:lock`;\n }\n}\n\nfunction toNumber(value: unknown): number {\n if (typeof value === \"number\") return value;\n if (typeof value === \"bigint\") return Number(value);\n if (typeof value === \"string\") return Number.parseInt(value, 10);\n return 0;\n}\n\nfunction toStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === \"string\");\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction getMessageScore(id: Uuid, activity: Create | Announce): number {\n if (activity.published != null) return activity.published.epochMilliseconds;\n return extractTimestamp(id);\n}\n\nfunction extractTimestamp(uuid: string): number {\n const match = uuidV7Pattern.exec(uuid);\n if (match == null) return 0;\n return Number.parseInt(`${match[1]}${match[2]}`, 16);\n}\n\nfunction getRawQuoteAuthorizationInteractingObject(\n json: unknown,\n): URL | undefined {\n if (typeof json !== \"object\" || json == null) return undefined;\n if (!(\"interactingObject\" in json)) return undefined;\n const interactingObject = json.interactingObject;\n if (typeof interactingObject !== \"string\") return undefined;\n try {\n return new URL(interactingObject);\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mod.js","names":["options: RedisRepositoryOptions","client: RedisClientLike","identifier: string","args: readonly string[]","key: string","keys: readonly string[]","chunks: string[][]","value: string","member: string","score: number","min: string","max: string","reverse: boolean","limit?: number","lockKey: string","operation: () => Promise<T>","keyPairs: CryptoKeyPair[]","pairs: KeyPair[]","id: Uuid","activity: Create | Announce","updater: (\n existing: Create | Announce,\n ) => Create | Announce | undefined | Promise<Create | Announce | undefined>","activity: Activity","options: RepositoryGetMessagesOptions","followId: URL","follower: Actor","followerId: URL","followerId: string","options: RepositoryGetFollowersOptions","follow: Follow","followeeId: URL","authorization: QuoteAuthorization","interactingObject: URL","parsed: unknown","parsed","authorization: URL","messageId: Uuid","attribution?: URL","value: QuoteAuthorizationReferenceData","voterId: URL","option: string","result: Record<string, number>","indexKey: string","value: unknown","name: string","value: number","ms: number","uuid: string","json: unknown"],"sources":["../src/mod.ts"],"sourcesContent":["// BotKit by Fedify: A framework for creating ActivityPub bots\n// Copyright (C) 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 QuoteAuthorization,\n} from \"@fedify/vocab\";\nimport { getLogger } from \"@logtape/logtape\";\nimport { createClient, type RedisClientOptions } from \"redis\";\n\nconst logger = getLogger([\"botkit\", \"redis\"]);\nconst defaultRedisLockTimeoutMs = 30_000;\nconst defaultRedisLockPollIntervalMs = 20;\nconst defaultRedisLockRenewIntervalMs = 10_000;\nconst redisMGetBatchSize = 100;\nconst uuidV7Pattern =\n /^([0-9a-f]{8})-([0-9a-f]{4})-7[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\ninterface RedisClientLike {\n readonly isOpen?: boolean;\n connect?(): Promise<unknown>;\n quit?(): Promise<unknown>;\n close?(): Promise<unknown> | unknown;\n on?(event: \"error\", listener: (error: unknown) => void): unknown;\n sendCommand(args: readonly string[]): Promise<unknown>;\n}\n\ninterface KeyPair {\n readonly private: JsonWebKey;\n readonly public: JsonWebKey;\n}\n\ninterface QuoteAuthorizationReferenceData {\n readonly messageId: Uuid;\n readonly attribution?: string;\n}\n\ninterface RedisRepositoryOptionsBase {\n /**\n * The Redis key prefix under which all BotKit data is stored.\n * @default `\"botkit\"`\n */\n readonly prefix?: string;\n\n /**\n * How long a Redis lock can live without renewal, in milliseconds.\n * @default `30000`\n */\n readonly lockTimeoutMs?: number;\n\n /**\n * How long to wait before retrying a held Redis lock, in milliseconds.\n * @default `20`\n */\n readonly lockPollIntervalMs?: number;\n\n /**\n * How often to renew a held Redis lock, in milliseconds.\n * @default `10000`\n */\n readonly lockRenewIntervalMs?: number;\n}\n\ninterface RedisRepositoryOptionsWithClient extends RedisRepositoryOptionsBase {\n /**\n * A pre-configured Redis client to use. It must already be connected.\n */\n readonly client: RedisClientLike;\n\n /**\n * Disallowed when `client` is provided.\n */\n readonly url?: never;\n\n /**\n * Disallowed when `client` is provided.\n */\n readonly clientOptions?: never;\n}\n\ninterface RedisRepositoryOptionsWithUrl extends RedisRepositoryOptionsBase {\n /**\n * A Redis connection string to connect with.\n */\n readonly url: string | URL;\n\n /**\n * Disallowed when `url` is provided.\n */\n readonly client?: never;\n\n /**\n * Additional node-redis client options.\n */\n readonly clientOptions?: Omit<RedisClientOptions, \"url\">;\n}\n\n/**\n * Options for creating a Redis repository.\n * @since 0.5.0\n */\nexport type RedisRepositoryOptions =\n | RedisRepositoryOptionsWithClient\n | RedisRepositoryOptionsWithUrl;\n\n/**\n * A repository for storing bot data using Redis.\n * @since 0.5.0\n */\nexport class RedisRepository implements Repository, AsyncDisposable {\n private readonly client: RedisClientLike;\n private readonly ownsClient: boolean;\n private ready: Promise<unknown> | undefined;\n private readonly lockTimeoutMs: number;\n private readonly lockPollIntervalMs: number;\n private readonly lockRenewIntervalMs: number;\n private closed = false;\n\n /**\n * The Redis key prefix under which all BotKit data is stored.\n */\n readonly prefix: string;\n\n /**\n * Creates a new Redis repository.\n * @param options The options for creating the repository.\n * @throws {TypeError} If exactly one of `client` and `url` is not provided.\n * @throws {RangeError} If a lock timing option is not a positive number, or\n * if `lockRenewIntervalMs` is not less than `lockTimeoutMs`.\n */\n constructor(options: RedisRepositoryOptions) {\n if (options == null) {\n throw new TypeError(\"RedisRepositoryOptions must be provided.\");\n }\n const hasClient = \"client\" in options && options.client != null;\n const hasUrl = \"url\" in options && options.url != null;\n if (hasClient === hasUrl) {\n throw new TypeError(\n \"RedisRepositoryOptions must provide exactly one of client or url.\",\n );\n }\n this.prefix = options.prefix ?? \"botkit\";\n this.lockTimeoutMs = options.lockTimeoutMs ?? defaultRedisLockTimeoutMs;\n this.lockPollIntervalMs = options.lockPollIntervalMs ??\n defaultRedisLockPollIntervalMs;\n this.lockRenewIntervalMs = options.lockRenewIntervalMs ??\n defaultRedisLockRenewIntervalMs;\n validatePositiveMilliseconds(\"lockTimeoutMs\", this.lockTimeoutMs);\n validatePositiveMilliseconds(\n \"lockPollIntervalMs\",\n this.lockPollIntervalMs,\n );\n validatePositiveMilliseconds(\n \"lockRenewIntervalMs\",\n this.lockRenewIntervalMs,\n );\n if (this.lockRenewIntervalMs >= this.lockTimeoutMs) {\n throw new RangeError(\n \"lockRenewIntervalMs must be less than lockTimeoutMs.\",\n );\n }\n if (hasClient) {\n this.client = options.client;\n this.ownsClient = false;\n } else {\n const client: RedisClientLike = createClient({\n ...options.clientOptions,\n url: options.url.toString(),\n });\n this.client = client;\n this.ownsClient = true;\n client.on?.(\"error\", (error) => {\n logger.warn(\"Owned Redis client emitted an error: {error}\", { error });\n });\n const ready = this.connect();\n ready.catch(() => {\n if (this.ready === ready) this.ready = undefined;\n });\n }\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.close();\n }\n\n /**\n * Closes the owned Redis connection.\n */\n async close(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n if (!this.ownsClient) return;\n const ready = this.ready;\n try {\n await ready;\n } catch (error) {\n logger.warn(\n \"Owned Redis client did not become ready before close: {error}\",\n {\n error,\n },\n );\n if (this.ready === ready) this.ready = undefined;\n }\n try {\n if (this.client.quit != null) {\n await this.client.quit();\n } else {\n await this.client.close?.();\n }\n } catch (error) {\n logger.warn(\"Failed to close owned Redis client: {error}\", { error });\n }\n }\n\n private key(...segments: readonly string[]): string {\n return [\n this.prefix,\n ...segments.map((segment) => encodeURIComponent(segment)),\n ].join(\":\");\n }\n\n private botKey(identifier: string, ...segments: readonly string[]): string {\n return this.key(\"bots\", identifier, ...segments);\n }\n\n private connect(): Promise<unknown> {\n const ready = this.client.connect?.() ?? Promise.resolve();\n this.ready = ready;\n return ready;\n }\n\n private async ensureReady(): Promise<void> {\n if (this.closed) {\n throw new TypeError(\"RedisRepository is closed.\");\n }\n if (!this.ownsClient) return;\n const ready = this.ready;\n if (ready == null) {\n if (this.client.isOpen) return;\n const nextReady = this.connect();\n try {\n await nextReady;\n } catch (error) {\n if (this.ready === nextReady) this.ready = undefined;\n throw error;\n }\n return;\n }\n try {\n await ready;\n } catch (error) {\n if (this.ready === ready) this.ready = undefined;\n throw error;\n }\n }\n\n private async command(args: readonly string[]): Promise<unknown> {\n await this.ensureReady();\n try {\n return await this.client.sendCommand(args);\n } catch (error) {\n logger.error(\"Redis command failed: {command}.\", {\n command: args[0],\n error,\n });\n throw error;\n }\n }\n\n private async get(key: string): Promise<string | undefined> {\n const value = await this.command([\"GET\", key]);\n return typeof value === \"string\" ? value : undefined;\n }\n\n private async mGet(keys: readonly string[]): Promise<string[]> {\n if (keys.length < 1) return [];\n const chunks: string[][] = [];\n for (let i = 0; i < keys.length; i += redisMGetBatchSize) {\n chunks.push(keys.slice(i, i + redisMGetBatchSize));\n }\n const replies = await Promise.all(\n chunks.map((chunk) => this.command([\"MGET\", ...chunk])),\n );\n return replies.flatMap(toStringArray);\n }\n\n private async set(key: string, value: string): Promise<void> {\n await this.command([\"SET\", key, value]);\n }\n\n private async del(...keys: readonly string[]): Promise<void> {\n if (keys.length < 1) return;\n await this.command([\"DEL\", ...keys]);\n }\n\n private async sAdd(key: string, member: string): Promise<void> {\n await this.command([\"SADD\", key, member]);\n }\n\n private async sRem(key: string, member: string): Promise<void> {\n await this.command([\"SREM\", key, member]);\n }\n\n private async sCard(key: string): Promise<number> {\n return toNumber(await this.command([\"SCARD\", key]));\n }\n\n private async zAdd(\n key: string,\n score: number,\n member: string,\n nx = false,\n ): Promise<void> {\n await this.command([\n \"ZADD\",\n key,\n ...(nx ? [\"NX\"] : []),\n score.toString(),\n member,\n ]);\n }\n\n private async zRem(key: string, member: string): Promise<void> {\n await this.command([\"ZREM\", key, member]);\n }\n\n private async zRange(key: string): Promise<string[]> {\n return toStringArray(await this.command([\"ZRANGE\", key, \"0\", \"-1\"]));\n }\n\n private async zRangeByScore(\n key: string,\n min: string,\n max: string,\n reverse: boolean,\n limit?: number,\n ): Promise<string[]> {\n const command = reverse ? \"ZREVRANGEBYSCORE\" : \"ZRANGEBYSCORE\";\n const args = [command, key, reverse ? max : min, reverse ? min : max];\n if (limit != null) args.push(\"LIMIT\", \"0\", limit.toString());\n return toStringArray(await this.command(args));\n }\n\n private async zCard(key: string): Promise<number> {\n return toNumber(await this.command([\"ZCARD\", key]));\n }\n\n private async nextSequence(key: string): Promise<number> {\n return toNumber(await this.command([\"INCR\", key]));\n }\n\n private async withRedisLock<T>(\n lockKey: string,\n operation: () => Promise<T>,\n ): Promise<T> {\n const token = crypto.randomUUID();\n while (\n await this.command([\n \"SET\",\n lockKey,\n token,\n \"NX\",\n \"PX\",\n this.lockTimeoutMs.toString(),\n ]) !== \"OK\"\n ) {\n await delay(this.lockPollIntervalMs);\n }\n const renew = setInterval(() => {\n void (async () => {\n try {\n if (this.closed) {\n clearInterval(renew);\n return;\n }\n const renewed = toNumber(\n await this.command([\n \"EVAL\",\n \"if redis.call('GET', KEYS[1]) == ARGV[1] then \" +\n \"return redis.call('PEXPIRE', KEYS[1], ARGV[2]) else return 0 end\",\n \"1\",\n lockKey,\n token,\n this.lockTimeoutMs.toString(),\n ]),\n );\n if (renewed === 0) clearInterval(renew);\n } catch (error) {\n if (this.closed) {\n clearInterval(renew);\n } else {\n logger.warn(\"Failed to renew Redis lock: {error}\", { error });\n }\n }\n })();\n }, this.lockRenewIntervalMs);\n try {\n return await operation();\n } finally {\n clearInterval(renew);\n try {\n await this.command([\n \"EVAL\",\n \"if redis.call('GET', KEYS[1]) == ARGV[1] then \" +\n \"return redis.call('DEL', KEYS[1]) else return 0 end\",\n \"1\",\n lockKey,\n token,\n ]);\n } catch (error) {\n logger.warn(\"Failed to release Redis lock: {error}\", { error });\n }\n }\n }\n\n async setKeyPairs(\n identifier: string,\n keyPairs: CryptoKeyPair[],\n ): Promise<void> {\n const pairs: KeyPair[] = await Promise.all(\n keyPairs.map(async (keyPair) => ({\n private: await exportJwk(keyPair.privateKey),\n public: await exportJwk(keyPair.publicKey),\n })),\n );\n await this.set(this.botKey(identifier, \"keyPairs\"), JSON.stringify(pairs));\n }\n\n async getKeyPairs(identifier: string): Promise<CryptoKeyPair[] | undefined> {\n const json = await this.get(this.botKey(identifier, \"keyPairs\"));\n if (json == null) return undefined;\n const pairs = JSON.parse(json) as KeyPair[];\n return await Promise.all(pairs.map(async (pair) => ({\n privateKey: await importJwk(pair.private, \"private\"),\n publicKey: await importJwk(pair.public, \"public\"),\n })));\n }\n\n async addMessage(\n identifier: string,\n id: Uuid,\n activity: Create | Announce,\n ): Promise<void> {\n await this.withRedisLock(\n this.botKey(identifier, \"messages\", id, \"lock\"),\n async () => {\n await this.set(\n this.botKey(identifier, \"messages\", id),\n JSON.stringify(await activity.toJsonLd({ format: \"compact\" })),\n );\n await this.zAdd(\n this.botKey(identifier, \"messages\"),\n getMessageScore(id, activity),\n id,\n );\n },\n );\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 key = this.botKey(identifier, \"messages\", id);\n return await this.withRedisLock(\n this.botKey(identifier, \"messages\", id, \"lock\"),\n async () => {\n const json = await this.get(key);\n if (json == null) return false;\n let activity: Activity;\n try {\n activity = await Activity.fromJsonLd(JSON.parse(json));\n } catch {\n return false;\n }\n if (!(activity instanceof Create || activity instanceof Announce)) {\n return false;\n }\n const updated = await updater(activity);\n if (updated == null) return false;\n await this.set(\n key,\n JSON.stringify(await updated.toJsonLd({ format: \"compact\" })),\n );\n await this.zAdd(\n this.botKey(identifier, \"messages\"),\n getMessageScore(id, updated),\n id,\n );\n return true;\n },\n );\n }\n\n async removeMessage(\n identifier: string,\n id: Uuid,\n ): Promise<Create | Announce | undefined> {\n const key = this.botKey(identifier, \"messages\", id);\n return await this.withRedisLock(\n this.botKey(identifier, \"messages\", id, \"lock\"),\n async () => {\n const json = await this.get(key);\n await this.del(key);\n await this.zRem(this.botKey(identifier, \"messages\"), id);\n if (json == null) return undefined;\n try {\n const activity = await Activity.fromJsonLd(JSON.parse(json));\n if (activity instanceof Create || activity instanceof Announce) {\n return activity;\n }\n } catch {\n return undefined;\n }\n return undefined;\n },\n );\n }\n\n async *getMessages(\n identifier: string,\n options: RepositoryGetMessagesOptions = {},\n ): AsyncIterable<Create | Announce> {\n const min = options.since == null\n ? \"-inf\"\n : options.since.epochMilliseconds.toString();\n const max = options.until == null\n ? \"+inf\"\n : options.until.epochMilliseconds.toString();\n const ids = await this.zRangeByScore(\n this.botKey(identifier, \"messages\"),\n min,\n max,\n options.order == null || options.order === \"newest\",\n options.limit,\n );\n if (ids.length < 1) return;\n const keys = ids.map((id) => this.botKey(identifier, \"messages\", id));\n const jsons = await this.mGet(keys);\n for (const json of jsons) {\n try {\n const activity = await Activity.fromJsonLd(JSON.parse(json));\n if (activity instanceof Create || activity instanceof Announce) {\n yield activity;\n }\n } catch {\n continue;\n }\n }\n }\n\n async getMessage(\n identifier: string,\n id: Uuid,\n ): Promise<Create | Announce | undefined> {\n const json = await this.get(this.botKey(identifier, \"messages\", id));\n if (json == null) return undefined;\n try {\n const activity = await Activity.fromJsonLd(JSON.parse(json));\n if (activity instanceof Create || activity instanceof Announce) {\n return activity;\n }\n } catch {\n return undefined;\n }\n return undefined;\n }\n\n countMessages(identifier: string): Promise<number> {\n return this.zCard(this.botKey(identifier, \"messages\"));\n }\n\n /**\n * Adds a follower for a follow request.\n *\n * @throws {TypeError} If the follower actor has no ID.\n */\n async addFollower(\n identifier: string,\n followId: URL,\n follower: Actor,\n ): Promise<void> {\n if (follower.id == null) {\n throw new TypeError(\"The follower ID is missing.\");\n }\n const followerId = follower.id.href;\n const followIdString = followId.href;\n const requestKey = this.botKey(\n identifier,\n \"followRequests\",\n followIdString,\n );\n await this.withRedisLock(\n this.botKey(identifier, \"followers\", \"lock\"),\n async () => {\n const previousFollowerId = await this.get(requestKey);\n await this.set(\n this.botKey(identifier, \"followers\", followerId),\n JSON.stringify(await follower.toJsonLd({ format: \"compact\" })),\n );\n await this.zAdd(\n this.botKey(identifier, \"followers\"),\n await this.nextSequence(this.botKey(identifier, \"followers\", \"seq\")),\n followerId,\n true,\n );\n await this.sAdd(\n this.botKey(identifier, \"followerRequests\", followerId),\n followIdString,\n );\n await this.set(requestKey, followerId);\n if (previousFollowerId != null && previousFollowerId !== followerId) {\n await this.sRem(\n this.botKey(identifier, \"followerRequests\", previousFollowerId),\n followIdString,\n );\n await this.cleanupFollower(identifier, previousFollowerId);\n }\n },\n );\n }\n\n async removeFollower(\n identifier: string,\n followId: URL,\n followerId: URL,\n ): Promise<Actor | undefined> {\n const followIdString = followId.href;\n const requestKey = this.botKey(\n identifier,\n \"followRequests\",\n followIdString,\n );\n return await this.withRedisLock(\n this.botKey(identifier, \"followers\", \"lock\"),\n async () => {\n const currentFollowerId = await this.get(requestKey);\n if (\n currentFollowerId != null && currentFollowerId !== followerId.href\n ) {\n return undefined;\n }\n const followerKey = this.botKey(\n identifier,\n \"followers\",\n followerId.href,\n );\n const json = await this.get(followerKey);\n if (currentFollowerId === followerId.href) await this.del(requestKey);\n await this.sRem(\n this.botKey(identifier, \"followerRequests\", followerId.href),\n followIdString,\n );\n const removed = await this.cleanupFollower(identifier, followerId.href);\n if (!removed || json == null) return undefined;\n try {\n const actor = await Object.fromJsonLd(JSON.parse(json));\n if (isActor(actor)) return actor;\n } catch {\n return undefined;\n }\n return undefined;\n },\n );\n }\n\n private async cleanupFollower(\n identifier: string,\n followerId: string,\n ): Promise<boolean> {\n const requestsKey = this.botKey(identifier, \"followerRequests\", followerId);\n if (await this.sCard(requestsKey) > 0) return false;\n await this.del(\n requestsKey,\n this.botKey(identifier, \"followers\", followerId),\n );\n await this.zRem(this.botKey(identifier, \"followers\"), followerId);\n return true;\n }\n\n async hasFollower(identifier: string, followerId: URL): Promise<boolean> {\n return await this.get(\n this.botKey(identifier, \"followers\", followerId.href),\n ) !=\n null;\n }\n\n async *getFollowers(\n identifier: string,\n options: RepositoryGetFollowersOptions = {},\n ): AsyncIterable<Actor> {\n if (options.limit === 0) return;\n const start = options.offset ?? 0;\n const stop = options.limit == null ? -1 : start + options.limit - 1;\n const ids = toStringArray(\n await this.command([\n \"ZRANGE\",\n this.botKey(identifier, \"followers\"),\n start.toString(),\n stop.toString(),\n ]),\n );\n if (ids.length < 1) return;\n const keys = ids.map((id) => this.botKey(identifier, \"followers\", id));\n const jsons = await this.mGet(keys);\n for (const json of jsons) {\n try {\n const actor = await Object.fromJsonLd(JSON.parse(json));\n if (isActor(actor)) yield actor;\n } catch {\n continue;\n }\n }\n }\n\n countFollowers(identifier: string): Promise<number> {\n return this.zCard(this.botKey(identifier, \"followers\"));\n }\n\n async addSentFollow(\n identifier: string,\n id: Uuid,\n follow: Follow,\n ): Promise<void> {\n await this.set(\n this.botKey(identifier, \"follows\", id),\n JSON.stringify(await follow.toJsonLd({ format: \"compact\" })),\n );\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 await this.del(this.botKey(identifier, \"follows\", id));\n return follow;\n }\n\n async getSentFollow(\n identifier: string,\n id: Uuid,\n ): Promise<Follow | undefined> {\n const json = await this.get(this.botKey(identifier, \"follows\", id));\n if (json == null) return undefined;\n try {\n return await Follow.fromJsonLd(JSON.parse(json));\n } catch {\n return undefined;\n }\n }\n\n async addFollowee(\n identifier: string,\n followeeId: URL,\n follow: Follow,\n ): Promise<void> {\n await this.withRedisLock(\n this.botKey(identifier, \"followees\", followeeId.href, \"lock\"),\n async () => {\n await this.set(\n this.botKey(identifier, \"followees\", followeeId.href),\n JSON.stringify(await follow.toJsonLd({ format: \"compact\" })),\n );\n await this.zAdd(\n this.key(\"index\", \"followees\", followeeId.href),\n 0,\n identifier,\n );\n },\n );\n }\n\n async removeFollowee(\n identifier: string,\n followeeId: URL,\n ): Promise<Follow | undefined> {\n return await this.withRedisLock(\n this.botKey(identifier, \"followees\", followeeId.href, \"lock\"),\n async () => {\n const follow = await this.getFollowee(identifier, followeeId);\n await this.del(this.botKey(identifier, \"followees\", followeeId.href));\n await this.zRem(\n this.key(\"index\", \"followees\", followeeId.href),\n identifier,\n );\n return follow;\n },\n );\n }\n\n async getFollowee(\n identifier: string,\n followeeId: URL,\n ): Promise<Follow | undefined> {\n const json = await this.get(\n this.botKey(identifier, \"followees\", followeeId.href),\n );\n if (json == null) return undefined;\n try {\n return await Follow.fromJsonLd(JSON.parse(json));\n } catch {\n return undefined;\n }\n }\n\n async *findFollowedBots(followeeId: URL): AsyncIterable<string> {\n yield* await this.zRange(this.key(\"index\", \"followees\", followeeId.href));\n }\n\n /**\n * Stores a quote authorization.\n *\n * @throws {TypeError} If the quote authorization has no interacting object.\n */\n async addQuoteAuthorization(\n identifier: string,\n id: Uuid,\n authorization: QuoteAuthorization,\n ): Promise<void> {\n const interactingObject = authorization.interactingObjectId;\n if (interactingObject == null) {\n throw new TypeError(\n \"The quote authorization interacting object is missing.\",\n );\n }\n const indexKey = this.botKey(\n identifier,\n \"quoteAuthorizationsByInteractingObject\",\n interactingObject.href,\n );\n await this.withRedisLock(\n this.quoteAuthorizationLockKey(indexKey),\n async () => {\n const existing = await this.get(indexKey);\n if (existing != null) {\n if (\n await this.getQuoteAuthorization(identifier, existing as Uuid) !=\n null\n ) {\n return;\n }\n await this.del(\n indexKey,\n this.botKey(\n identifier,\n \"quoteAuthorizationInteractingObjects\",\n existing,\n ),\n );\n }\n await this.set(\n this.botKey(identifier, \"quoteAuthorizations\", id),\n JSON.stringify(await authorization.toJsonLd({ format: \"compact\" })),\n );\n await this.set(\n this.botKey(identifier, \"quoteAuthorizationInteractingObjects\", id),\n interactingObject.href,\n );\n await this.set(indexKey, id);\n },\n );\n }\n\n async getQuoteAuthorization(\n identifier: string,\n id: Uuid,\n ): Promise<QuoteAuthorization | undefined> {\n const json = await this.get(\n this.botKey(identifier, \"quoteAuthorizations\", id),\n );\n if (json == null) return undefined;\n try {\n return await QuoteAuthorization.fromJsonLd(JSON.parse(json));\n } catch {\n return undefined;\n }\n }\n\n async findQuoteAuthorization(\n identifier: string,\n interactingObject: URL,\n ): Promise<QuoteAuthorization | undefined> {\n const indexKey = this.botKey(\n identifier,\n \"quoteAuthorizationsByInteractingObject\",\n interactingObject.href,\n );\n return await this.withRedisLock(\n this.quoteAuthorizationLockKey(indexKey),\n async () => {\n const id = await this.get(indexKey);\n if (id == null) return undefined;\n const authorization = await this.getQuoteAuthorization(\n identifier,\n id as Uuid,\n );\n if (authorization == null && await this.get(indexKey) === id) {\n await this.del(\n indexKey,\n this.botKey(identifier, \"quoteAuthorizationInteractingObjects\", id),\n );\n }\n return authorization;\n },\n );\n }\n\n async removeQuoteAuthorization(\n identifier: string,\n id: Uuid,\n ): Promise<QuoteAuthorization | undefined> {\n const key = this.botKey(identifier, \"quoteAuthorizations\", id);\n const interactingObjectKey = this.botKey(\n identifier,\n \"quoteAuthorizationInteractingObjects\",\n id,\n );\n const storedInteractingObject = await this.get(interactingObjectKey) ??\n await this.findStoredQuoteAuthorizationInteractingObject(key);\n if (storedInteractingObject == null) return undefined;\n const indexKey = this.botKey(\n identifier,\n \"quoteAuthorizationsByInteractingObject\",\n storedInteractingObject,\n );\n const parsed = await this.withRedisLock(\n this.quoteAuthorizationLockKey(indexKey),\n async () => {\n const json = await this.get(key);\n if (json == null) {\n await this.del(interactingObjectKey);\n return undefined;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n return undefined;\n }\n const interactingObject = getRawQuoteAuthorizationInteractingObject(\n parsed,\n );\n if (interactingObject?.href !== storedInteractingObject) {\n return undefined;\n }\n await this.del(key, interactingObjectKey);\n if (await this.get(indexKey) === id) await this.del(indexKey);\n return parsed;\n },\n );\n if (parsed == null) return undefined;\n try {\n return await QuoteAuthorization.fromJsonLd(parsed);\n } catch {\n return undefined;\n }\n }\n\n private async findStoredQuoteAuthorizationInteractingObject(\n key: string,\n ): Promise<string | undefined> {\n const json = await this.get(key);\n if (json == null) return undefined;\n try {\n const interactingObject = getRawQuoteAuthorizationInteractingObject(\n JSON.parse(json),\n );\n return interactingObject?.href;\n } catch {\n return undefined;\n }\n }\n\n async addQuoteAuthorizationReference(\n identifier: string,\n authorization: URL,\n messageId: Uuid,\n attribution?: URL,\n ): Promise<void> {\n await this.withRedisLock(\n this.quoteAuthorizationReferenceLockKey(identifier, authorization),\n async () => {\n const value: QuoteAuthorizationReferenceData = attribution == null\n ? { messageId }\n : { messageId, attribution: attribution.href };\n await this.set(\n this.botKey(identifier, \"quoteAuthorizationRefs\", authorization.href),\n JSON.stringify(value),\n );\n await this.zAdd(\n this.key(\"index\", \"quoteAuthorizationRefs\", authorization.href),\n 0,\n identifier,\n );\n },\n );\n }\n\n async findQuoteAuthorizationReference(\n identifier: string,\n authorization: URL,\n ): Promise<Uuid | undefined> {\n const json = await this.get(\n this.botKey(identifier, \"quoteAuthorizationRefs\", authorization.href),\n );\n if (json == null) return undefined;\n try {\n const value = JSON.parse(json) as QuoteAuthorizationReferenceData;\n if (typeof value !== \"object\" || value == null) return undefined;\n return value.messageId;\n } catch {\n return undefined;\n }\n }\n\n async *findQuoteAuthorizationReferenceIdentifiers(\n authorization: URL,\n ): AsyncIterable<string> {\n yield* await this.zRange(\n this.key(\"index\", \"quoteAuthorizationRefs\", authorization.href),\n );\n }\n\n async findQuoteAuthorizationReferenceAttribution(\n identifier: string,\n authorization: URL,\n ): Promise<URL | undefined> {\n const json = await this.get(\n this.botKey(identifier, \"quoteAuthorizationRefs\", authorization.href),\n );\n if (json == null) return undefined;\n let value: QuoteAuthorizationReferenceData;\n try {\n value = JSON.parse(json) as QuoteAuthorizationReferenceData;\n } catch {\n return undefined;\n }\n if (typeof value !== \"object\" || value == null) return undefined;\n if (value.attribution == null) return undefined;\n try {\n return new URL(value.attribution);\n } catch {\n return undefined;\n }\n }\n\n async removeQuoteAuthorizationReference(\n identifier: string,\n authorization: URL,\n ): Promise<void> {\n await this.withRedisLock(\n this.quoteAuthorizationReferenceLockKey(identifier, authorization),\n async () => {\n await this.del(\n this.botKey(identifier, \"quoteAuthorizationRefs\", authorization.href),\n );\n await this.zRem(\n this.key(\"index\", \"quoteAuthorizationRefs\", authorization.href),\n identifier,\n );\n },\n );\n }\n\n async vote(\n identifier: string,\n messageId: Uuid,\n voterId: URL,\n option: string,\n ): Promise<void> {\n await this.sAdd(\n this.botKey(identifier, \"polls\", messageId, \"voters\"),\n voterId.href,\n );\n await this.sAdd(\n this.botKey(identifier, \"polls\", messageId, \"options\", option),\n voterId.href,\n );\n await this.zAdd(\n this.botKey(identifier, \"polls\", messageId, \"options\"),\n 0,\n option,\n );\n }\n\n countVoters(identifier: string, messageId: Uuid): Promise<number> {\n return this.sCard(this.botKey(identifier, \"polls\", messageId, \"voters\"));\n }\n\n async countVotes(\n identifier: string,\n messageId: Uuid,\n ): Promise<Readonly<Record<string, number>>> {\n const result: Record<string, number> = {};\n const options = await this.zRange(\n this.botKey(identifier, \"polls\", messageId, \"options\"),\n );\n await Promise.all(options.map(async (option) => {\n result[option] = await this.sCard(\n this.botKey(identifier, \"polls\", messageId, \"options\", option),\n );\n }));\n return result;\n }\n\n forIdentifier(identifier: string): ActorScopedRepository {\n return new ActorScopedRepository(this, identifier);\n }\n\n private quoteAuthorizationLockKey(indexKey: string): string {\n return `${indexKey}:lock`;\n }\n\n private quoteAuthorizationReferenceLockKey(\n identifier: string,\n authorization: URL,\n ): string {\n return this.botKey(\n identifier,\n \"quoteAuthorizationRefs\",\n authorization.href,\n \"lock\",\n );\n }\n}\n\nfunction toNumber(value: unknown): number {\n if (typeof value === \"number\") return value;\n if (typeof value === \"bigint\") return Number(value);\n if (typeof value === \"string\") return Number.parseInt(value, 10);\n return 0;\n}\n\nfunction validatePositiveMilliseconds(name: string, value: number): void {\n if (!Number.isFinite(value) || value <= 0) {\n throw new RangeError(`${name} must be a positive number.`);\n }\n}\n\nfunction toStringArray(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((item): item is string => typeof item === \"string\");\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction getMessageScore(id: Uuid, activity: Create | Announce): number {\n if (activity.published != null) return activity.published.epochMilliseconds;\n return extractTimestamp(id);\n}\n\nfunction extractTimestamp(uuid: string): number {\n const match = uuidV7Pattern.exec(uuid);\n if (match == null) return 0;\n return Number.parseInt(`${match[1]}${match[2]}`, 16);\n}\n\nfunction getRawQuoteAuthorizationInteractingObject(\n json: unknown,\n): URL | undefined {\n if (typeof json !== \"object\" || json == null) return undefined;\n if (!(\"interactingObject\" in json)) return undefined;\n const interactingObject = json.interactingObject;\n if (typeof interactingObject !== \"string\") return undefined;\n try {\n return new URL(interactingObject);\n } catch {\n return undefined;\n }\n}\n"],"mappings":";;;;;;;AAoCA,MAAM,SAAS,UAAU,CAAC,UAAU,OAAQ,EAAC;AAC7C,MAAM,4BAA4B;AAClC,MAAM,iCAAiC;AACvC,MAAM,kCAAkC;AACxC,MAAM,qBAAqB;AAC3B,MAAM,gBACJ;;;;;AA6FF,IAAa,kBAAb,MAAoE;CAClE,AAAiB;CACjB,AAAiB;CACjB,AAAQ;CACR,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAQ,SAAS;;;;CAKjB,AAAS;;;;;;;;CAST,YAAYA,SAAiC;AAC3C,MAAI,WAAW,KACb,OAAM,IAAI,UAAU;EAEtB,MAAM,YAAY,YAAY,WAAW,QAAQ,UAAU;EAC3D,MAAM,SAAS,SAAS,WAAW,QAAQ,OAAO;AAClD,MAAI,cAAc,OAChB,OAAM,IAAI,UACR;AAGJ,OAAK,SAAS,QAAQ,UAAU;AAChC,OAAK,gBAAgB,QAAQ,iBAAiB;AAC9C,OAAK,qBAAqB,QAAQ,sBAChC;AACF,OAAK,sBAAsB,QAAQ,uBACjC;AACF,+BAA6B,iBAAiB,KAAK,cAAc;AACjE,+BACE,sBACA,KAAK,mBACN;AACD,+BACE,uBACA,KAAK,oBACN;AACD,MAAI,KAAK,uBAAuB,KAAK,cACnC,OAAM,IAAI,WACR;AAGJ,MAAI,WAAW;AACb,QAAK,SAAS,QAAQ;AACtB,QAAK,aAAa;EACnB,OAAM;GACL,MAAMC,SAA0B,aAAa;IAC3C,GAAG,QAAQ;IACX,KAAK,QAAQ,IAAI,UAAU;GAC5B,EAAC;AACF,QAAK,SAAS;AACd,QAAK,aAAa;AAClB,UAAO,KAAK,SAAS,CAAC,UAAU;AAC9B,WAAO,KAAK,gDAAgD,EAAE,MAAO,EAAC;GACvE,EAAC;GACF,MAAM,QAAQ,KAAK,SAAS;AAC5B,SAAM,MAAM,MAAM;AAChB,QAAI,KAAK,UAAU,MAAO,MAAK;GAChC,EAAC;EACH;CACF;CAED,OAAO,OAAO,gBAA+B;AAC3C,QAAM,KAAK,OAAO;CACnB;;;;CAKD,MAAM,QAAuB;AAC3B,MAAI,KAAK,OAAQ;AACjB,OAAK,SAAS;AACd,OAAK,KAAK,WAAY;EACtB,MAAM,QAAQ,KAAK;AACnB,MAAI;AACF,SAAM;EACP,SAAQ,OAAO;AACd,UAAO,KACL,iEACA,EACE,MACD,EACF;AACD,OAAI,KAAK,UAAU,MAAO,MAAK;EAChC;AACD,MAAI;AACF,OAAI,KAAK,OAAO,QAAQ,KACtB,OAAM,KAAK,OAAO,MAAM;OAExB,OAAM,KAAK,OAAO,SAAS;EAE9B,SAAQ,OAAO;AACd,UAAO,KAAK,+CAA+C,EAAE,MAAO,EAAC;EACtE;CACF;CAED,AAAQ,IAAI,GAAG,UAAqC;AAClD,SAAO,CACL,KAAK,QACL,GAAG,SAAS,IAAI,CAAC,YAAY,mBAAmB,QAAQ,CAAC,AAC1D,EAAC,KAAK,IAAI;CACZ;CAED,AAAQ,OAAOC,YAAoB,GAAG,UAAqC;AACzE,SAAO,KAAK,IAAI,QAAQ,YAAY,GAAG,SAAS;CACjD;CAED,AAAQ,UAA4B;EAClC,MAAM,QAAQ,KAAK,OAAO,WAAW,IAAI,QAAQ,SAAS;AAC1D,OAAK,QAAQ;AACb,SAAO;CACR;CAED,MAAc,cAA6B;AACzC,MAAI,KAAK,OACP,OAAM,IAAI,UAAU;AAEtB,OAAK,KAAK,WAAY;EACtB,MAAM,QAAQ,KAAK;AACnB,MAAI,SAAS,MAAM;AACjB,OAAI,KAAK,OAAO,OAAQ;GACxB,MAAM,YAAY,KAAK,SAAS;AAChC,OAAI;AACF,UAAM;GACP,SAAQ,OAAO;AACd,QAAI,KAAK,UAAU,UAAW,MAAK;AACnC,UAAM;GACP;AACD;EACD;AACD,MAAI;AACF,SAAM;EACP,SAAQ,OAAO;AACd,OAAI,KAAK,UAAU,MAAO,MAAK;AAC/B,SAAM;EACP;CACF;CAED,MAAc,QAAQC,MAA2C;AAC/D,QAAM,KAAK,aAAa;AACxB,MAAI;AACF,UAAO,MAAM,KAAK,OAAO,YAAY,KAAK;EAC3C,SAAQ,OAAO;AACd,UAAO,MAAM,oCAAoC;IAC/C,SAAS,KAAK;IACd;GACD,EAAC;AACF,SAAM;EACP;CACF;CAED,MAAc,IAAIC,KAA0C;EAC1D,MAAM,QAAQ,MAAM,KAAK,QAAQ,CAAC,OAAO,GAAI,EAAC;AAC9C,gBAAc,UAAU,WAAW;CACpC;CAED,MAAc,KAAKC,MAA4C;AAC7D,MAAI,KAAK,SAAS,EAAG,QAAO,CAAE;EAC9B,MAAMC,SAAqB,CAAE;AAC7B,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,mBACpC,QAAO,KAAK,KAAK,MAAM,GAAG,IAAI,mBAAmB,CAAC;EAEpD,MAAM,UAAU,MAAM,QAAQ,IAC5B,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,CAAC,QAAQ,GAAG,KAAM,EAAC,CAAC,CACxD;AACD,SAAO,QAAQ,QAAQ,cAAc;CACtC;CAED,MAAc,IAAIF,KAAaG,OAA8B;AAC3D,QAAM,KAAK,QAAQ;GAAC;GAAO;GAAK;EAAM,EAAC;CACxC;CAED,MAAc,IAAI,GAAG,MAAwC;AAC3D,MAAI,KAAK,SAAS,EAAG;AACrB,QAAM,KAAK,QAAQ,CAAC,OAAO,GAAG,IAAK,EAAC;CACrC;CAED,MAAc,KAAKH,KAAaI,QAA+B;AAC7D,QAAM,KAAK,QAAQ;GAAC;GAAQ;GAAK;EAAO,EAAC;CAC1C;CAED,MAAc,KAAKJ,KAAaI,QAA+B;AAC7D,QAAM,KAAK,QAAQ;GAAC;GAAQ;GAAK;EAAO,EAAC;CAC1C;CAED,MAAc,MAAMJ,KAA8B;AAChD,SAAO,SAAS,MAAM,KAAK,QAAQ,CAAC,SAAS,GAAI,EAAC,CAAC;CACpD;CAED,MAAc,KACZA,KACAK,OACAD,QACA,KAAK,OACU;AACf,QAAM,KAAK,QAAQ;GACjB;GACA;GACA,GAAI,KAAK,CAAC,IAAK,IAAG,CAAE;GACpB,MAAM,UAAU;GAChB;EACD,EAAC;CACH;CAED,MAAc,KAAKJ,KAAaI,QAA+B;AAC7D,QAAM,KAAK,QAAQ;GAAC;GAAQ;GAAK;EAAO,EAAC;CAC1C;CAED,MAAc,OAAOJ,KAAgC;AACnD,SAAO,cAAc,MAAM,KAAK,QAAQ;GAAC;GAAU;GAAK;GAAK;EAAK,EAAC,CAAC;CACrE;CAED,MAAc,cACZA,KACAM,KACAC,KACAC,SACAC,OACmB;EACnB,MAAM,UAAU,UAAU,qBAAqB;EAC/C,MAAM,OAAO;GAAC;GAAS;GAAK,UAAU,MAAM;GAAK,UAAU,MAAM;EAAI;AACrE,MAAI,SAAS,KAAM,MAAK,KAAK,SAAS,KAAK,MAAM,UAAU,CAAC;AAC5D,SAAO,cAAc,MAAM,KAAK,QAAQ,KAAK,CAAC;CAC/C;CAED,MAAc,MAAMT,KAA8B;AAChD,SAAO,SAAS,MAAM,KAAK,QAAQ,CAAC,SAAS,GAAI,EAAC,CAAC;CACpD;CAED,MAAc,aAAaA,KAA8B;AACvD,SAAO,SAAS,MAAM,KAAK,QAAQ,CAAC,QAAQ,GAAI,EAAC,CAAC;CACnD;CAED,MAAc,cACZU,SACAC,WACY;EACZ,MAAM,QAAQ,OAAO,YAAY;AACjC,SACE,MAAM,KAAK,QAAQ;GACjB;GACA;GACA;GACA;GACA;GACA,KAAK,cAAc,UAAU;EAC9B,EAAC,KAAK,KAEP,OAAM,MAAM,KAAK,mBAAmB;EAEtC,MAAM,QAAQ,YAAY,MAAM;AAC9B,GAAK,CAAC,YAAY;AAChB,QAAI;AACF,SAAI,KAAK,QAAQ;AACf,oBAAc,MAAM;AACpB;KACD;KACD,MAAM,UAAU,SACd,MAAM,KAAK,QAAQ;MACjB;MACA;MAEA;MACA;MACA;MACA,KAAK,cAAc,UAAU;KAC9B,EAAC,CACH;AACD,SAAI,YAAY,EAAG,eAAc,MAAM;IACxC,SAAQ,OAAO;AACd,SAAI,KAAK,OACP,eAAc,MAAM;SAEpB,QAAO,KAAK,uCAAuC,EAAE,MAAO,EAAC;IAEhE;GACF,IAAG;EACL,GAAE,KAAK,oBAAoB;AAC5B,MAAI;AACF,UAAO,MAAM,WAAW;EACzB,UAAS;AACR,iBAAc,MAAM;AACpB,OAAI;AACF,UAAM,KAAK,QAAQ;KACjB;KACA;KAEA;KACA;KACA;IACD,EAAC;GACH,SAAQ,OAAO;AACd,WAAO,KAAK,yCAAyC,EAAE,MAAO,EAAC;GAChE;EACF;CACF;CAED,MAAM,YACJb,YACAc,UACe;EACf,MAAMC,QAAmB,MAAM,QAAQ,IACrC,SAAS,IAAI,OAAO,aAAa;GAC/B,SAAS,MAAM,UAAU,QAAQ,WAAW;GAC5C,QAAQ,MAAM,UAAU,QAAQ,UAAU;EAC3C,GAAE,CACJ;AACD,QAAM,KAAK,IAAI,KAAK,OAAO,YAAY,WAAW,EAAE,KAAK,UAAU,MAAM,CAAC;CAC3E;CAED,MAAM,YAAYf,YAA0D;EAC1E,MAAM,OAAO,MAAM,KAAK,IAAI,KAAK,OAAO,YAAY,WAAW,CAAC;AAChE,MAAI,QAAQ,KAAM;EAClB,MAAM,QAAQ,KAAK,MAAM,KAAK;AAC9B,SAAO,MAAM,QAAQ,IAAI,MAAM,IAAI,OAAO,UAAU;GAClD,YAAY,MAAM,UAAU,KAAK,SAAS,UAAU;GACpD,WAAW,MAAM,UAAU,KAAK,QAAQ,SAAS;EAClD,GAAE,CAAC;CACL;CAED,MAAM,WACJA,YACAgB,IACAC,UACe;AACf,QAAM,KAAK,cACT,KAAK,OAAO,YAAY,YAAY,IAAI,OAAO,EAC/C,YAAY;AACV,SAAM,KAAK,IACT,KAAK,OAAO,YAAY,YAAY,GAAG,EACvC,KAAK,UAAU,MAAM,SAAS,SAAS,EAAE,QAAQ,UAAW,EAAC,CAAC,CAC/D;AACD,SAAM,KAAK,KACT,KAAK,OAAO,YAAY,WAAW,EACnC,gBAAgB,IAAI,SAAS,EAC7B,GACD;EACF,EACF;CACF;CAED,MAAM,cACJjB,YACAgB,IACAE,SAGkB;EAClB,MAAM,MAAM,KAAK,OAAO,YAAY,YAAY,GAAG;AACnD,SAAO,MAAM,KAAK,cAChB,KAAK,OAAO,YAAY,YAAY,IAAI,OAAO,EAC/C,YAAY;GACV,MAAM,OAAO,MAAM,KAAK,IAAI,IAAI;AAChC,OAAI,QAAQ,KAAM,QAAO;GACzB,IAAIC;AACJ,OAAI;AACF,eAAW,MAAM,SAAS,WAAW,KAAK,MAAM,KAAK,CAAC;GACvD,QAAO;AACN,WAAO;GACR;AACD,SAAM,oBAAoB,UAAU,oBAAoB,UACtD,QAAO;GAET,MAAM,UAAU,MAAM,QAAQ,SAAS;AACvC,OAAI,WAAW,KAAM,QAAO;AAC5B,SAAM,KAAK,IACT,KACA,KAAK,UAAU,MAAM,QAAQ,SAAS,EAAE,QAAQ,UAAW,EAAC,CAAC,CAC9D;AACD,SAAM,KAAK,KACT,KAAK,OAAO,YAAY,WAAW,EACnC,gBAAgB,IAAI,QAAQ,EAC5B,GACD;AACD,UAAO;EACR,EACF;CACF;CAED,MAAM,cACJnB,YACAgB,IACwC;EACxC,MAAM,MAAM,KAAK,OAAO,YAAY,YAAY,GAAG;AACnD,SAAO,MAAM,KAAK,cAChB,KAAK,OAAO,YAAY,YAAY,IAAI,OAAO,EAC/C,YAAY;GACV,MAAM,OAAO,MAAM,KAAK,IAAI,IAAI;AAChC,SAAM,KAAK,IAAI,IAAI;AACnB,SAAM,KAAK,KAAK,KAAK,OAAO,YAAY,WAAW,EAAE,GAAG;AACxD,OAAI,QAAQ,KAAM;AAClB,OAAI;IACF,MAAM,WAAW,MAAM,SAAS,WAAW,KAAK,MAAM,KAAK,CAAC;AAC5D,QAAI,oBAAoB,UAAU,oBAAoB,SACpD,QAAO;GAEV,QAAO;AACN;GACD;AACD;EACD,EACF;CACF;CAED,OAAO,YACLhB,YACAoB,UAAwC,CAAE,GACR;EAClC,MAAM,MAAM,QAAQ,SAAS,OACzB,SACA,QAAQ,MAAM,kBAAkB,UAAU;EAC9C,MAAM,MAAM,QAAQ,SAAS,OACzB,SACA,QAAQ,MAAM,kBAAkB,UAAU;EAC9C,MAAM,MAAM,MAAM,KAAK,cACrB,KAAK,OAAO,YAAY,WAAW,EACnC,KACA,KACA,QAAQ,SAAS,QAAQ,QAAQ,UAAU,UAC3C,QAAQ,MACT;AACD,MAAI,IAAI,SAAS,EAAG;EACpB,MAAM,OAAO,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,YAAY,YAAY,GAAG,CAAC;EACrE,MAAM,QAAQ,MAAM,KAAK,KAAK,KAAK;AACnC,OAAK,MAAM,QAAQ,MACjB,KAAI;GACF,MAAM,WAAW,MAAM,SAAS,WAAW,KAAK,MAAM,KAAK,CAAC;AAC5D,OAAI,oBAAoB,UAAU,oBAAoB,SACpD,OAAM;EAET,QAAO;AACN;EACD;CAEJ;CAED,MAAM,WACJpB,YACAgB,IACwC;EACxC,MAAM,OAAO,MAAM,KAAK,IAAI,KAAK,OAAO,YAAY,YAAY,GAAG,CAAC;AACpE,MAAI,QAAQ,KAAM;AAClB,MAAI;GACF,MAAM,WAAW,MAAM,SAAS,WAAW,KAAK,MAAM,KAAK,CAAC;AAC5D,OAAI,oBAAoB,UAAU,oBAAoB,SACpD,QAAO;EAEV,QAAO;AACN;EACD;AACD;CACD;CAED,cAAchB,YAAqC;AACjD,SAAO,KAAK,MAAM,KAAK,OAAO,YAAY,WAAW,CAAC;CACvD;;;;;;CAOD,MAAM,YACJA,YACAqB,UACAC,UACe;AACf,MAAI,SAAS,MAAM,KACjB,OAAM,IAAI,UAAU;EAEtB,MAAM,aAAa,SAAS,GAAG;EAC/B,MAAM,iBAAiB,SAAS;EAChC,MAAM,aAAa,KAAK,OACtB,YACA,kBACA,eACD;AACD,QAAM,KAAK,cACT,KAAK,OAAO,YAAY,aAAa,OAAO,EAC5C,YAAY;GACV,MAAM,qBAAqB,MAAM,KAAK,IAAI,WAAW;AACrD,SAAM,KAAK,IACT,KAAK,OAAO,YAAY,aAAa,WAAW,EAChD,KAAK,UAAU,MAAM,SAAS,SAAS,EAAE,QAAQ,UAAW,EAAC,CAAC,CAC/D;AACD,SAAM,KAAK,KACT,KAAK,OAAO,YAAY,YAAY,EACpC,MAAM,KAAK,aAAa,KAAK,OAAO,YAAY,aAAa,MAAM,CAAC,EACpE,YACA,KACD;AACD,SAAM,KAAK,KACT,KAAK,OAAO,YAAY,oBAAoB,WAAW,EACvD,eACD;AACD,SAAM,KAAK,IAAI,YAAY,WAAW;AACtC,OAAI,sBAAsB,QAAQ,uBAAuB,YAAY;AACnE,UAAM,KAAK,KACT,KAAK,OAAO,YAAY,oBAAoB,mBAAmB,EAC/D,eACD;AACD,UAAM,KAAK,gBAAgB,YAAY,mBAAmB;GAC3D;EACF,EACF;CACF;CAED,MAAM,eACJtB,YACAqB,UACAE,YAC4B;EAC5B,MAAM,iBAAiB,SAAS;EAChC,MAAM,aAAa,KAAK,OACtB,YACA,kBACA,eACD;AACD,SAAO,MAAM,KAAK,cAChB,KAAK,OAAO,YAAY,aAAa,OAAO,EAC5C,YAAY;GACV,MAAM,oBAAoB,MAAM,KAAK,IAAI,WAAW;AACpD,OACE,qBAAqB,QAAQ,sBAAsB,WAAW,KAE9D;GAEF,MAAM,cAAc,KAAK,OACvB,YACA,aACA,WAAW,KACZ;GACD,MAAM,OAAO,MAAM,KAAK,IAAI,YAAY;AACxC,OAAI,sBAAsB,WAAW,KAAM,OAAM,KAAK,IAAI,WAAW;AACrE,SAAM,KAAK,KACT,KAAK,OAAO,YAAY,oBAAoB,WAAW,KAAK,EAC5D,eACD;GACD,MAAM,UAAU,MAAM,KAAK,gBAAgB,YAAY,WAAW,KAAK;AACvE,QAAK,WAAW,QAAQ,KAAM;AAC9B,OAAI;IACF,MAAM,QAAQ,MAAM,SAAO,WAAW,KAAK,MAAM,KAAK,CAAC;AACvD,QAAI,QAAQ,MAAM,CAAE,QAAO;GAC5B,QAAO;AACN;GACD;AACD;EACD,EACF;CACF;CAED,MAAc,gBACZvB,YACAwB,YACkB;EAClB,MAAM,cAAc,KAAK,OAAO,YAAY,oBAAoB,WAAW;AAC3E,MAAI,MAAM,KAAK,MAAM,YAAY,GAAG,EAAG,QAAO;AAC9C,QAAM,KAAK,IACT,aACA,KAAK,OAAO,YAAY,aAAa,WAAW,CACjD;AACD,QAAM,KAAK,KAAK,KAAK,OAAO,YAAY,YAAY,EAAE,WAAW;AACjE,SAAO;CACR;CAED,MAAM,YAAYxB,YAAoBuB,YAAmC;AACvE,SAAO,MAAM,KAAK,IAChB,KAAK,OAAO,YAAY,aAAa,WAAW,KAAK,CACtD,IACC;CACH;CAED,OAAO,aACLvB,YACAyB,UAAyC,CAAE,GACrB;AACtB,MAAI,QAAQ,UAAU,EAAG;EACzB,MAAM,QAAQ,QAAQ,UAAU;EAChC,MAAM,OAAO,QAAQ,SAAS,OAAO,KAAK,QAAQ,QAAQ,QAAQ;EAClE,MAAM,MAAM,cACV,MAAM,KAAK,QAAQ;GACjB;GACA,KAAK,OAAO,YAAY,YAAY;GACpC,MAAM,UAAU;GAChB,KAAK,UAAU;EAChB,EAAC,CACH;AACD,MAAI,IAAI,SAAS,EAAG;EACpB,MAAM,OAAO,IAAI,IAAI,CAAC,OAAO,KAAK,OAAO,YAAY,aAAa,GAAG,CAAC;EACtE,MAAM,QAAQ,MAAM,KAAK,KAAK,KAAK;AACnC,OAAK,MAAM,QAAQ,MACjB,KAAI;GACF,MAAM,QAAQ,MAAM,SAAO,WAAW,KAAK,MAAM,KAAK,CAAC;AACvD,OAAI,QAAQ,MAAM,CAAE,OAAM;EAC3B,QAAO;AACN;EACD;CAEJ;CAED,eAAezB,YAAqC;AAClD,SAAO,KAAK,MAAM,KAAK,OAAO,YAAY,YAAY,CAAC;CACxD;CAED,MAAM,cACJA,YACAgB,IACAU,QACe;AACf,QAAM,KAAK,IACT,KAAK,OAAO,YAAY,WAAW,GAAG,EACtC,KAAK,UAAU,MAAM,OAAO,SAAS,EAAE,QAAQ,UAAW,EAAC,CAAC,CAC7D;CACF;CAED,MAAM,iBACJ1B,YACAgB,IAC6B;EAC7B,MAAM,SAAS,MAAM,KAAK,cAAc,YAAY,GAAG;AACvD,MAAI,UAAU,KAAM;AACpB,QAAM,KAAK,IAAI,KAAK,OAAO,YAAY,WAAW,GAAG,CAAC;AACtD,SAAO;CACR;CAED,MAAM,cACJhB,YACAgB,IAC6B;EAC7B,MAAM,OAAO,MAAM,KAAK,IAAI,KAAK,OAAO,YAAY,WAAW,GAAG,CAAC;AACnE,MAAI,QAAQ,KAAM;AAClB,MAAI;AACF,UAAO,MAAM,OAAO,WAAW,KAAK,MAAM,KAAK,CAAC;EACjD,QAAO;AACN;EACD;CACF;CAED,MAAM,YACJhB,YACA2B,YACAD,QACe;AACf,QAAM,KAAK,cACT,KAAK,OAAO,YAAY,aAAa,WAAW,MAAM,OAAO,EAC7D,YAAY;AACV,SAAM,KAAK,IACT,KAAK,OAAO,YAAY,aAAa,WAAW,KAAK,EACrD,KAAK,UAAU,MAAM,OAAO,SAAS,EAAE,QAAQ,UAAW,EAAC,CAAC,CAC7D;AACD,SAAM,KAAK,KACT,KAAK,IAAI,SAAS,aAAa,WAAW,KAAK,EAC/C,GACA,WACD;EACF,EACF;CACF;CAED,MAAM,eACJ1B,YACA2B,YAC6B;AAC7B,SAAO,MAAM,KAAK,cAChB,KAAK,OAAO,YAAY,aAAa,WAAW,MAAM,OAAO,EAC7D,YAAY;GACV,MAAM,SAAS,MAAM,KAAK,YAAY,YAAY,WAAW;AAC7D,SAAM,KAAK,IAAI,KAAK,OAAO,YAAY,aAAa,WAAW,KAAK,CAAC;AACrE,SAAM,KAAK,KACT,KAAK,IAAI,SAAS,aAAa,WAAW,KAAK,EAC/C,WACD;AACD,UAAO;EACR,EACF;CACF;CAED,MAAM,YACJ3B,YACA2B,YAC6B;EAC7B,MAAM,OAAO,MAAM,KAAK,IACtB,KAAK,OAAO,YAAY,aAAa,WAAW,KAAK,CACtD;AACD,MAAI,QAAQ,KAAM;AAClB,MAAI;AACF,UAAO,MAAM,OAAO,WAAW,KAAK,MAAM,KAAK,CAAC;EACjD,QAAO;AACN;EACD;CACF;CAED,OAAO,iBAAiBA,YAAwC;AAC9D,SAAO,MAAM,KAAK,OAAO,KAAK,IAAI,SAAS,aAAa,WAAW,KAAK,CAAC;CAC1E;;;;;;CAOD,MAAM,sBACJ3B,YACAgB,IACAY,eACe;EACf,MAAM,oBAAoB,cAAc;AACxC,MAAI,qBAAqB,KACvB,OAAM,IAAI,UACR;EAGJ,MAAM,WAAW,KAAK,OACpB,YACA,0CACA,kBAAkB,KACnB;AACD,QAAM,KAAK,cACT,KAAK,0BAA0B,SAAS,EACxC,YAAY;GACV,MAAM,WAAW,MAAM,KAAK,IAAI,SAAS;AACzC,OAAI,YAAY,MAAM;AACpB,QACE,MAAM,KAAK,sBAAsB,YAAY,SAAiB,IAC5D,KAEF;AAEF,UAAM,KAAK,IACT,UACA,KAAK,OACH,YACA,wCACA,SACD,CACF;GACF;AACD,SAAM,KAAK,IACT,KAAK,OAAO,YAAY,uBAAuB,GAAG,EAClD,KAAK,UAAU,MAAM,cAAc,SAAS,EAAE,QAAQ,UAAW,EAAC,CAAC,CACpE;AACD,SAAM,KAAK,IACT,KAAK,OAAO,YAAY,wCAAwC,GAAG,EACnE,kBAAkB,KACnB;AACD,SAAM,KAAK,IAAI,UAAU,GAAG;EAC7B,EACF;CACF;CAED,MAAM,sBACJ5B,YACAgB,IACyC;EACzC,MAAM,OAAO,MAAM,KAAK,IACtB,KAAK,OAAO,YAAY,uBAAuB,GAAG,CACnD;AACD,MAAI,QAAQ,KAAM;AAClB,MAAI;AACF,UAAO,MAAM,mBAAmB,WAAW,KAAK,MAAM,KAAK,CAAC;EAC7D,QAAO;AACN;EACD;CACF;CAED,MAAM,uBACJhB,YACA6B,mBACyC;EACzC,MAAM,WAAW,KAAK,OACpB,YACA,0CACA,kBAAkB,KACnB;AACD,SAAO,MAAM,KAAK,cAChB,KAAK,0BAA0B,SAAS,EACxC,YAAY;GACV,MAAM,KAAK,MAAM,KAAK,IAAI,SAAS;AACnC,OAAI,MAAM,KAAM;GAChB,MAAM,gBAAgB,MAAM,KAAK,sBAC/B,YACA,GACD;AACD,OAAI,iBAAiB,QAAQ,MAAM,KAAK,IAAI,SAAS,KAAK,GACxD,OAAM,KAAK,IACT,UACA,KAAK,OAAO,YAAY,wCAAwC,GAAG,CACpE;AAEH,UAAO;EACR,EACF;CACF;CAED,MAAM,yBACJ7B,YACAgB,IACyC;EACzC,MAAM,MAAM,KAAK,OAAO,YAAY,uBAAuB,GAAG;EAC9D,MAAM,uBAAuB,KAAK,OAChC,YACA,wCACA,GACD;EACD,MAAM,0BAA0B,MAAM,KAAK,IAAI,qBAAqB,IAClE,MAAM,KAAK,8CAA8C,IAAI;AAC/D,MAAI,2BAA2B,KAAM;EACrC,MAAM,WAAW,KAAK,OACpB,YACA,0CACA,wBACD;EACD,MAAM,SAAS,MAAM,KAAK,cACxB,KAAK,0BAA0B,SAAS,EACxC,YAAY;GACV,MAAM,OAAO,MAAM,KAAK,IAAI,IAAI;AAChC,OAAI,QAAQ,MAAM;AAChB,UAAM,KAAK,IAAI,qBAAqB;AACpC;GACD;GACD,IAAIc;AACJ,OAAI;AACF,eAAS,KAAK,MAAM,KAAK;GAC1B,QAAO;AACN;GACD;GACD,MAAM,oBAAoB,0CACxBC,SACD;AACD,OAAI,mBAAmB,SAAS,wBAC9B;AAEF,SAAM,KAAK,IAAI,KAAK,qBAAqB;AACzC,OAAI,MAAM,KAAK,IAAI,SAAS,KAAK,GAAI,OAAM,KAAK,IAAI,SAAS;AAC7D,UAAOA;EACR,EACF;AACD,MAAI,UAAU,KAAM;AACpB,MAAI;AACF,UAAO,MAAM,mBAAmB,WAAW,OAAO;EACnD,QAAO;AACN;EACD;CACF;CAED,MAAc,8CACZ7B,KAC6B;EAC7B,MAAM,OAAO,MAAM,KAAK,IAAI,IAAI;AAChC,MAAI,QAAQ,KAAM;AAClB,MAAI;GACF,MAAM,oBAAoB,0CACxB,KAAK,MAAM,KAAK,CACjB;AACD,UAAO,mBAAmB;EAC3B,QAAO;AACN;EACD;CACF;CAED,MAAM,+BACJF,YACAgC,eACAC,WACAC,aACe;AACf,QAAM,KAAK,cACT,KAAK,mCAAmC,YAAY,cAAc,EAClE,YAAY;GACV,MAAMC,QAAyC,eAAe,OAC1D,EAAE,UAAW,IACb;IAAE;IAAW,aAAa,YAAY;GAAM;AAChD,SAAM,KAAK,IACT,KAAK,OAAO,YAAY,0BAA0B,cAAc,KAAK,EACrE,KAAK,UAAU,MAAM,CACtB;AACD,SAAM,KAAK,KACT,KAAK,IAAI,SAAS,0BAA0B,cAAc,KAAK,EAC/D,GACA,WACD;EACF,EACF;CACF;CAED,MAAM,gCACJnC,YACAgC,eAC2B;EAC3B,MAAM,OAAO,MAAM,KAAK,IACtB,KAAK,OAAO,YAAY,0BAA0B,cAAc,KAAK,CACtE;AACD,MAAI,QAAQ,KAAM;AAClB,MAAI;GACF,MAAM,QAAQ,KAAK,MAAM,KAAK;AAC9B,cAAW,UAAU,YAAY,SAAS,KAAM;AAChD,UAAO,MAAM;EACd,QAAO;AACN;EACD;CACF;CAED,OAAO,2CACLA,eACuB;AACvB,SAAO,MAAM,KAAK,OAChB,KAAK,IAAI,SAAS,0BAA0B,cAAc,KAAK,CAChE;CACF;CAED,MAAM,2CACJhC,YACAgC,eAC0B;EAC1B,MAAM,OAAO,MAAM,KAAK,IACtB,KAAK,OAAO,YAAY,0BAA0B,cAAc,KAAK,CACtE;AACD,MAAI,QAAQ,KAAM;EAClB,IAAIG;AACJ,MAAI;AACF,WAAQ,KAAK,MAAM,KAAK;EACzB,QAAO;AACN;EACD;AACD,aAAW,UAAU,YAAY,SAAS,KAAM;AAChD,MAAI,MAAM,eAAe,KAAM;AAC/B,MAAI;AACF,UAAO,IAAI,IAAI,MAAM;EACtB,QAAO;AACN;EACD;CACF;CAED,MAAM,kCACJnC,YACAgC,eACe;AACf,QAAM,KAAK,cACT,KAAK,mCAAmC,YAAY,cAAc,EAClE,YAAY;AACV,SAAM,KAAK,IACT,KAAK,OAAO,YAAY,0BAA0B,cAAc,KAAK,CACtE;AACD,SAAM,KAAK,KACT,KAAK,IAAI,SAAS,0BAA0B,cAAc,KAAK,EAC/D,WACD;EACF,EACF;CACF;CAED,MAAM,KACJhC,YACAiC,WACAG,SACAC,QACe;AACf,QAAM,KAAK,KACT,KAAK,OAAO,YAAY,SAAS,WAAW,SAAS,EACrD,QAAQ,KACT;AACD,QAAM,KAAK,KACT,KAAK,OAAO,YAAY,SAAS,WAAW,WAAW,OAAO,EAC9D,QAAQ,KACT;AACD,QAAM,KAAK,KACT,KAAK,OAAO,YAAY,SAAS,WAAW,UAAU,EACtD,GACA,OACD;CACF;CAED,YAAYrC,YAAoBiC,WAAkC;AAChE,SAAO,KAAK,MAAM,KAAK,OAAO,YAAY,SAAS,WAAW,SAAS,CAAC;CACzE;CAED,MAAM,WACJjC,YACAiC,WAC2C;EAC3C,MAAMK,SAAiC,CAAE;EACzC,MAAM,UAAU,MAAM,KAAK,OACzB,KAAK,OAAO,YAAY,SAAS,WAAW,UAAU,CACvD;AACD,QAAM,QAAQ,IAAI,QAAQ,IAAI,OAAO,WAAW;AAC9C,UAAO,UAAU,MAAM,KAAK,MAC1B,KAAK,OAAO,YAAY,SAAS,WAAW,WAAW,OAAO,CAC/D;EACF,EAAC,CAAC;AACH,SAAO;CACR;CAED,cAActC,YAA2C;AACvD,SAAO,IAAI,sBAAsB,MAAM;CACxC;CAED,AAAQ,0BAA0BuC,UAA0B;AAC1D,UAAQ,EAAE,SAAS;CACpB;CAED,AAAQ,mCACNvC,YACAgC,eACQ;AACR,SAAO,KAAK,OACV,YACA,0BACA,cAAc,MACd,OACD;CACF;AACF;AAED,SAAS,SAASQ,OAAwB;AACxC,YAAW,UAAU,SAAU,QAAO;AACtC,YAAW,UAAU,SAAU,QAAO,OAAO,MAAM;AACnD,YAAW,UAAU,SAAU,QAAO,OAAO,SAAS,OAAO,GAAG;AAChE,QAAO;AACR;AAED,SAAS,6BAA6BC,MAAcC,OAAqB;AACvE,MAAK,OAAO,SAAS,MAAM,IAAI,SAAS,EACtC,OAAM,IAAI,YAAY,EAAE,KAAK;AAEhC;AAED,SAAS,cAAcF,OAA0B;AAC/C,MAAK,MAAM,QAAQ,MAAM,CAAE,QAAO,CAAE;AACpC,QAAO,MAAM,OAAO,CAAC,gBAAgC,SAAS,SAAS;AACxE;AAED,SAAS,MAAMG,IAA2B;AACxC,QAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAG;AACxD;AAED,SAAS,gBAAgB3B,IAAUC,UAAqC;AACtE,KAAI,SAAS,aAAa,KAAM,QAAO,SAAS,UAAU;AAC1D,QAAO,iBAAiB,GAAG;AAC5B;AAED,SAAS,iBAAiB2B,MAAsB;CAC9C,MAAM,QAAQ,cAAc,KAAK,KAAK;AACtC,KAAI,SAAS,KAAM,QAAO;AAC1B,QAAO,OAAO,UAAU,EAAE,MAAM,GAAG,EAAE,MAAM,GAAG,GAAG,GAAG;AACrD;AAED,SAAS,0CACPC,MACiB;AACjB,YAAW,SAAS,YAAY,QAAQ,KAAM;AAC9C,OAAM,uBAAuB,MAAO;CACpC,MAAM,oBAAoB,KAAK;AAC/B,YAAW,sBAAsB,SAAU;AAC3C,KAAI;AACF,SAAO,IAAI,IAAI;CAChB,QAAO;AACN;CACD;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/botkit-redis",
|
|
3
|
-
"version": "0.5.0-dev.
|
|
3
|
+
"version": "0.5.0-dev.329",
|
|
4
4
|
"description": "Redis-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"
|
|
46
|
+
"@fedify/botkit": "^0.5.0-dev.329+223ad49e"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@fedify/fedify": "~2.3.1",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build": "tsdown",
|
|
60
|
-
"prepublish": "tsdown",
|
|
60
|
+
"prepublish": "mise run check && tsdown",
|
|
61
61
|
"test": "tsdown && cd src/ && node --test --experimental-transform-types"
|
|
62
62
|
}
|
|
63
63
|
}
|