@seedcord/core 0.11.0 → 0.12.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/Bus-W1K-uMeO.mjs +930 -0
  2. package/dist/Bus-W1K-uMeO.mjs.map +1 -0
  3. package/dist/{CoordinatedShutdown-H5YjZn8A.d.mts → CoordinatedShutdown-BVtNNSsF.d.mts} +13 -4
  4. package/dist/{CoordinatedStartup-CGnJrqcl.mjs → CoordinatedStartup-DA3gW8op.mjs} +22 -20
  5. package/dist/CoordinatedStartup-DA3gW8op.mjs.map +1 -0
  6. package/dist/Generated-ByC53_A2.d.mts +26 -0
  7. package/dist/{HmrModuleHandler-8ZC-UuZh.mjs → HmrModuleHandler-BMD7dg8N.mjs} +2 -2
  8. package/dist/HmrModuleHandler-BMD7dg8N.mjs.map +1 -0
  9. package/dist/PluginHost-CYe-UVpp.mjs +552 -0
  10. package/dist/PluginHost-CYe-UVpp.mjs.map +1 -0
  11. package/dist/{PluginHost-DB4j8unu.d.mts → PluginHost-DWBgBS6Q.d.mts} +35 -8
  12. package/dist/{PluginLifecycle-AhQ46NIr.mjs → PluginLifecycle-DdwRzcDj.mjs} +3 -3
  13. package/dist/PluginLifecycle-DdwRzcDj.mjs.map +1 -0
  14. package/dist/{ServerHost-CtmAhEXn.mjs → ServerHost-DHg-Y2_q.mjs} +17 -6
  15. package/dist/{ServerHost-CtmAhEXn.mjs.map → ServerHost-DHg-Y2_q.mjs.map} +1 -1
  16. package/dist/{ServerHost-DYeh0KTk.d.mts → ServerHost-whpI0h4d.d.mts} +7 -6
  17. package/dist/bindViteEnv-NFzvdqlT.mjs +32 -0
  18. package/dist/{bindViteEnv-DWq5rftJ.mjs.map → bindViteEnv-NFzvdqlT.mjs.map} +1 -1
  19. package/dist/env-internal.index.mjs +1 -1
  20. package/dist/hmr.index.mjs +1 -1
  21. package/dist/{index-Du71Xx69.d.mts → index-Boo33hh3.d.mts} +10 -11
  22. package/dist/index.d.mts +108 -6
  23. package/dist/index.mjs +118 -12
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/internal.index.d.mts +25 -7
  26. package/dist/internal.index.mjs +8 -8
  27. package/dist/node-internal.index.d.mts +7 -4
  28. package/dist/node-internal.index.mjs +19 -6
  29. package/dist/node-internal.index.mjs.map +1 -1
  30. package/dist/node.index.d.mts +3 -3
  31. package/dist/node.index.mjs +22 -28
  32. package/dist/node.index.mjs.map +1 -1
  33. package/dist/{phases-FTM69J3a.d.mts → phases-Dojnr-ng.d.mts} +111 -12
  34. package/dist/plugin.index.d.mts +3 -2
  35. package/dist/plugin.index.mjs +1 -1
  36. package/dist/{subscribers-Ce3SWy23.mjs → subscribers-D4_HZcgw.mjs} +12 -829
  37. package/dist/subscribers-D4_HZcgw.mjs.map +1 -0
  38. package/dist/{bindViteEnv-DWq5rftJ.mjs → usingCtx-Cf2cZi96.mjs} +1 -31
  39. package/package.json +11 -9
  40. package/dist/CoordinatedStartup-CGnJrqcl.mjs.map +0 -1
  41. package/dist/HmrModuleHandler-8ZC-UuZh.mjs.map +0 -1
  42. package/dist/PluginHost-B8kxLOeb.mjs +0 -245
  43. package/dist/PluginHost-B8kxLOeb.mjs.map +0 -1
  44. package/dist/PluginLifecycle-AhQ46NIr.mjs.map +0 -1
  45. package/dist/phases-BauKJOTT.mjs +0 -31
  46. package/dist/phases-BauKJOTT.mjs.map +0 -1
  47. package/dist/publishDefault-D3dAyzZ9.mjs +0 -7
  48. package/dist/publishDefault-D3dAyzZ9.mjs.map +0 -1
  49. package/dist/subscribers-Ce3SWy23.mjs.map +0 -1
@@ -0,0 +1,552 @@
1
+ import { _ as MemoryDriver, t as Bus, v as ShutdownPhase, y as StartupPhase } from "./Bus-W1K-uMeO.mjs";
2
+ import { SeedcordErrorCode, isSeedcordError } from "@seedcord/errors";
3
+ import { SeedcordError, SeedcordRangeError, SeedcordTypeError } from "@seedcord/errors/internal";
4
+ import { FRAMEWORK_CHANNELS, Logger } from "@seedcord/logger";
5
+ import { HostPluginKeys } from "@seedcord/types/internal";
6
+
7
+ //#region src/hmr/devChannel.ts
8
+ let channel;
9
+ /** @internal */
10
+ function setDevChannel(next) {
11
+ channel = next;
12
+ }
13
+ /** @internal */
14
+ function getDevChannel() {
15
+ return channel;
16
+ }
17
+
18
+ //#endregion
19
+ //#region src/plugin/lifecycle.ts
20
+ const DEFAULT_INIT_TIMEOUT_MS = 15e3;
21
+ const DEFAULT_READY_TIMEOUT_MS = 15e3;
22
+ const DEFAULT_DISPOSE_TIMEOUT_MS = 1e4;
23
+ function timeoutOf(field, declared, fallback, pluginName) {
24
+ if (declared === void 0) return fallback;
25
+ if (!Number.isFinite(declared) || declared <= 0) throw new SeedcordRangeError(SeedcordErrorCode.LifecycleInvalidTimeout, [`${pluginName}'s lifecycle ${field}.timeout`, declared]);
26
+ return declared;
27
+ }
28
+ /** @internal */
29
+ function resolveLifecycleSpec(spec, pluginName) {
30
+ return {
31
+ init: {
32
+ phase: spec?.init?.phase ?? 1,
33
+ timeout: timeoutOf("init", spec?.init?.timeout, DEFAULT_INIT_TIMEOUT_MS, pluginName)
34
+ },
35
+ ready: { timeout: timeoutOf("ready", spec?.ready?.timeout, DEFAULT_READY_TIMEOUT_MS, pluginName) },
36
+ dispose: {
37
+ phase: spec?.dispose?.phase ?? 3,
38
+ timeout: timeoutOf("dispose", spec?.dispose?.timeout, DEFAULT_DISPOSE_TIMEOUT_MS, pluginName)
39
+ }
40
+ };
41
+ }
42
+
43
+ //#endregion
44
+ //#region src/plugin/Plugin.ts
45
+ let attaching;
46
+ /** @internal */
47
+ const resolvedSpecSlot = Symbol("seedcord:plugin:spec");
48
+ /** @internal */
49
+ const loggerSlot = Symbol("seedcord:plugin:logger");
50
+ /** @internal */
51
+ const scopeSlot = Symbol("seedcord:plugin:scope");
52
+ /** @internal */
53
+ const FixedScope = Symbol("seedcord:plugin:fixed-scope");
54
+ /**
55
+ * Base class for a seedcord plugin. Extend it and implement `init()`. `this.core` is the running
56
+ * bot.
57
+ *
58
+ * Your constructor takes the host first and passes it to `super()`. Add your own parameters after
59
+ * it. `attach()` types them at the call site.
60
+ *
61
+ * A plugin that narrows `transport` or `runtime` in `Opts` passes the same values to `super()`.
62
+ * `attach()` throws when either one differs from the bot's.
63
+ *
64
+ * @typeParam Opts - Where this plugin may attach.
65
+ * @typeParam TCore - The `Core` each transport binds `this.core` to.
66
+ * @typeParam Fixed - The `transport` and `runtime` values a transport's own `Plugin` base sets. Leave it out.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * class Analytics extends Plugin<{ transport: 'gateway' }> {
71
+ * constructor(
72
+ * host: CoreBase,
73
+ * private readonly apiKey: string
74
+ * ) {
75
+ * super(host, { transport: 'gateway', init: { phase: StartupPhase.Login } });
76
+ * }
77
+ *
78
+ * public async init(): Promise<void> {
79
+ * await this.connect(this.apiKey);
80
+ * }
81
+ * }
82
+ *
83
+ * seedcord.attach('analytics', Analytics, apiKey);
84
+ * ```
85
+ */
86
+ var Plugin = class {
87
+ host;
88
+ /** @internal */
89
+ static [FixedScope] = {};
90
+ /** @internal */
91
+ [resolvedSpecSlot];
92
+ /** @internal */
93
+ [loggerSlot];
94
+ /** @internal */
95
+ [scopeSlot];
96
+ /** Logs under the plugin's class name, on the channel its attach key sets. */
97
+ logger;
98
+ #kv;
99
+ constructor(host, ...[spec]) {
100
+ this.host = host;
101
+ const attachment = attaching;
102
+ attaching = void 0;
103
+ this.#kv = attachment?.kv;
104
+ this.logger = new Logger(this.constructor.name, attachment && { channel: attachment.key });
105
+ this[loggerSlot] = this.logger;
106
+ this[resolvedSpecSlot] = resolveLifecycleSpec(spec, this.constructor.name);
107
+ const passed = spec;
108
+ const fixed = new.target[FixedScope];
109
+ this[scopeSlot] = {
110
+ transport: fixed.transport ?? passed?.transport ?? "any",
111
+ runtime: fixed.runtime ?? passed?.runtime ?? "any"
112
+ };
113
+ }
114
+ /** The host, typed to the transport whose `Plugin` base this class extends. */
115
+ get core() {
116
+ return this.host;
117
+ }
118
+ /**
119
+ * This plugin's records in the bot's `config.kv`, stored under its attach key. Two attachments of
120
+ * one plugin class each get their own records. Renaming the attach key leaves the records under
121
+ * the old key behind.
122
+ *
123
+ * @throws A **SeedcordTypeError** when something built the plugin with `new` outside `attach()`.
124
+ * @example
125
+ * ```ts
126
+ * // works in a field initializer, the constructor, and every method
127
+ * private readonly jobs = this.kv.collection<Job>('job');
128
+ * ```
129
+ */
130
+ get kv() {
131
+ if (!this.#kv) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKvOutsideAttach, [this.constructor.name]);
132
+ return this.#kv;
133
+ }
134
+ /**
135
+ * Rejects the options this plugin got.
136
+ *
137
+ * @param reason - Why the options are invalid.
138
+ * @throws A **SeedcordTypeError** every time, with the plugin class name and `reason` in its message.
139
+ */
140
+ rejectOptions(reason) {
141
+ throw new SeedcordTypeError(SeedcordErrorCode.PluginOptionsRejected, [this.constructor.name, reason]);
142
+ }
143
+ /** Override to reload plugin state on an HMR update. */
144
+ onHmr(_event) {
145
+ return Promise.resolve();
146
+ }
147
+ /**
148
+ * Registers critical file patterns that should trigger a full restart when changed in Dev HMR.
149
+ * @param patterns - Glob patterns. A relative one resolves against `root` from `seedcord.config.ts`.
150
+ */
151
+ registerCriticalFiles(patterns) {
152
+ getDevChannel()?.send("seedcord:register-critical-files", { patterns });
153
+ }
154
+ };
155
+ /** @internal */
156
+ function resolvedLifecycleSpecOf(plugin) {
157
+ return plugin[resolvedSpecSlot];
158
+ }
159
+ /** @internal */
160
+ function extendsThisCorePlugin(ctor) {
161
+ return ctor.prototype instanceof Plugin;
162
+ }
163
+ /** @internal */
164
+ function pluginLoggerOf(plugin) {
165
+ return plugin[loggerSlot];
166
+ }
167
+ /** @internal */
168
+ function declaredScopeOf(plugin) {
169
+ return plugin[scopeSlot];
170
+ }
171
+ /** @internal */
172
+ function constructAttached(key, kv, construct) {
173
+ attaching = {
174
+ key,
175
+ kv
176
+ };
177
+ try {
178
+ return construct();
179
+ } finally {
180
+ attaching = void 0;
181
+ }
182
+ }
183
+
184
+ //#endregion
185
+ //#region src/kv/GuardedDriver.ts
186
+ function translated(operation, key, error) {
187
+ if (isSeedcordError(error)) return error;
188
+ return new SeedcordError(SeedcordErrorCode.CoreKvDriverFailed, [operation, key], { cause: error });
189
+ }
190
+ async function guard(operation, key, run) {
191
+ try {
192
+ return await run();
193
+ } catch (error) {
194
+ throw translated(operation, key, error);
195
+ }
196
+ }
197
+ var GuardedDriver = class {
198
+ driver;
199
+ constructor(driver) {
200
+ this.driver = driver;
201
+ }
202
+ async get(key) {
203
+ return guard("read", key, () => this.driver.get(key));
204
+ }
205
+ async set(key, value, ttlMs) {
206
+ await guard("write", key, () => this.driver.set(key, value, ttlMs));
207
+ }
208
+ async delete(key) {
209
+ await guard("delete", key, () => this.driver.delete(key));
210
+ }
211
+ async *entries(prefix) {
212
+ try {
213
+ for await (const entry of this.driver.entries(prefix)) yield entry;
214
+ } catch (error) {
215
+ throw translated("list", prefix, error);
216
+ }
217
+ }
218
+ };
219
+
220
+ //#endregion
221
+ //#region src/kv/KeyedQueue.ts
222
+ var KeyedQueue = class {
223
+ tails = /* @__PURE__ */ new Map();
224
+ run(key, task) {
225
+ const run = (this.tails.get(key) ?? Promise.resolve()).then(task);
226
+ const tail = run.then(() => void 0, () => void 0);
227
+ this.tails.set(key, tail);
228
+ tail.then(() => {
229
+ if (this.tails.get(key) === tail) this.tails.delete(key);
230
+ });
231
+ return run;
232
+ }
233
+ };
234
+
235
+ //#endregion
236
+ //#region src/kv/KvCollection.ts
237
+ /**
238
+ * The records of one type, stored under one name in `config.kv`. Get one from
239
+ * `kv.collection<T>('name')`.
240
+ *
241
+ * Every record goes through JSON on its way in and out. A `Date` you store reads back from `get()`
242
+ * as an ISO string.
243
+ *
244
+ * @example
245
+ * ```ts
246
+ * interface Profile { bio: string; joinedAt: Date }
247
+ * const profiles = this.core.kv.collection<Profile>('profile');
248
+ *
249
+ * await profiles.set(user.id, { bio: 'hi', joinedAt: new Date() });
250
+ * const profile = await profiles.get(user.id); // { bio: string; joinedAt: string } | undefined
251
+ * await profiles.delete(user.id);
252
+ * ```
253
+ */
254
+ var KvCollection = class {
255
+ #driver;
256
+ #prefix;
257
+ #updates;
258
+ constructor(driver, prefix, updates) {
259
+ this.#driver = driver;
260
+ this.#prefix = prefix;
261
+ this.#updates = updates;
262
+ }
263
+ /** Reads the record stored under `id`. Resolves to `undefined` when nothing is there or the record expired. */
264
+ async get(id) {
265
+ const key = this.#prefix + id;
266
+ const raw = await this.#driver.get(key);
267
+ const value = this.#liveValue(raw);
268
+ if (raw !== void 0 && value === void 0) await this.#deleteExpired(key);
269
+ return value;
270
+ }
271
+ /**
272
+ * Stores `value` under `id` and replaces whatever was there.
273
+ *
274
+ * @param ttlMs - How long the record lasts, in milliseconds. Leave it out to keep the record until you delete it.
275
+ */
276
+ async set(id, value, ttlMs) {
277
+ const key = this.#prefix + id;
278
+ await this.#updates.run(key, async () => this.#write(key, value, ttlMs));
279
+ }
280
+ /** Removes the record under `id`. */
281
+ async delete(id) {
282
+ const key = this.#prefix + id;
283
+ await this.#updates.run(key, async () => this.#driver.delete(key));
284
+ }
285
+ /**
286
+ * Stores what `next` returns for the record under `id`, then resolves to that value. `next` gets
287
+ * the current record, or `undefined` when there is none.
288
+ *
289
+ * Use it over a `get()` then a `set()` when two calls can change one record at the same time.
290
+ * `set()`, `delete()`, and `update()` calls on one id run one after the other in this process.
291
+ * None of them loses another's change. Two processes sharing one backend can still both read
292
+ * before either writes.
293
+ *
294
+ * @param ttlMs - How many milliseconds the stored record lasts. Pass a function to work it out from the record. Leave it out to keep the record until you delete it.
295
+ * @example
296
+ * ```ts
297
+ * // counts a run of the command and resolves to the new count
298
+ * const runs = await counts.update(user.id, (count) => (count ?? 0) + 1);
299
+ * ```
300
+ */
301
+ async update(id, next, ttlMs) {
302
+ const key = this.#prefix + id;
303
+ return this.#updates.run(key, async () => {
304
+ const value = next(this.#liveValue(await this.#driver.get(key)));
305
+ await this.#write(key, value, typeof ttlMs === "function" ? ttlMs(value) : ttlMs);
306
+ return value;
307
+ });
308
+ }
309
+ /**
310
+ * Yields `[id, record]` for every record in this collection that hasn't expired. Use it to read
311
+ * back what the bot saved before a restart.
312
+ *
313
+ * @throws A **SeedcordError** when the backend can't list its keys, like `@keyv/memcache`.
314
+ * @example
315
+ * ```ts
316
+ * // schedules every saved reminder again after a restart
317
+ * for await (const [id, reminder] of reminders.entries()) schedule(id, reminder);
318
+ * ```
319
+ */
320
+ async *entries() {
321
+ const expired = [];
322
+ try {
323
+ for await (const [key, raw] of this.#driver.entries(this.#prefix)) {
324
+ const value = this.#liveValue(raw);
325
+ if (value === void 0) expired.push(key);
326
+ else yield [key.slice(this.#prefix.length), value];
327
+ }
328
+ } finally {
329
+ await Promise.all(expired.map(async (key) => this.#deleteExpired(key)));
330
+ }
331
+ }
332
+ async #write(key, value, ttlMs) {
333
+ const envelope = ttlMs === void 0 ? { value } : {
334
+ value,
335
+ expiresAt: Date.now() + ttlMs
336
+ };
337
+ await this.#driver.set(key, JSON.stringify(envelope), ttlMs);
338
+ }
339
+ async #deleteExpired(key) {
340
+ await this.#updates.run(key, async () => {
341
+ const raw = await this.#driver.get(key);
342
+ if (raw !== void 0 && this.#liveValue(raw) === void 0) await this.#driver.delete(key);
343
+ });
344
+ }
345
+ #liveValue(raw) {
346
+ if (raw === void 0) return void 0;
347
+ const envelope = JSON.parse(raw);
348
+ if (envelope.expiresAt !== void 0 && envelope.expiresAt <= Date.now()) return void 0;
349
+ return envelope.value;
350
+ }
351
+ };
352
+
353
+ //#endregion
354
+ //#region src/kv/Kv.ts
355
+ function keyPrefix(path) {
356
+ return path.map((segment) => `${encodeURIComponent(segment)}:`).join("");
357
+ }
358
+ /**
359
+ * The bot's key-value store, or one prefixed slice of it. `core.kv` holds your handlers' records. A
360
+ * plugin's `this.kv` holds the plugin's.
361
+ */
362
+ var Kv = class {
363
+ /** What you passed as `config.kv`. Register its type in `KvTypes` to call the library's own methods on it. */
364
+ driver;
365
+ #guarded;
366
+ #path;
367
+ #updates = new KeyedQueue();
368
+ constructor(driver, ...path) {
369
+ this.driver = driver;
370
+ this.#guarded = new GuardedDriver(driver);
371
+ this.#path = path;
372
+ }
373
+ /**
374
+ * Records of one type under `name`. Pass the type argument. A record type holding a function,
375
+ * `Map`, `Set`, or `bigint` fails to compile here.
376
+ */
377
+ collection(name) {
378
+ return new KvCollection(this.#guarded, keyPrefix([...this.#path, name]), this.#updates);
379
+ }
380
+ };
381
+
382
+ //#endregion
383
+ //#region src/rateLimiter/KvRateLimiter.ts
384
+ function limitedResult(live, now) {
385
+ const resetAt = live.reduce((earliest, exp) => Math.min(earliest, exp), Number.POSITIVE_INFINITY);
386
+ return {
387
+ limited: true,
388
+ resetAt,
389
+ remaining: 0,
390
+ retryAfterMs: resetAt - now
391
+ };
392
+ }
393
+ function openResult(remaining) {
394
+ return {
395
+ limited: false,
396
+ resetAt: 0,
397
+ remaining,
398
+ retryAfterMs: 0
399
+ };
400
+ }
401
+ function limitOf(window) {
402
+ return Math.max(1, window.limit ?? 1);
403
+ }
404
+ function liveUses(stored, now) {
405
+ return (stored ?? []).filter((exp) => exp > now);
406
+ }
407
+ var KvRateLimiter = class {
408
+ uses;
409
+ constructor(kv) {
410
+ this.uses = kv.collection("rate-limit");
411
+ }
412
+ async charge(key, window) {
413
+ const limit = limitOf(window);
414
+ let result;
415
+ await this.uses.update(key, (stored) => {
416
+ const now = Date.now();
417
+ const live = liveUses(stored, now);
418
+ const limited = live.length >= limit;
419
+ if (!limited) live.push(now + window.windowMs);
420
+ result = limited ? limitedResult(live, now) : openResult(limit - live.length);
421
+ return live;
422
+ }, (live) => live.reduce((latest, exp) => Math.max(latest, exp), 0) - Date.now());
423
+ return result;
424
+ }
425
+ async peek(key, window) {
426
+ const stored = await this.uses.get(key);
427
+ const now = Date.now();
428
+ const limit = limitOf(window);
429
+ const live = liveUses(stored, now);
430
+ return live.length >= limit ? limitedResult(live, now) : openResult(limit - live.length);
431
+ }
432
+ async reset(key) {
433
+ await this.uses.delete(key);
434
+ }
435
+ };
436
+
437
+ //#endregion
438
+ //#region src/plugin/splitPluginKey.ts
439
+ function splitPluginKey(key) {
440
+ const dot = key.indexOf(".");
441
+ return dot === -1 ? {
442
+ head: key,
443
+ leaf: void 0
444
+ } : {
445
+ head: key.slice(0, dot),
446
+ leaf: key.slice(dot + 1)
447
+ };
448
+ }
449
+
450
+ //#endregion
451
+ //#region src/plugin/PluginHost.ts
452
+ const RESERVED_KEYS = new Set(FRAMEWORK_CHANNELS);
453
+ const attachmentsSlot = Symbol("seedcord:host:attachments");
454
+ const sealSlot = Symbol("seedcord:host:seal");
455
+ /** Base class for a plugin host. */
456
+ var PluginHost = class {
457
+ /** @see {@link Bus} */
458
+ bus;
459
+ /** @see {@link Kv} */
460
+ kv;
461
+ /** @see {@link IRateLimiter} */
462
+ rateLimiter;
463
+ #sealed = false;
464
+ #attachments = [];
465
+ #groups = /* @__PURE__ */ new Map();
466
+ #scope;
467
+ constructor(transport, runtime, driver) {
468
+ this.#scope = {
469
+ transport,
470
+ runtime
471
+ };
472
+ this.bus = new Bus(this);
473
+ const kvDriver = driver ?? new MemoryDriver();
474
+ this.kv = new Kv(kvDriver, "app");
475
+ this.rateLimiter = new KvRateLimiter(new Kv(kvDriver, "seedcord"));
476
+ }
477
+ /** @internal */
478
+ get [attachmentsSlot]() {
479
+ return this.#attachments;
480
+ }
481
+ /** @internal */
482
+ [sealSlot]() {
483
+ this.#sealed = true;
484
+ }
485
+ /** @internal codegen reads this to emit the `Core` augmentation */
486
+ get [HostPluginKeys]() {
487
+ return this.#attachments.map((attachment) => attachment.key);
488
+ }
489
+ attach(key, Plugin, ...args) {
490
+ if (this.#sealed) throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);
491
+ const { head, leaf } = splitPluginKey(key);
492
+ if (RESERVED_KEYS.has(head)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginReservedChannel, [head]);
493
+ if (head === "" || leaf === "") throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyMalformed, [key, "has an empty part."]);
494
+ if (leaf?.includes(".")) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyMalformed, [key, "has more than one dot."]);
495
+ this.#assertFree(head, leaf, key);
496
+ if (!extendsThisCorePlugin(Plugin)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginFromOtherCore, [Plugin.name]);
497
+ const kv = new Kv(this.kv.driver, "plugins", key);
498
+ const instance = constructAttached(key, kv, () => new Plugin(this, ...args));
499
+ this.#assertScope(Plugin.name, declaredScopeOf(instance));
500
+ this.#attachments.push({
501
+ key,
502
+ instance,
503
+ spec: resolvedLifecycleSpecOf(instance)
504
+ });
505
+ if (leaf === void 0) return Object.assign(this, { [key]: instance });
506
+ this.#groupFor(head)[leaf] = instance;
507
+ return this;
508
+ }
509
+ #assertFree(head, leaf, key) {
510
+ if (leaf === void 0) {
511
+ if (this.#groups.has(key)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyHoldsGroup, [key]);
512
+ if (key in this) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyExists, [key]);
513
+ return;
514
+ }
515
+ const group = this.#groups.get(head);
516
+ if (!group) {
517
+ if (head in this) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginGroupTaken, [head, key]);
518
+ return;
519
+ }
520
+ if (Object.hasOwn(group, leaf)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyExists, [key]);
521
+ }
522
+ #assertScope(pluginName, declared) {
523
+ for (const axis of ["transport", "runtime"]) {
524
+ const host = this.#scope[axis];
525
+ if (declared[axis] === "any" || declared[axis] === host) continue;
526
+ throw new SeedcordTypeError(SeedcordErrorCode.CorePluginScopeMismatch, [
527
+ pluginName,
528
+ axis,
529
+ declared[axis],
530
+ host
531
+ ]);
532
+ }
533
+ }
534
+ #groupFor(head) {
535
+ const existing = this.#groups.get(head);
536
+ if (existing) return existing;
537
+ const group = Object.create(null);
538
+ this.#groups.set(head, group);
539
+ Object.assign(this, { [head]: group });
540
+ return group;
541
+ }
542
+ };
543
+ function attachmentsOf(host) {
544
+ return host[attachmentsSlot];
545
+ }
546
+ function sealAttachments(host) {
547
+ host[sealSlot]();
548
+ }
549
+
550
+ //#endregion
551
+ export { FixedScope as a, getDevChannel as c, splitPluginKey as i, setDevChannel as l, attachmentsOf as n, Plugin as o, sealAttachments as r, pluginLoggerOf as s, PluginHost as t };
552
+ //# sourceMappingURL=PluginHost-CYe-UVpp.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PluginHost-CYe-UVpp.mjs","names":[],"sources":["../src/hmr/devChannel.ts","../src/plugin/lifecycle.ts","../src/plugin/Plugin.ts","../src/kv/GuardedDriver.ts","../src/kv/KeyedQueue.ts","../src/kv/KvCollection.ts","../src/kv/Kv.ts","../src/rateLimiter/KvRateLimiter.ts","../src/plugin/splitPluginKey.ts","../src/plugin/PluginHost.ts"],"sourcesContent":["import type { DevChannel, SeedcordCliEvents, SeedcordFrameworkEvents } from '@seedcord/types/internal';\n\nexport type CliChannel = DevChannel<SeedcordFrameworkEvents, SeedcordCliEvents>;\n\n// one module instance per process, the HmrManager write and every framework read share it\nlet channel: CliChannel | undefined;\n\n/** @internal */\nexport function setDevChannel(next: CliChannel | undefined): void {\n channel = next;\n}\n\n/** @internal */\nexport function getDevChannel(): CliChannel | undefined {\n return channel;\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordRangeError } from '@seedcord/errors/internal';\n\nimport { ShutdownPhase, StartupPhase } from '#src/lifecycle/phases';\n\nimport type { TaskShutdownPhase } from '#src/lifecycle/phases';\n\n/**\n * Sets when a plugin's lifecycle methods run. Pass it as the second argument to `super()`. Omit a\n * field to keep its default.\n *\n * @see {@link StartupPhase}\n * @see {@link ShutdownPhase}\n * @example\n * ```ts\n * super(host, { init: { phase: StartupPhase.Login }, dispose: { timeout: 30_000 } });\n * ```\n */\nexport interface PluginLifecycleSpec {\n /**\n * Defaults to `StartupPhase.Configuration` and 15000ms. Set the phase to `Ready` and `init()`\n * runs ahead of every plugin's `ready()`.\n */\n init?: { phase?: StartupPhase; timeout?: number };\n /** `ready()` always runs in `StartupPhase.Ready`. Defaults to a 15000ms timeout. */\n ready?: { timeout?: number };\n /** Defaults to `ShutdownPhase.Disconnect` and 10000ms. */\n dispose?: { phase?: TaskShutdownPhase; timeout?: number };\n}\n\n/** @internal */\nexport interface ResolvedPluginLifecycleSpec {\n readonly init: { readonly phase: StartupPhase; readonly timeout: number };\n readonly ready: { readonly timeout: number };\n readonly dispose: { readonly phase: TaskShutdownPhase; readonly timeout: number };\n}\n\nconst DEFAULT_INIT_TIMEOUT_MS = 15_000;\nconst DEFAULT_READY_TIMEOUT_MS = 15_000;\nconst DEFAULT_DISPOSE_TIMEOUT_MS = 10_000;\n\nfunction timeoutOf(\n field: 'init' | 'ready' | 'dispose',\n declared: number | undefined,\n fallback: number,\n pluginName: string\n): number {\n if (declared === undefined) return fallback;\n if (!Number.isFinite(declared) || declared <= 0) {\n throw new SeedcordRangeError(SeedcordErrorCode.LifecycleInvalidTimeout, [\n `${pluginName}'s lifecycle ${field}.timeout`,\n declared\n ]);\n }\n return declared;\n}\n\n/** @internal */\nexport function resolveLifecycleSpec(\n spec: PluginLifecycleSpec | undefined,\n pluginName: string\n): ResolvedPluginLifecycleSpec {\n return {\n init: {\n phase: spec?.init?.phase ?? StartupPhase.Configuration,\n timeout: timeoutOf('init', spec?.init?.timeout, DEFAULT_INIT_TIMEOUT_MS, pluginName)\n },\n ready: {\n timeout: timeoutOf('ready', spec?.ready?.timeout, DEFAULT_READY_TIMEOUT_MS, pluginName)\n },\n dispose: {\n phase: spec?.dispose?.phase ?? ShutdownPhase.Disconnect,\n timeout: timeoutOf('dispose', spec?.dispose?.timeout, DEFAULT_DISPOSE_TIMEOUT_MS, pluginName)\n }\n };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordTypeError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\n\nimport { getDevChannel } from '#hmr/devChannel';\n\nimport { RuntimeBrand, TransportBrand } from './brands';\nimport { resolveLifecycleSpec } from './lifecycle';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { Kv } from '#src/kv/Kv';\nimport type { ScopedSpec } from './brands';\nimport type { ResolvedPluginLifecycleSpec, PluginLifecycleSpec } from './lifecycle';\nimport type {\n DeclaredScope,\n TransportOf,\n PluginOptions,\n PluginSpecArgs,\n RuntimeOf,\n ScopeAxis,\n Runtime,\n RuntimeAssert,\n Transport,\n TransportAssert\n} from './options';\nimport type { Tail, FrameworkChannel, HmrAware, HmrUpdateEvent } from '@seedcord/types';\n\ninterface Attaching {\n readonly key: string;\n readonly kv: Kv;\n}\n\n// set only while attach() runs `new Plugin()`\nlet attaching: Attaching | undefined;\n\nexport interface Initializeable {\n init(): Promise<void>;\n}\n\n/** @internal */\nconst resolvedSpecSlot = Symbol('seedcord:plugin:spec');\n/** @internal */\nconst loggerSlot = Symbol('seedcord:plugin:logger');\n/** @internal */\nconst scopeSlot = Symbol('seedcord:plugin:scope');\n/** @internal */\nexport const FixedScope = Symbol('seedcord:plugin:fixed-scope');\n\n/**\n * Base class for a seedcord plugin. Extend it and implement `init()`. `this.core` is the running\n * bot.\n *\n * Your constructor takes the host first and passes it to `super()`. Add your own parameters after\n * it. `attach()` types them at the call site.\n *\n * A plugin that narrows `transport` or `runtime` in `Opts` passes the same values to `super()`.\n * `attach()` throws when either one differs from the bot's.\n *\n * @typeParam Opts - Where this plugin may attach.\n * @typeParam TCore - The `Core` each transport binds `this.core` to.\n * @typeParam Fixed - The `transport` and `runtime` values a transport's own `Plugin` base sets. Leave it out.\n *\n * @example\n * ```ts\n * class Analytics extends Plugin<{ transport: 'gateway' }> {\n * constructor(\n * host: CoreBase,\n * private readonly apiKey: string\n * ) {\n * super(host, { transport: 'gateway', init: { phase: StartupPhase.Login } });\n * }\n *\n * public async init(): Promise<void> {\n * await this.connect(this.apiKey);\n * }\n * }\n *\n * seedcord.attach('analytics', Analytics, apiKey);\n * ```\n */\nexport abstract class Plugin<\n Opts extends PluginOptions = {},\n TCore extends CoreBase = CoreBase,\n Fixed extends ScopeAxis = never\n>\n implements Initializeable, HmrAware\n{\n /** @internal */\n protected static readonly [FixedScope]: Partial<DeclaredScope> = {};\n\n // phantom, never set at runtime.\n /** @internal */\n declare readonly [TransportBrand]?: TransportOf<Opts>;\n /** @internal */\n declare readonly [RuntimeBrand]?: RuntimeOf<Opts>;\n\n /** @internal */\n readonly [resolvedSpecSlot]: ResolvedPluginLifecycleSpec;\n /** @internal */\n readonly [loggerSlot]: Logger;\n /** @internal */\n readonly [scopeSlot]: DeclaredScope;\n\n /** Logs under the plugin's class name, on the channel its attach key sets. */\n protected readonly logger: Logger;\n\n readonly #kv: Kv | undefined;\n\n // CoreBase here keeps the augmented Core out of ConstructorParameters, which attach reads\n constructor(\n private readonly host: CoreBase,\n ...[spec]: PluginSpecArgs<Opts, Fixed>\n ) {\n // cleared here. a plugin this constructor builds by hand doesn't get the outer key\n const attachment = attaching;\n attaching = undefined;\n this.#kv = attachment?.kv;\n\n this.logger = new Logger(this.constructor.name, attachment && { channel: attachment.key });\n this[loggerSlot] = this.logger;\n this[resolvedSpecSlot] = resolveLifecycleSpec(spec, this.constructor.name);\n\n const passed: (PluginLifecycleSpec & Partial<DeclaredScope>) | undefined = spec;\n const fixed = new.target[FixedScope];\n this[scopeSlot] = {\n transport: fixed.transport ?? passed?.transport ?? 'any',\n runtime: fixed.runtime ?? passed?.runtime ?? 'any'\n };\n }\n\n /** The host, typed to the transport whose `Plugin` base this class extends. */\n protected get core(): TCore {\n // justified: each transport binds TCore to the Core its own host satisfies\n return this.host as TCore;\n }\n\n /**\n * This plugin's records in the bot's `config.kv`, stored under its attach key. Two attachments of\n * one plugin class each get their own records. Renaming the attach key leaves the records under\n * the old key behind.\n *\n * @throws A **SeedcordTypeError** when something built the plugin with `new` outside `attach()`.\n * @example\n * ```ts\n * // works in a field initializer, the constructor, and every method\n * private readonly jobs = this.kv.collection<Job>('job');\n * ```\n */\n protected get kv(): Kv {\n if (!this.#kv)\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKvOutsideAttach, [this.constructor.name]);\n return this.#kv;\n }\n\n /**\n * Runs in `StartupPhase.Configuration` by default.\n * Move it to a different phase with the {@link PluginLifecycleSpec}.\n */\n abstract init(): Promise<void>;\n\n /**\n * Runs in the Ready phase, after every attached plugin's `init()` has resolved. The other Ready\n * tasks run alongside it, including the http server binding its port.\n *\n * Override it when your work needs a logged-in client or a resolved application id.\n */\n ready?(): Promise<void>;\n\n /**\n * Runs during teardown, in `ShutdownPhase.Disconnect` by default. seedcord skips it when `init()`\n * has thrown.\n *\n * When `init()` outlasts its timeout, seedcord calls this once that `init()` resolves, outside any\n * shutdown phase. The process must still be alive for that call to happen.\n */\n dispose?(): Promise<void>;\n\n /**\n * Returns data that `seedcord codegen` writes into the bot's `seedcord-gen.ts` as a type.\n * Read it back with `Generated<typeof YourPlugin>`. Return plain data, or classes and objects\n * that a project file or your plugin's package exports. The class needs a `static readonly id`.\n *\n * `seedcord codegen` calls this without running `init()`. Read only what the constructor stored\n * and the bot's files.\n */\n codegen?(): Promise<unknown>;\n\n /**\n * Rejects the options this plugin got.\n *\n * @param reason - Why the options are invalid.\n * @throws A **SeedcordTypeError** every time, with the plugin class name and `reason` in its message.\n */\n protected rejectOptions(reason: string): never {\n throw new SeedcordTypeError(SeedcordErrorCode.PluginOptionsRejected, [this.constructor.name, reason]);\n }\n\n /** Override to reload plugin state on an HMR update. */\n public onHmr(_event: HmrUpdateEvent): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Registers critical file patterns that should trigger a full restart when changed in Dev HMR.\n * @param patterns - Glob patterns. A relative one resolves against `root` from `seedcord.config.ts`.\n */\n protected registerCriticalFiles(patterns: string[]): void {\n getDevChannel()?.send('seedcord:register-critical-files', { patterns });\n }\n}\n\n/** @internal */\nexport function resolvedLifecycleSpecOf(plugin: PluginLike): ResolvedPluginLifecycleSpec {\n return plugin[resolvedSpecSlot];\n}\n\n/** @internal */\nexport function extendsThisCorePlugin(ctor: PluginCtor): boolean {\n return ctor.prototype instanceof Plugin;\n}\n\n/** @internal */\nexport function pluginLoggerOf(plugin: PluginLike): Logger {\n return plugin[loggerSlot];\n}\n\n/** @internal */\nexport function declaredScopeOf(plugin: PluginLike): DeclaredScope {\n return plugin[scopeSlot];\n}\n\n/** @internal */\nexport function constructAttached<TPlugin>(key: string, kv: Kv, construct: () => TPlugin): TPlugin {\n attaching = { key, kv };\n try {\n return construct();\n } finally {\n attaching = undefined;\n }\n}\n\nexport type { PluginLifecycleSpec } from './lifecycle';\nexport type { PluginOptions } from './options';\n\n// a bound of Plugin<{}> rejects every plugin that declares an option, since Plugin<A> and Plugin<B>\n// are mutually unassignable. every member here must stay Opts-independent.\n/** @internal */\nexport type PluginLike = Pick<\n Plugin,\n 'init' | 'ready' | 'dispose' | 'onHmr' | typeof resolvedSpecSlot | typeof loggerSlot | typeof scopeSlot\n>;\n\n// a `CoreBase` first parameter rejects a narrowing ctor on its own, and it also collapses\n// `InstanceType<Ctor>` to `PluginLike` at every attach call. CoreParamAssert checks it instead.\n/** @internal */\nexport type PluginCtor<TPlugin extends PluginLike = PluginLike> = new (...args: any[]) => TPlugin;\n\n/** @internal */\nexport type PluginArgs<Ctor extends PluginCtor> =\n InheritsScopedConstructor<Ctor> extends true ? [] : Tail<ConstructorParameters<Ctor>>;\n\n// a narrowed plugin with no constructor of its own inherits the base one, whose spec carries ScopedSpec\ntype InheritsScopedConstructor<Ctor extends PluginCtor> =\n Tail<ConstructorParameters<Ctor>> extends [infer Spec, ...unknown[]]\n ? typeof ScopedSpec extends keyof Spec\n ? true\n : false\n : false;\n\n/** @internal */\nexport type Attached<Key extends string, Instance> = Key extends `${infer Group}.${infer Leaf}`\n ? Record<Group, Record<Leaf, Instance>>\n : Record<Key, Instance>;\n\n// attach writes the key onto the host, so a key like toString would shadow the one on Object.prototype\n// eslint-disable-next-line @typescript-eslint/no-wrapper-object-types -- lint:fix swaps this for `keyof object`, which is never\ntype HostMember<Host> = keyof Host | keyof Object;\n\n// a string argument against an object type renders only as `string is not assignable`\n/** @internal */\nexport type AttachKeyAssert<Key extends string, Host> = Key extends ''\n ? 'A plugin key needs a name.'\n : Key extends `${infer Group}.${infer Leaf}`\n ? Leaf extends `${string}.${string}`\n ? `'${Key}' has more than one dot. A plugin key takes one dot at most.`\n : '' extends Group | Leaf\n ? `'${Key}' has an empty part. Write a group and a plugin name around the dot, like 'services.users'.`\n : Group extends FrameworkChannel\n ? `'${Group}' is a channel the framework logs on. Pick another group name.`\n : Group extends keyof Host\n ? Host[Group] extends PluginLike\n ? `'${Group}' already holds a plugin, so '${Leaf}' cannot nest inside it.`\n : Host[Group] extends Record<string, PluginLike>\n ? Leaf extends keyof Host[Group]\n ? `'${Key}' is already attached.`\n : Key\n : `'${Group}' is already a member on the bot. Pick another group name.`\n : Group extends HostMember<Host>\n ? `'${Group}' is already a member on the bot. Pick another group name.`\n : Key\n : Key extends FrameworkChannel\n ? `'${Key}' is a channel the framework logs on. Pick another plugin key.`\n : Key extends keyof Host\n ? Host[Key] extends PluginLike\n ? `'${Key}' is already attached.`\n : Host[Key] extends Record<string, PluginLike>\n ? `'${Key}' already holds a group of plugins. Attach this one under a name of its own.`\n : `'${Key}' is already a member on the bot. Pick another plugin key.`\n : Key extends HostMember<Host>\n ? `'${Key}' is already a member on the bot. Pick another plugin key.`\n : Key;\n\ntype CoreParamTooNarrow = Record<\n 'this plugin constructor must take CoreBase as its first parameter and read the transport Core off this.core',\n never\n>;\n\n// a narrowed first parameter fails `CoreBase extends Param`\ntype CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0]\n ? unknown\n : CoreParamTooNarrow;\n\ntype ConstructorMissing = Record<\n \"this plugin narrows its transport or runtime. Give it a constructor that passes them to super(), like super(host, { runtime: 'server' })\",\n never\n>;\n\ntype OwnConstructorAssert<Ctor extends PluginCtor> =\n InheritsScopedConstructor<Ctor> extends true ? ConstructorMissing : unknown;\n\ntype AttachAsserts<Ctor extends PluginCtor, BotT extends Transport, BotRt extends Runtime> = TransportAssert<\n InstanceType<Ctor>,\n BotT\n> &\n RuntimeAssert<InstanceType<Ctor>, BotRt> &\n CoreParamAssert<Ctor> &\n OwnConstructorAssert<Ctor>;\n\n// typescript can't infer Ctor from a generic class through `Ctor & AttachAsserts`. every assert returns unknown to pass.\n/** @internal */\nexport type AttachableCtor<Ctor extends PluginCtor, BotT extends Transport, BotRt extends Runtime> =\n unknown extends AttachAsserts<Ctor, BotT, BotRt> ? Ctor : AttachAsserts<Ctor, BotT, BotRt>;\n","import { SeedcordErrorCode, isSeedcordError } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport type { SeedcordErrorArguments } from '@seedcord/errors/internal';\nimport type { KvDriver, KvEntry } from '@seedcord/types';\nimport type { Promisable } from 'type-fest';\n\ntype Operation = SeedcordErrorArguments<SeedcordErrorCode.CoreKvDriverFailed>[0];\n\nfunction translated(operation: Operation, key: string, error: unknown): unknown {\n if (isSeedcordError(error)) return error;\n return new SeedcordError(SeedcordErrorCode.CoreKvDriverFailed, [operation, key], { cause: error });\n}\n\nasync function guard<TResult>(operation: Operation, key: string, run: () => Promisable<TResult>): Promise<TResult> {\n try {\n return await run();\n } catch (error) {\n throw translated(operation, key, error);\n }\n}\n\nexport class GuardedDriver implements KvDriver {\n constructor(private readonly driver: KvDriver) {}\n\n public async get(key: string): Promise<string | undefined> {\n return guard('read', key, () => this.driver.get(key));\n }\n\n public async set(key: string, value: string, ttlMs?: number): Promise<void> {\n await guard('write', key, () => this.driver.set(key, value, ttlMs));\n }\n\n public async delete(key: string): Promise<void> {\n await guard('delete', key, () => this.driver.delete(key));\n }\n\n public async *entries(prefix: string): AsyncIterable<KvEntry> {\n try {\n for await (const entry of this.driver.entries(prefix)) yield entry;\n } catch (error) {\n throw translated('list', prefix, error);\n }\n }\n}\n","export class KeyedQueue {\n private readonly tails = new Map<string, Promise<void>>();\n\n public run<TResult>(key: string, task: () => Promise<TResult>): Promise<TResult> {\n const run = (this.tails.get(key) ?? Promise.resolve()).then(task);\n const tail = run.then(\n () => undefined,\n () => undefined\n );\n this.tails.set(key, tail);\n void tail.then(() => {\n if (this.tails.get(key) === tail) this.tails.delete(key);\n });\n return run;\n }\n}\n","import type { KeyedQueue } from './KeyedQueue';\nimport type { KvDriver } from '@seedcord/types';\nimport type { Jsonify, Simplify } from 'type-fest';\n\n// a hover shows the record's fields in place of JsonifyObject<...>. SimplifyDeep hits TS2589 in get()\ntype Stored<TRecord> = Simplify<Jsonify<TRecord>>;\n\n// expiresAt covers backends that ignore a ttl, like unstorage's fs driver\ninterface Envelope<TValue> {\n value: TValue;\n expiresAt?: number;\n}\n\n/**\n * The records of one type, stored under one name in `config.kv`. Get one from\n * `kv.collection<T>('name')`.\n *\n * Every record goes through JSON on its way in and out. A `Date` you store reads back from `get()`\n * as an ISO string.\n *\n * @example\n * ```ts\n * interface Profile { bio: string; joinedAt: Date }\n * const profiles = this.core.kv.collection<Profile>('profile');\n *\n * await profiles.set(user.id, { bio: 'hi', joinedAt: new Date() });\n * const profile = await profiles.get(user.id); // { bio: string; joinedAt: string } | undefined\n * await profiles.delete(user.id);\n * ```\n */\nexport class KvCollection<TRecord> {\n readonly #driver: KvDriver;\n readonly #prefix: string;\n readonly #updates: KeyedQueue;\n\n constructor(driver: KvDriver, prefix: string, updates: KeyedQueue) {\n this.#driver = driver;\n this.#prefix = prefix;\n this.#updates = updates;\n }\n\n /** Reads the record stored under `id`. Resolves to `undefined` when nothing is there or the record expired. */\n public async get(id: string): Promise<Stored<TRecord> | undefined> {\n const key = this.#prefix + id;\n const raw = await this.#driver.get(key);\n const value = this.#liveValue(raw);\n if (raw !== undefined && value === undefined) await this.#deleteExpired(key);\n return value;\n }\n\n /**\n * Stores `value` under `id` and replaces whatever was there.\n *\n * @param ttlMs - How long the record lasts, in milliseconds. Leave it out to keep the record until you delete it.\n */\n public async set(id: string, value: TRecord, ttlMs?: number): Promise<void> {\n const key = this.#prefix + id;\n await this.#updates.run(key, async () => this.#write(key, value, ttlMs));\n }\n\n /** Removes the record under `id`. */\n public async delete(id: string): Promise<void> {\n const key = this.#prefix + id;\n await this.#updates.run(key, async () => this.#driver.delete(key));\n }\n\n /**\n * Stores what `next` returns for the record under `id`, then resolves to that value. `next` gets\n * the current record, or `undefined` when there is none.\n *\n * Use it over a `get()` then a `set()` when two calls can change one record at the same time.\n * `set()`, `delete()`, and `update()` calls on one id run one after the other in this process.\n * None of them loses another's change. Two processes sharing one backend can still both read\n * before either writes.\n *\n * @param ttlMs - How many milliseconds the stored record lasts. Pass a function to work it out from the record. Leave it out to keep the record until you delete it.\n * @example\n * ```ts\n * // counts a run of the command and resolves to the new count\n * const runs = await counts.update(user.id, (count) => (count ?? 0) + 1);\n * ```\n */\n public async update(\n id: string,\n next: (current: Stored<TRecord> | undefined) => TRecord,\n ttlMs?: number | ((value: TRecord) => number)\n ): Promise<TRecord> {\n const key = this.#prefix + id;\n return this.#updates.run(key, async () => {\n const value = next(this.#liveValue(await this.#driver.get(key)));\n await this.#write(key, value, typeof ttlMs === 'function' ? ttlMs(value) : ttlMs);\n return value;\n });\n }\n\n /**\n * Yields `[id, record]` for every record in this collection that hasn't expired. Use it to read\n * back what the bot saved before a restart.\n *\n * @throws A **SeedcordError** when the backend can't list its keys, like `@keyv/memcache`.\n * @example\n * ```ts\n * // schedules every saved reminder again after a restart\n * for await (const [id, reminder] of reminders.entries()) schedule(id, reminder);\n * ```\n */\n public async *entries(): AsyncIterable<[id: string, record: Stored<TRecord>]> {\n const expired: string[] = [];\n try {\n for await (const [key, raw] of this.#driver.entries(this.#prefix)) {\n const value = this.#liveValue(raw);\n if (value === undefined) expired.push(key);\n else yield [key.slice(this.#prefix.length), value];\n }\n } finally {\n // deletes wait until the listing ends. a driver may be reading from an open cursor\n await Promise.all(expired.map(async (key) => this.#deleteExpired(key)));\n }\n }\n\n async #write(key: string, value: TRecord, ttlMs: number | undefined): Promise<void> {\n const envelope: Envelope<TRecord> = ttlMs === undefined ? { value } : { value, expiresAt: Date.now() + ttlMs };\n await this.#driver.set(key, JSON.stringify(envelope), ttlMs);\n }\n\n // reads again in the queue. a set queued after the first read may have stored a live record\n async #deleteExpired(key: string): Promise<void> {\n await this.#updates.run(key, async () => {\n const raw = await this.#driver.get(key);\n if (raw !== undefined && this.#liveValue(raw) === undefined) await this.#driver.delete(key);\n });\n }\n\n #liveValue(raw: string | undefined): Stored<TRecord> | undefined {\n if (raw === undefined) return undefined;\n // justified: only this collection writes under its prefix\n const envelope = JSON.parse(raw) as Envelope<Stored<TRecord>>;\n if (envelope.expiresAt !== undefined && envelope.expiresAt <= Date.now()) return undefined;\n return envelope.value;\n }\n}\n","import { GuardedDriver } from './GuardedDriver';\nimport { KeyedQueue } from './KeyedQueue';\nimport { KvCollection } from './KvCollection';\n\nimport type { Config } from '@seedcord/types';\n\ntype ConfiguredDriver = NonNullable<Config['kv']>;\n\n// what JSON.stringify keeps as written. a Date passes through its toJSON\ntype JsonSafe<TValue> = TValue extends string | number | boolean | null | undefined\n ? TValue\n : TValue extends { toJSON(): unknown }\n ? TValue\n : TValue extends (...args: never[]) => unknown\n ? never\n : TValue extends ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>\n ? never\n : TValue extends readonly (infer Item)[]\n ? readonly JsonSafe<Item>[]\n : TValue extends object\n ? { [K in keyof TValue]: JsonSafe<TValue[K]> }\n : never;\n\n// tsc prints a failed string argument against an object type as only `string is not assignable`\ntype CollectionName<TRecord> = unknown extends TRecord\n ? 'Pass the record type, like kv.collection<Profile>(name).'\n : [TRecord] extends [JsonSafe<TRecord>]\n ? string\n : 'This record type holds a value JSON cannot keep, like a function, Map, Set, or bigint.';\n\n// unescaped, every key under `user:settings:` would also start with `user:`\nfunction keyPrefix(path: readonly string[]): string {\n return path.map((segment) => `${encodeURIComponent(segment)}:`).join('');\n}\n\n/**\n * The bot's key-value store, or one prefixed slice of it. `core.kv` holds your handlers' records. A\n * plugin's `this.kv` holds the plugin's.\n */\nexport class Kv {\n /** What you passed as `config.kv`. Register its type in `KvTypes` to call the library's own methods on it. */\n public readonly driver: ConfiguredDriver;\n readonly #guarded: GuardedDriver;\n readonly #path: readonly string[];\n readonly #updates = new KeyedQueue();\n\n constructor(driver: ConfiguredDriver, ...path: string[]) {\n this.driver = driver;\n this.#guarded = new GuardedDriver(driver);\n this.#path = path;\n }\n\n /**\n * Records of one type under `name`. Pass the type argument. A record type holding a function,\n * `Map`, `Set`, or `bigint` fails to compile here.\n */\n public collection<TRecord>(name: CollectionName<TRecord>): KvCollection<TRecord> {\n return new KvCollection<TRecord>(this.#guarded, keyPrefix([...this.#path, name]), this.#updates);\n }\n}\n","import type { Kv } from '#src/kv/Kv';\nimport type { KvCollection } from '#src/kv/KvCollection';\nimport type { EpochMs, IRateLimiter, RateLimitResult, RateLimitWindow } from '@seedcord/types';\n\nfunction limitedResult(live: readonly number[], now: number): RateLimitResult {\n // a reduce, because spreading into Math.min throws past the argument cap\n const resetAt = live.reduce((earliest, exp) => Math.min(earliest, exp), Number.POSITIVE_INFINITY) as EpochMs;\n return { limited: true, resetAt, remaining: 0, retryAfterMs: resetAt - now };\n}\n\nfunction openResult(remaining: number): RateLimitResult {\n return { limited: false, resetAt: 0 as EpochMs, remaining, retryAfterMs: 0 };\n}\n\n// 0 or a negative limit never means \"block everything\"\nfunction limitOf(window: RateLimitWindow): number {\n return Math.max(1, window.limit ?? 1);\n}\n\nfunction liveUses(stored: readonly number[] | undefined, now: number): number[] {\n return (stored ?? []).filter((exp) => exp > now);\n}\n\nexport class KvRateLimiter implements IRateLimiter {\n private readonly uses: KvCollection<number[]>;\n\n constructor(kv: Kv) {\n this.uses = kv.collection<number[]>('rate-limit');\n }\n\n public async charge(key: string, window: RateLimitWindow): Promise<RateLimitResult> {\n const limit = limitOf(window);\n // update() calls the callback before it resolves\n let result!: RateLimitResult;\n await this.uses.update(\n key,\n (stored) => {\n // read here, since this callback can wait in the queue behind other charges\n const now = Date.now();\n const live = liveUses(stored, now);\n const limited = live.length >= limit;\n if (!limited) live.push(now + window.windowMs);\n result = limited ? limitedResult(live, now) : openResult(limit - live.length);\n return live;\n },\n (live) => live.reduce((latest, exp) => Math.max(latest, exp), 0) - Date.now()\n );\n return result;\n }\n\n public async peek(key: string, window: RateLimitWindow): Promise<RateLimitResult> {\n const stored = await this.uses.get(key);\n const now = Date.now();\n const limit = limitOf(window);\n const live = liveUses(stored, now);\n return live.length >= limit ? limitedResult(live, now) : openResult(limit - live.length);\n }\n\n public async reset(key: string): Promise<void> {\n await this.uses.delete(key);\n }\n}\n","interface PluginKeyParts {\n readonly head: string;\n readonly leaf: string | undefined;\n}\n\n// 'services.users' attaches the plugin as users inside the group services\nexport function splitPluginKey(key: string): PluginKeyParts {\n const dot = key.indexOf('.');\n return dot === -1 ? { head: key, leaf: undefined } : { head: key.slice(0, dot), leaf: key.slice(dot + 1) };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError, SeedcordTypeError } from '@seedcord/errors/internal';\nimport { FRAMEWORK_CHANNELS } from '@seedcord/logger';\nimport { HostPluginKeys } from '@seedcord/types/internal';\n\nimport { Kv } from '#src/kv/Kv';\nimport { MemoryDriver } from '#src/kv/MemoryDriver';\nimport { KvRateLimiter } from '#src/rateLimiter/KvRateLimiter';\nimport { Bus } from '#subscribers/Bus';\n\nimport { constructAttached, declaredScopeOf, extendsThisCorePlugin, resolvedLifecycleSpecOf } from './Plugin';\nimport { splitPluginKey } from './splitPluginKey';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { ResolvedPluginLifecycleSpec } from './lifecycle';\nimport type { DeclaredScope, Runtime, Transport } from './options';\nimport type { AttachableCtor, Attached, AttachKeyAssert, PluginArgs, PluginCtor, PluginLike } from './Plugin';\nimport type { REST } from '@discordjs/rest';\nimport type { Config, IRateLimiter } from '@seedcord/types';\n\nexport interface Attachment {\n readonly key: string;\n readonly instance: PluginLike;\n readonly spec: ResolvedPluginLifecycleSpec;\n}\n\nconst RESERVED_KEYS: ReadonlySet<string> = new Set(FRAMEWORK_CHANNELS);\n\nconst attachmentsSlot = Symbol('seedcord:host:attachments');\nconst sealSlot = Symbol('seedcord:host:seal');\n\n/** Base class for a plugin host. */\nexport abstract class PluginHost<BotT extends Transport, BotRt extends Runtime> implements CoreBase {\n public abstract readonly config: Config;\n public abstract readonly rest: REST;\n public abstract readonly applicationId: string;\n\n /** @see {@link Bus} */\n public readonly bus: Bus;\n /** @see {@link Kv} */\n public readonly kv: Kv;\n /** @see {@link IRateLimiter} */\n public readonly rateLimiter: IRateLimiter;\n\n #sealed = false;\n readonly #attachments: Attachment[] = [];\n readonly #groups = new Map<string, Record<string, PluginLike>>();\n readonly #scope: { readonly transport: BotT; readonly runtime: BotRt };\n\n constructor(transport: BotT, runtime: BotRt, driver: Config['kv']) {\n this.#scope = { transport, runtime };\n this.bus = new Bus(this);\n const kvDriver = driver ?? new MemoryDriver();\n this.kv = new Kv(kvDriver, 'app');\n this.rateLimiter = new KvRateLimiter(new Kv(kvDriver, 'seedcord'));\n }\n\n /** @internal */\n public get [attachmentsSlot](): readonly Attachment[] {\n return this.#attachments;\n }\n\n /** @internal */\n public [sealSlot](): void {\n this.#sealed = true;\n }\n\n /** @internal codegen reads this to emit the `Core` augmentation */\n public get [HostPluginKeys](): readonly string[] {\n return this.#attachments.map((attachment) => attachment.key);\n }\n\n /**\n * Attaches a plugin under `key`. Read the instance back as `core[key]`. `seedcord codegen`\n * writes the `Core` augmentation that types it there.\n *\n * Put one dot in the key to nest the plugin under a group. `'services.users'` reads back as\n * `core.services.users`. Each name holds one plugin or one group.\n *\n * Startup runs each plugin's `init()` in attach order within its phase. A node bot starts in\n * `start()`. An edge bot starts on its first request.\n *\n * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.\n * Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).\n * A narrower one fails to compile here.\n *\n * @param key - Also the channel the plugin logs on, dots included.\n * @param args - Whatever your constructor takes after the host.\n * @throws A **SeedcordError** when you attach after the bot has started.\n * @throws A **SeedcordTypeError** when `key` is taken, reserved, or malformed.\n * @throws A **SeedcordTypeError** when the plugin extends `Plugin` from another copy of `@seedcord/core`.\n * @throws A **SeedcordTypeError** when the plugin's `transport` or `runtime` differs from this host and the types were bypassed.\n * @example\n * ```ts\n * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... });\n * ```\n */\n public attach<Key extends string, Ctor extends PluginCtor>(\n this: this,\n key: AttachKeyAssert<Key, this>,\n Plugin: AttachableCtor<Ctor, BotT, BotRt>,\n ...args: PluginArgs<Ctor>\n ): this & Attached<Key, InstanceType<Ctor>>;\n public attach<Key extends string, Ctor extends PluginCtor>(\n this: this,\n key: Key,\n Plugin: Ctor,\n ...args: PluginArgs<Ctor>\n ): this & Attached<Key, InstanceType<Ctor>> {\n if (this.#sealed) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);\n }\n\n const { head, leaf } = splitPluginKey(key);\n\n // runs before assertFree because some reserved channels are also host members\n if (RESERVED_KEYS.has(head)) {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginReservedChannel, [head]);\n }\n\n if (head === '' || leaf === '') {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyMalformed, [key, 'has an empty part.']);\n }\n if (leaf?.includes('.')) {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyMalformed, [key, 'has more than one dot.']);\n }\n this.#assertFree(head, leaf, key);\n\n if (!extendsThisCorePlugin(Plugin)) {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginFromOtherCore, [Plugin.name]);\n }\n\n const kv = new Kv(this.kv.driver, 'plugins', key);\n const instance = constructAttached(key, kv, () => new Plugin(this, ...args));\n this.#assertScope(Plugin.name, declaredScopeOf(instance));\n this.#attachments.push({ key, instance, spec: resolvedLifecycleSpecOf(instance) });\n\n if (leaf === undefined) {\n return Object.assign(this, { [key]: instance }) as this & Attached<Key, InstanceType<Ctor>>;\n }\n\n this.#groupFor(head)[leaf] = instance;\n return this as this & Attached<Key, InstanceType<Ctor>>;\n }\n\n #assertFree(head: string, leaf: string | undefined, key: string): void {\n if (leaf === undefined) {\n if (this.#groups.has(key)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyHoldsGroup, [key]);\n if (key in this) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyExists, [key]);\n return;\n }\n\n const group = this.#groups.get(head);\n if (!group) {\n if (head in this) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginGroupTaken, [head, key]);\n return;\n }\n if (Object.hasOwn(group, leaf)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginKeyExists, [key]);\n }\n\n // the types already reject a mismatch. plain JS and casts reach this.\n #assertScope(pluginName: string, declared: DeclaredScope): void {\n for (const axis of ['transport', 'runtime'] as const) {\n const host = this.#scope[axis];\n if (declared[axis] === 'any' || declared[axis] === host) continue;\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginScopeMismatch, [\n pluginName,\n axis,\n declared[axis],\n host\n ]);\n }\n }\n\n #groupFor(head: string): Record<string, PluginLike> {\n const existing = this.#groups.get(head);\n if (existing) return existing;\n\n // a null prototype makes a leaf called __proto__ or valueOf an ordinary key\n const group = Object.create(null) as Record<string, PluginLike>;\n this.#groups.set(head, group);\n Object.assign(this, { [head]: group });\n return group;\n }\n}\n\nexport function attachmentsOf(\n host: Pick<PluginHost<Transport, Runtime>, typeof attachmentsSlot>\n): readonly Attachment[] {\n return host[attachmentsSlot];\n}\n\nexport function sealAttachments(host: Pick<PluginHost<Transport, Runtime>, typeof sealSlot>): void {\n host[sealSlot]();\n}\n"],"mappings":"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"}