@seedcord/core 0.12.0-next.0 → 0.12.0-next.2

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 (41) hide show
  1. package/README.md +3 -3
  2. package/dist/{Bus-W1K-uMeO.mjs → Bus-VJw-QpoY.mjs} +110 -54
  3. package/dist/Bus-VJw-QpoY.mjs.map +1 -0
  4. package/dist/{CoordinatedShutdown-BVtNNSsF.d.mts → CoordinatedShutdown-fPKLWjc5.d.mts} +2 -2
  5. package/dist/{CoordinatedStartup-DA3gW8op.mjs → CoordinatedStartup-Zw_Lk7Zk.mjs} +3 -3
  6. package/dist/{CoordinatedStartup-DA3gW8op.mjs.map → CoordinatedStartup-Zw_Lk7Zk.mjs.map} +1 -1
  7. package/dist/HmrModuleHandler-Btgu8r96.mjs +165 -0
  8. package/dist/HmrModuleHandler-Btgu8r96.mjs.map +1 -0
  9. package/dist/{PluginHost-DWBgBS6Q.d.mts → PluginHost-DFnkIldj.d.mts} +10 -9
  10. package/dist/{PluginHost-CYe-UVpp.mjs → PluginHost-DTtxWRiL.mjs} +4 -4
  11. package/dist/PluginHost-DTtxWRiL.mjs.map +1 -0
  12. package/dist/{PluginLifecycle-DdwRzcDj.mjs → PluginLifecycle-hx5zUc-P.mjs} +4 -4
  13. package/dist/PluginLifecycle-hx5zUc-P.mjs.map +1 -0
  14. package/dist/{ServerHost-whpI0h4d.d.mts → ServerHost-B5o3ScCF.d.mts} +3 -3
  15. package/dist/{ServerHost-DHg-Y2_q.mjs → ServerHost-CDaEDR4C.mjs} +4 -4
  16. package/dist/{ServerHost-DHg-Y2_q.mjs.map → ServerHost-CDaEDR4C.mjs.map} +1 -1
  17. package/dist/hmr.index.d.mts +20 -20
  18. package/dist/hmr.index.mjs +1 -1
  19. package/dist/{index-Boo33hh3.d.mts → index-DGHng1IN.d.mts} +108 -193
  20. package/dist/index.d.mts +216 -71
  21. package/dist/index.mjs +215 -100
  22. package/dist/index.mjs.map +1 -1
  23. package/dist/internal.index.d.mts +204 -82
  24. package/dist/internal.index.mjs +572 -73
  25. package/dist/internal.index.mjs.map +1 -1
  26. package/dist/node-internal.index.d.mts +4 -3
  27. package/dist/node-internal.index.mjs +3 -3
  28. package/dist/node.index.d.mts +2 -2
  29. package/dist/node.index.mjs +4 -4
  30. package/dist/{phases-Dojnr-ng.d.mts → phases-C2QCSqRu.d.mts} +60 -50
  31. package/dist/plugin.index.d.mts +1 -1
  32. package/dist/plugin.index.mjs +1 -1
  33. package/dist/{subscribers-D4_HZcgw.mjs → subscribers-CGuVjbrg.mjs} +139 -253
  34. package/dist/subscribers-CGuVjbrg.mjs.map +1 -0
  35. package/package.json +10 -9
  36. package/dist/Bus-W1K-uMeO.mjs.map +0 -1
  37. package/dist/HmrModuleHandler-BMD7dg8N.mjs +0 -162
  38. package/dist/HmrModuleHandler-BMD7dg8N.mjs.map +0 -1
  39. package/dist/PluginHost-CYe-UVpp.mjs.map +0 -1
  40. package/dist/PluginLifecycle-DdwRzcDj.mjs.map +0 -1
  41. package/dist/subscribers-D4_HZcgw.mjs.map +0 -1
@@ -0,0 +1,165 @@
1
+ import { SeedcordErrorCode, isSeedcordError, paint } from "@seedcord/errors";
2
+ import { botFolder, vitePluginThrew } from "@seedcord/utils/internal";
3
+ import { formatFilePath } from "@seedcord/utils";
4
+ import { pathToFileURL } from "node:url";
5
+
6
+ //#region src/hmr/HmrModuleHandler.ts
7
+ const displayName = (unit, fallback) => typeof unit === "function" ? unit.name : fallback;
8
+ function unitNames({ handlers, middlewares }) {
9
+ return [...handlers.map((handler) => displayName(handler, "Handler")), ...middlewares.map((middleware) => displayName(middleware, "Middleware"))];
10
+ }
11
+ function hasUnits({ handlers, middlewares }) {
12
+ return handlers.length > 0 || middlewares.length > 0;
13
+ }
14
+ function addToFile(fileToUnits, file, unit) {
15
+ const units = fileToUnits.get(file);
16
+ if (units) units.add(unit);
17
+ else fileToUnits.set(file, /* @__PURE__ */ new Set([unit]));
18
+ }
19
+ /**
20
+ * Reloads THandler and optional TMiddleware modules on HMR updates.
21
+ *
22
+ * Call `handle()` from your `HmrAware.onHmr()` for standard reload behavior, or write a fully custom `onHmr()` and skip this class.
23
+ *
24
+ * This is only useful during development.
25
+ */
26
+ var HmrModuleHandler = class {
27
+ #options;
28
+ #fileToHandlers = /* @__PURE__ */ new Map();
29
+ #fileToMiddlewares = /* @__PURE__ */ new Map();
30
+ #handlerArtifacts = /* @__PURE__ */ new Map();
31
+ #blockedFiles = /* @__PURE__ */ new Set();
32
+ constructor(options) {
33
+ this.#options = {
34
+ ...options,
35
+ logger: options.logger.withChannel("hmr")
36
+ };
37
+ }
38
+ /**
39
+ * Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories.
40
+ * When that unloads something, it also retries every file that failed earlier on a duplicate name.
41
+ */
42
+ async handle(event) {
43
+ const { file, affectedModules, type } = event;
44
+ if (type === "createDir") return;
45
+ const affected = affectedModules?.length ? affectedModules : [file];
46
+ const retried = affected.some((candidate) => this.#holdsUnits(candidate)) ? [...this.#blockedFiles].filter((blocked) => blocked !== file) : [];
47
+ if (type === "delete" || type === "deleteDir") {
48
+ this.#report("Unloaded", unitNames(this.#unload(file)), [file]);
49
+ await this.#reload(retried, event);
50
+ } else await this.#reload([...affected, ...retried], event);
51
+ }
52
+ /** Links the handler to its source file so a later update can unload it. */
53
+ trackHandler(file, handler) {
54
+ addToFile(this.#fileToHandlers, file, handler);
55
+ const { getArtifacts } = this.#options.handlers;
56
+ if (getArtifacts) this.#handlerArtifacts.set(handler, getArtifacts(handler));
57
+ }
58
+ /** Links the middleware to its source file so a later update can unload it. */
59
+ trackMiddleware(file, middleware) {
60
+ addToFile(this.#fileToMiddlewares, file, middleware);
61
+ }
62
+ #holdsUnits(file) {
63
+ return this.#fileToHandlers.has(file) || this.#fileToMiddlewares.has(file);
64
+ }
65
+ #inScope(file) {
66
+ const { handlers, middlewares } = this.#options;
67
+ if (botFolder(handlers.dir).holdsModule(file)) return true;
68
+ return middlewares ? botFolder(middlewares.dir).holdsModule(file) : false;
69
+ }
70
+ async #reload(candidates, { type, rollback = true }) {
71
+ const files = [...new Set(candidates)].filter((file) => this.#inScope(file));
72
+ const lastGood = new Map(files.map((file) => [file, this.#unload(file)]));
73
+ const startedAt = Date.now();
74
+ const names = [];
75
+ const touched = [];
76
+ for (const [file, units] of lastGood) {
77
+ const loaded = await this.#load(file, units, rollback);
78
+ if (loaded.length === 0) continue;
79
+ names.push(...loaded);
80
+ touched.push(file);
81
+ }
82
+ this.#report(type === "create" ? "Registered" : "Reloaded", names, touched, Date.now() - startedAt);
83
+ }
84
+ #report(verb, names, files, elapsedMs) {
85
+ if (names.length === 0) return;
86
+ const [only, ...others] = files;
87
+ const from = only && others.length === 0 ? formatFilePath(only) : `${String(files.length)} files`;
88
+ const timing = elapsedMs === void 0 ? "" : ` ${paint.mute("in")} ${String(elapsedMs)}ms`;
89
+ this.#options.logger.debug(`${verb} ${paint.sky.bold(names.join(", "))} from ${paint.mute(from)}${timing}`);
90
+ }
91
+ #unload(file) {
92
+ const handlers = [...this.#fileToHandlers.get(file) ?? []];
93
+ const middlewares = [...this.#fileToMiddlewares.get(file) ?? []];
94
+ for (const handler of handlers) {
95
+ this.#options.handlers.unregister(handler, this.#handlerArtifacts.get(handler));
96
+ this.#handlerArtifacts.delete(handler);
97
+ }
98
+ for (const middleware of middlewares) this.#options.middlewares?.unregister(middleware);
99
+ this.#fileToHandlers.delete(file);
100
+ this.#fileToMiddlewares.delete(file);
101
+ this.#blockedFiles.delete(file);
102
+ return {
103
+ handlers,
104
+ middlewares
105
+ };
106
+ }
107
+ async #load(file, lastGood, rollback) {
108
+ try {
109
+ const units = await this.#importUnits(file);
110
+ this.#register(file, units);
111
+ return unitNames(units);
112
+ } catch (error) {
113
+ const path = formatFilePath(file);
114
+ this.#options.logger.error(`Failed to reload file: ${path}`, vitePluginThrew(error) ?? error);
115
+ this.#abandon(file, error);
116
+ }
117
+ if (rollback && hasUnits(lastGood)) this.#rollBack(file, lastGood);
118
+ return [];
119
+ }
120
+ async #importUnits(file) {
121
+ const { handlers, middlewares } = this.#options;
122
+ let fileUrl = pathToFileURL(file).href;
123
+ if (process.env.VITEST === "true") fileUrl += `?update=${Date.now()}`;
124
+ const imported = await import(
125
+ /* @vite-ignore */
126
+ fileUrl
127
+ );
128
+ const units = {
129
+ handlers: [],
130
+ middlewares: []
131
+ };
132
+ for (const val of Object.values(imported)) if (handlers.is(val)) units.handlers.push(val);
133
+ else if (middlewares?.is(val)) units.middlewares.push(val);
134
+ return units;
135
+ }
136
+ #register(file, { handlers, middlewares }) {
137
+ for (const handler of handlers) {
138
+ this.#options.handlers.register(handler, file);
139
+ this.trackHandler(file, handler);
140
+ }
141
+ for (const middleware of middlewares) {
142
+ this.#options.middlewares?.register(middleware, file);
143
+ this.trackMiddleware(file, middleware);
144
+ }
145
+ }
146
+ #abandon(file, error) {
147
+ this.#unload(file);
148
+ if (isSeedcordError(error, "SeedcordTypeError", SeedcordErrorCode.CoreDuplicateName)) this.#blockedFiles.add(file);
149
+ }
150
+ #rollBack(file, lastGood) {
151
+ const { logger } = this.#options;
152
+ const path = paint.mute(formatFilePath(file));
153
+ try {
154
+ this.#register(file, lastGood);
155
+ logger.warn(`${paint.amber.bold("Rolled back")} ${path} to the last-good version`);
156
+ } catch (error) {
157
+ logger.error(`Failed to roll back ${path}`, error);
158
+ this.#abandon(file, error);
159
+ }
160
+ }
161
+ };
162
+
163
+ //#endregion
164
+ export { HmrModuleHandler as t };
165
+ //# sourceMappingURL=HmrModuleHandler-Btgu8r96.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HmrModuleHandler-Btgu8r96.mjs","names":[],"sources":["../src/hmr/HmrModuleHandler.ts"],"sourcesContent":["import { pathToFileURL } from 'node:url';\n\nimport { isSeedcordError, paint, SeedcordErrorCode } from '@seedcord/errors';\nimport { formatFilePath } from '@seedcord/utils';\nimport { botFolder, vitePluginThrew } from '@seedcord/utils/internal';\n\nimport type { Logger } from '@seedcord/logger';\nimport type { HmrUpdateEvent } from '@seedcord/types';\n\ninterface HmrFolderOptions<TUnit> {\n dir: string;\n is: (val: unknown) => val is TUnit;\n register: (unit: TUnit, file: string) => void;\n}\n\ninterface HmrHandlerOptions<THandler, TArtifacts> extends HmrFolderOptions<THandler> {\n unregister: (handler: THandler, artifacts?: TArtifacts) => void;\n /** Called each time a handler is tracked. `unregister` receives the result as its second argument. */\n getArtifacts?: (handler: THandler) => TArtifacts;\n}\n\ninterface HmrMiddlewareOptions<TMiddleware> extends HmrFolderOptions<TMiddleware> {\n unregister: (middleware: TMiddleware) => void;\n}\n\ninterface HmrModuleHandlerOptions<THandler, TMiddleware = void, TArtifacts = unknown> {\n handlers: HmrHandlerOptions<THandler, TArtifacts>;\n middlewares?: HmrMiddlewareOptions<TMiddleware>;\n logger: Logger;\n}\n\ninterface FileUnits<THandler, TMiddleware> {\n handlers: THandler[];\n middlewares: TMiddleware[];\n}\n\nconst displayName = (unit: unknown, fallback: string): string => (typeof unit === 'function' ? unit.name : fallback);\n\nfunction unitNames({ handlers, middlewares }: FileUnits<unknown, unknown>): string[] {\n return [\n ...handlers.map((handler) => displayName(handler, 'Handler')),\n ...middlewares.map((middleware) => displayName(middleware, 'Middleware'))\n ];\n}\n\nfunction hasUnits({ handlers, middlewares }: FileUnits<unknown, unknown>): boolean {\n return handlers.length > 0 || middlewares.length > 0;\n}\n\nfunction addToFile<TUnit>(fileToUnits: Map<string, Set<TUnit>>, file: string, unit: TUnit): void {\n const units = fileToUnits.get(file);\n if (units) units.add(unit);\n else fileToUnits.set(file, new Set([unit]));\n}\n\n/**\n * Reloads THandler and optional TMiddleware modules on HMR updates.\n *\n * Call `handle()` from your `HmrAware.onHmr()` for standard reload behavior, or write a fully custom `onHmr()` and skip this class.\n *\n * This is only useful during development.\n */\nexport class HmrModuleHandler<THandler, TMiddleware = void, TArtifacts = unknown> {\n readonly #options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>;\n // no import.meta.hot.data stash needed because a leaf reload never reconstructs the singleton holding these Maps\n readonly #fileToHandlers = new Map<string, Set<THandler>>();\n readonly #fileToMiddlewares = new Map<string, Set<TMiddleware>>();\n readonly #handlerArtifacts = new Map<THandler, TArtifacts>();\n // a moved handler's new file can load before its old file unloads\n readonly #blockedFiles = new Set<string>();\n\n constructor(options: HmrModuleHandlerOptions<THandler, TMiddleware, TArtifacts>) {\n this.#options = { ...options, logger: options.logger.withChannel('hmr') };\n }\n\n /**\n * Unloads or reloads the modules affected by an {@link HmrUpdateEvent}, scoped to the handler and middleware directories.\n * When that unloads something, it also retries every file that failed earlier on a duplicate name.\n */\n public async handle(event: HmrUpdateEvent): Promise<void> {\n const { file, affectedModules, type } = event;\n\n // a directory is never importable. its files arrive as their own create events\n if (type === 'createDir') return;\n\n const affected = affectedModules?.length ? affectedModules : [file];\n const freesNames = affected.some((candidate) => this.#holdsUnits(candidate));\n const retried = freesNames ? [...this.#blockedFiles].filter((blocked) => blocked !== file) : [];\n\n if (type === 'delete' || type === 'deleteDir') {\n this.#report('Unloaded', unitNames(this.#unload(file)), [file]);\n await this.#reload(retried, event);\n } else {\n await this.#reload([...affected, ...retried], event);\n }\n }\n\n /** Links the handler to its source file so a later update can unload it. */\n public trackHandler(file: string, handler: THandler): void {\n addToFile(this.#fileToHandlers, file, handler);\n\n const { getArtifacts } = this.#options.handlers;\n if (getArtifacts) this.#handlerArtifacts.set(handler, getArtifacts(handler));\n }\n\n /** Links the middleware to its source file so a later update can unload it. */\n public trackMiddleware(file: string, middleware: TMiddleware): void {\n addToFile(this.#fileToMiddlewares, file, middleware);\n }\n\n #holdsUnits(file: string): boolean {\n return this.#fileToHandlers.has(file) || this.#fileToMiddlewares.has(file);\n }\n\n #inScope(file: string): boolean {\n const { handlers, middlewares } = this.#options;\n\n if (botFolder(handlers.dir).holdsModule(file)) return true;\n return middlewares ? botFolder(middlewares.dir).holdsModule(file) : false;\n }\n\n async #reload(candidates: Iterable<string>, { type, rollback = true }: HmrUpdateEvent): Promise<void> {\n const files = [...new Set(candidates)].filter((file) => this.#inScope(file));\n // every file unloads before any file loads, so no file in this save can block another\n const lastGood = new Map(files.map((file) => [file, this.#unload(file)]));\n\n const startedAt = Date.now();\n const names: string[] = [];\n const touched: string[] = [];\n\n for (const [file, units] of lastGood) {\n const loaded = await this.#load(file, units, rollback);\n if (loaded.length === 0) continue;\n\n names.push(...loaded);\n touched.push(file);\n }\n\n this.#report(type === 'create' ? 'Registered' : 'Reloaded', names, touched, Date.now() - startedAt);\n }\n\n // one line per save, since one edit can reload every module that imports the file\n #report(verb: string, names: string[], files: string[], elapsedMs?: number): void {\n if (names.length === 0) return;\n\n const [only, ...others] = files;\n const from = only && others.length === 0 ? formatFilePath(only) : `${String(files.length)} files`;\n const timing = elapsedMs === undefined ? '' : ` ${paint.mute('in')} ${String(elapsedMs)}ms`;\n this.#options.logger.debug(`${verb} ${paint.sky.bold(names.join(', '))} from ${paint.mute(from)}${timing}`);\n }\n\n // these are the same class objects, which is how a live db connection survives a rollback\n #unload(file: string): FileUnits<THandler, TMiddleware> {\n const handlers = [...(this.#fileToHandlers.get(file) ?? [])];\n const middlewares = [...(this.#fileToMiddlewares.get(file) ?? [])];\n\n for (const handler of handlers) {\n this.#options.handlers.unregister(handler, this.#handlerArtifacts.get(handler));\n this.#handlerArtifacts.delete(handler);\n }\n for (const middleware of middlewares) {\n this.#options.middlewares?.unregister(middleware);\n }\n\n this.#fileToHandlers.delete(file);\n this.#fileToMiddlewares.delete(file);\n this.#blockedFiles.delete(file);\n return { handlers, middlewares };\n }\n\n async #load(file: string, lastGood: FileUnits<THandler, TMiddleware>, rollback: boolean): Promise<string[]> {\n try {\n const units = await this.#importUnits(file);\n this.#register(file, units);\n return unitNames(units);\n } catch (error) {\n const path = formatFilePath(file);\n this.#options.logger.error(`Failed to reload file: ${path}`, vitePluginThrew(error) ?? error);\n this.#abandon(file, error);\n }\n\n if (rollback && hasUnits(lastGood)) this.#rollBack(file, lastGood);\n return [];\n }\n\n async #importUnits(file: string): Promise<FileUnits<THandler, TMiddleware>> {\n const { handlers, middlewares } = this.#options;\n\n let fileUrl = pathToFileURL(file).href;\n // vitest has no real vite server managing the module graph\n if (process.env.VITEST === 'true') fileUrl += `?update=${Date.now()}`;\n\n // justified: a dynamic import resolves to an untyped export map\n const imported = (await import(/* @vite-ignore */ fileUrl)) as Record<string, unknown>;\n\n const units: FileUnits<THandler, TMiddleware> = { handlers: [], middlewares: [] };\n for (const val of Object.values(imported)) {\n if (handlers.is(val)) units.handlers.push(val);\n else if (middlewares?.is(val)) units.middlewares.push(val);\n }\n return units;\n }\n\n #register(file: string, { handlers, middlewares }: FileUnits<THandler, TMiddleware>): void {\n for (const handler of handlers) {\n this.#options.handlers.register(handler, file);\n this.trackHandler(file, handler);\n }\n for (const middleware of middlewares) {\n this.#options.middlewares?.register(middleware, file);\n this.trackMiddleware(file, middleware);\n }\n }\n\n // a failed registration can leave part of the file registered\n #abandon(file: string, error: unknown): void {\n this.#unload(file);\n if (isSeedcordError(error, 'SeedcordTypeError', SeedcordErrorCode.CoreDuplicateName)) {\n this.#blockedFiles.add(file);\n }\n }\n\n // a typo keeps the last-good units live until the next good save\n #rollBack(file: string, lastGood: FileUnits<THandler, TMiddleware>): void {\n const { logger } = this.#options;\n const path = paint.mute(formatFilePath(file));\n\n try {\n this.#register(file, lastGood);\n logger.warn(`${paint.amber.bold('Rolled back')} ${path} to the last-good version`);\n } catch (error) {\n logger.error(`Failed to roll back ${path}`, error);\n this.#abandon(file, error);\n }\n }\n}\n"],"mappings":"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"}
@@ -1,8 +1,9 @@
1
- import { F as Kv, a as Bus, i as CoreBase, n as StartupPhase, r as TaskShutdownPhase } from "./phases-Dojnr-ng.mjs";
1
+ import { P as Kv, a as Bus, i as CoreBase, n as StartupPhase, r as TaskShutdownPhase } from "./phases-C2QCSqRu.mjs";
2
2
  import { Logger } from "@seedcord/logger";
3
3
  import { REST } from "@discordjs/rest";
4
4
  import { HostPluginKeys } from "@seedcord/types/internal";
5
5
  import { Config, FrameworkChannel, HmrAware, HmrUpdateEvent, IRateLimiter, Tail, TypedExclude } from "@seedcord/types";
6
+ import { Promisable } from "type-fest";
6
7
  //#region src/plugin/brands.d.ts
7
8
  declare const TransportBrand: unique symbol;
8
9
  declare const RuntimeBrand: unique symbol;
@@ -124,7 +125,7 @@ type RuntimeAssert<Plug, BotRt extends Runtime> = BrandRuntime<Plug> extends 'an
124
125
  //#endregion
125
126
  //#region src/plugin/Plugin.d.ts
126
127
  interface Initializeable {
127
- init(): Promise<void>;
128
+ init(): Promisable<void>;
128
129
  }
129
130
  /** @internal */
130
131
  declare const resolvedSpecSlot: unique symbol;
@@ -138,7 +139,7 @@ declare const FixedScope: unique symbol;
138
139
  * Base class for a seedcord plugin. Extend it and implement `init()`. `this.core` is the running
139
140
  * bot.
140
141
  *
141
- * Your constructor takes the host first and passes it to `super()`. Add your own parameters after
142
+ * Take the host as your constructor's first parameter and pass it to `super()`. Add your own parameters after
142
143
  * it. `attach()` types them at the call site.
143
144
  *
144
145
  * A plugin that narrows `transport` or `runtime` in `Opts` passes the same values to `super()`.
@@ -203,14 +204,14 @@ declare abstract class Plugin<Opts extends PluginOptions = {}, TCore extends Cor
203
204
  * Runs in `StartupPhase.Configuration` by default.
204
205
  * Move it to a different phase with the {@link PluginLifecycleSpec}.
205
206
  */
206
- abstract init(): Promise<void>;
207
+ abstract init(): Promisable<void>;
207
208
  /**
208
209
  * Runs in the Ready phase, after every attached plugin's `init()` has resolved. The other Ready
209
210
  * tasks run alongside it, including the http server binding its port.
210
211
  *
211
212
  * Override it when your work needs a logged-in client or a resolved application id.
212
213
  */
213
- ready?(): Promise<void>;
214
+ ready?(): Promisable<void>;
214
215
  /**
215
216
  * Runs during teardown, in `ShutdownPhase.Disconnect` by default. seedcord skips it when `init()`
216
217
  * has thrown.
@@ -218,7 +219,7 @@ declare abstract class Plugin<Opts extends PluginOptions = {}, TCore extends Cor
218
219
  * When `init()` outlasts its timeout, seedcord calls this once that `init()` resolves, outside any
219
220
  * shutdown phase. The process must still be alive for that call to happen.
220
221
  */
221
- dispose?(): Promise<void>;
222
+ dispose?(): Promisable<void>;
222
223
  /**
223
224
  * Returns data that `seedcord codegen` writes into the bot's `seedcord-gen.ts` as a type.
224
225
  * Read it back with `Generated<typeof YourPlugin>`. Return plain data, or classes and objects
@@ -236,7 +237,7 @@ declare abstract class Plugin<Opts extends PluginOptions = {}, TCore extends Cor
236
237
  */
237
238
  protected rejectOptions(reason: string): never;
238
239
  /** Override to reload plugin state on an HMR update. */
239
- onHmr(_event: HmrUpdateEvent): Promise<void>;
240
+ onHmr(_event: HmrUpdateEvent): Promisable<void>;
240
241
  /**
241
242
  * Registers critical file patterns that should trigger a full restart when changed in Dev HMR.
242
243
  * @param patterns - Glob patterns. A relative one resolves against `root` from `seedcord.config.ts`.
@@ -301,7 +302,7 @@ declare abstract class PluginHost<BotT extends Transport, BotRt extends Runtime>
301
302
  * `start()`. An edge bot starts on its first request.
302
303
  *
303
304
  * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.
304
- * Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).
305
+ * Take `CoreBase` as your constructor's first parameter. `attach()` passes it, so you don't.
305
306
  * A narrower one fails to compile here.
306
307
  *
307
308
  * @param key - Also the channel the plugin logs on, dots included.
@@ -321,4 +322,4 @@ declare function attachmentsOf(host: Pick<PluginHost<Transport, Runtime>, typeof
321
322
  declare function sealAttachments(host: Pick<PluginHost<Transport, Runtime>, typeof sealSlot>): void;
322
323
  //#endregion
323
324
  export { FixedScope as a, PluginArgs as c, Runtime as d, Transport as f, sealAttachments as i, PluginCtor as l, PluginHost as n, Initializeable as o, PluginLifecycleSpec as p, attachmentsOf as r, Plugin as s, Attachment as t, PluginOptions as u };
324
- //# sourceMappingURL=PluginHost-DWBgBS6Q.d.mts.map
325
+ //# sourceMappingURL=PluginHost-DFnkIldj.d.mts.map
@@ -1,4 +1,4 @@
1
- import { _ as MemoryDriver, t as Bus, v as ShutdownPhase, y as StartupPhase } from "./Bus-W1K-uMeO.mjs";
1
+ import { b as StartupPhase, t as Bus, v as MemoryDriver, y as ShutdownPhase } from "./Bus-VJw-QpoY.mjs";
2
2
  import { SeedcordErrorCode, isSeedcordError } from "@seedcord/errors";
3
3
  import { SeedcordError, SeedcordRangeError, SeedcordTypeError } from "@seedcord/errors/internal";
4
4
  import { FRAMEWORK_CHANNELS, Logger } from "@seedcord/logger";
@@ -55,7 +55,7 @@ const FixedScope = Symbol("seedcord:plugin:fixed-scope");
55
55
  * Base class for a seedcord plugin. Extend it and implement `init()`. `this.core` is the running
56
56
  * bot.
57
57
  *
58
- * Your constructor takes the host first and passes it to `super()`. Add your own parameters after
58
+ * Take the host as your constructor's first parameter and pass it to `super()`. Add your own parameters after
59
59
  * it. `attach()` types them at the call site.
60
60
  *
61
61
  * A plugin that narrows `transport` or `runtime` in `Opts` passes the same values to `super()`.
@@ -548,5 +548,5 @@ function sealAttachments(host) {
548
548
  }
549
549
 
550
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
551
+ export { KeyedQueue as a, pluginLoggerOf as c, splitPluginKey as i, getDevChannel as l, attachmentsOf as n, FixedScope as o, sealAttachments as r, Plugin as s, PluginHost as t, setDevChannel as u };
552
+ //# sourceMappingURL=PluginHost-DTtxWRiL.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PluginHost-DTtxWRiL.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';\nimport type { Promisable } from 'type-fest';\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(): Promisable<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 * Take the host as your constructor's first parameter and pass 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(): Promisable<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?(): Promisable<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?(): Promisable<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): Promisable<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 that 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 * Take `CoreBase` as your constructor's first parameter. `attach()` passes it, so you don't.\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":";;;;;;;AAKA,IAAI;;AAGJ,SAAgB,cAAc,MAAoC;CAC9D,UAAU;AACd;;AAGA,SAAgB,gBAAwC;CACpD,OAAO;AACX;;;;ACsBA,MAAM,0BAA0B;AAChC,MAAM,2BAA2B;AACjC,MAAM,6BAA6B;AAEnC,SAAS,UACL,OACA,UACA,UACA,YACM;CACN,IAAI,aAAa,QAAW,OAAO;CACnC,IAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,YAAY,GAC1C,MAAM,IAAI,mBAAmB,kBAAkB,yBAAyB,CACpE,GAAG,WAAW,eAAe,MAAM,WACnC,QACJ,CAAC;CAEL,OAAO;AACX;;AAGA,SAAgB,qBACZ,MACA,YAC2B;CAC3B,OAAO;EACH,MAAM;GACF,OAAO,MAAM,MAAM;GACnB,SAAS,UAAU,QAAQ,MAAM,MAAM,SAAS,yBAAyB,UAAU;EACvF;EACA,OAAO,EACH,SAAS,UAAU,SAAS,MAAM,OAAO,SAAS,0BAA0B,UAAU,EAC1F;EACA,SAAS;GACL,OAAO,MAAM,SAAS;GACtB,SAAS,UAAU,WAAW,MAAM,SAAS,SAAS,4BAA4B,UAAU;EAChG;CACJ;AACJ;;;;ACzCA,IAAI;;AAOJ,MAAM,mBAAmB,OAAO,sBAAsB;;AAEtD,MAAM,aAAa,OAAO,wBAAwB;;AAElD,MAAM,YAAY,OAAO,uBAAuB;;AAEhD,MAAa,aAAa,OAAO,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkC9D,IAAsB,SAAtB,MAMA;CAwByB;;CAtBrB,QAA2B,cAAsC,CAAC;;CASlE,CAAU;;CAEV,CAAU;;CAEV,CAAU;;CAGV,AAAmB;CAEnB,AAAS;CAGT,YACI,AAAiB,MACjB,GAAG,CAAC,OACN;EAFmB;EAIjB,MAAM,aAAa;EACnB,YAAY;EACZ,KAAK,MAAM,YAAY;EAEvB,KAAK,SAAS,IAAI,OAAO,KAAK,YAAY,MAAM,cAAc,EAAE,SAAS,WAAW,IAAI,CAAC;EACzF,KAAK,cAAc,KAAK;EACxB,KAAK,oBAAoB,qBAAqB,MAAM,KAAK,YAAY,IAAI;EAEzE,MAAM,SAAqE;EAC3E,MAAM,QAAQ,WAAW;EACzB,KAAK,aAAa;GACd,WAAW,MAAM,aAAa,QAAQ,aAAa;GACnD,SAAS,MAAM,WAAW,QAAQ,WAAW;EACjD;CACJ;;CAGA,IAAc,OAAc;EAExB,OAAO,KAAK;CAChB;;;;;;;;;;;;;CAcA,IAAc,KAAS;EACnB,IAAI,CAAC,KAAK,KACN,MAAM,IAAI,kBAAkB,kBAAkB,2BAA2B,CAAC,KAAK,YAAY,IAAI,CAAC;EACpG,OAAO,KAAK;CAChB;;;;;;;CAyCA,AAAU,cAAc,QAAuB;EAC3C,MAAM,IAAI,kBAAkB,kBAAkB,uBAAuB,CAAC,KAAK,YAAY,MAAM,MAAM,CAAC;CACxG;;CAGA,AAAO,MAAM,QAA0C;EACnD,OAAO,QAAQ,QAAQ;CAC3B;;;;;CAMA,AAAU,sBAAsB,UAA0B;EACtD,cAAc,CAAC,EAAE,KAAK,oCAAoC,EAAE,SAAS,CAAC;CAC1E;AACJ;;AAGA,SAAgB,wBAAwB,QAAiD;CACrF,OAAO,OAAO;AAClB;;AAGA,SAAgB,sBAAsB,MAA2B;CAC7D,OAAO,KAAK,qBAAqB;AACrC;;AAGA,SAAgB,eAAe,QAA4B;CACvD,OAAO,OAAO;AAClB;;AAGA,SAAgB,gBAAgB,QAAmC;CAC/D,OAAO,OAAO;AAClB;;AAGA,SAAgB,kBAA2B,KAAa,IAAQ,WAAmC;CAC/F,YAAY;EAAE;EAAK;CAAG;CACtB,IAAI;EACA,OAAO,UAAU;CACrB,UAAU;EACN,YAAY;CAChB;AACJ;;;;ACvOA,SAAS,WAAW,WAAsB,KAAa,OAAyB;CAC5E,IAAI,gBAAgB,KAAK,GAAG,OAAO;CACnC,OAAO,IAAI,cAAc,kBAAkB,oBAAoB,CAAC,WAAW,GAAG,GAAG,EAAE,OAAO,MAAM,CAAC;AACrG;AAEA,eAAe,MAAe,WAAsB,KAAa,KAAkD;CAC/G,IAAI;EACA,OAAO,MAAM,IAAI;CACrB,SAAS,OAAO;EACZ,MAAM,WAAW,WAAW,KAAK,KAAK;CAC1C;AACJ;AAEA,IAAa,gBAAb,MAA+C;CACd;CAA7B,YAAY,AAAiB,QAAkB;EAAlB;CAAmB;CAEhD,MAAa,IAAI,KAA0C;EACvD,OAAO,MAAM,QAAQ,WAAW,KAAK,OAAO,IAAI,GAAG,CAAC;CACxD;CAEA,MAAa,IAAI,KAAa,OAAe,OAA+B;EACxE,MAAM,MAAM,SAAS,WAAW,KAAK,OAAO,IAAI,KAAK,OAAO,KAAK,CAAC;CACtE;CAEA,MAAa,OAAO,KAA4B;EAC5C,MAAM,MAAM,UAAU,WAAW,KAAK,OAAO,OAAO,GAAG,CAAC;CAC5D;CAEA,OAAc,QAAQ,QAAwC;EAC1D,IAAI;GACA,WAAW,MAAM,SAAS,KAAK,OAAO,QAAQ,MAAM,GAAG,MAAM;EACjE,SAAS,OAAO;GACZ,MAAM,WAAW,QAAQ,QAAQ,KAAK;EAC1C;CACJ;AACJ;;;;AC5CA,IAAa,aAAb,MAAwB;CACpB,AAAiB,wBAAQ,IAAI,IAA2B;CAExD,AAAO,IAAa,KAAa,MAAgD;EAC7E,MAAM,OAAO,KAAK,MAAM,IAAI,GAAG,KAAK,QAAQ,QAAQ,EAAC,CAAE,KAAK,IAAI;EAChE,MAAM,OAAO,IAAI,WACP,cACA,MACV;EACA,KAAK,MAAM,IAAI,KAAK,IAAI;EACxB,AAAK,KAAK,WAAW;GACjB,IAAI,KAAK,MAAM,IAAI,GAAG,MAAM,MAAM,KAAK,MAAM,OAAO,GAAG;EAC3D,CAAC;EACD,OAAO;CACX;AACJ;;;;;;;;;;;;;;;;;;;;;ACeA,IAAa,eAAb,MAAmC;CAC/B,AAAS;CACT,AAAS;CACT,AAAS;CAET,YAAY,QAAkB,QAAgB,SAAqB;EAC/D,KAAK,UAAU;EACf,KAAK,UAAU;EACf,KAAK,WAAW;CACpB;;CAGA,MAAa,IAAI,IAAkD;EAC/D,MAAM,MAAM,KAAK,UAAU;EAC3B,MAAM,MAAM,MAAM,KAAK,QAAQ,IAAI,GAAG;EACtC,MAAM,QAAQ,KAAK,WAAW,GAAG;EACjC,IAAI,QAAQ,UAAa,UAAU,QAAW,MAAM,KAAK,eAAe,GAAG;EAC3E,OAAO;CACX;;;;;;CAOA,MAAa,IAAI,IAAY,OAAgB,OAA+B;EACxE,MAAM,MAAM,KAAK,UAAU;EAC3B,MAAM,KAAK,SAAS,IAAI,KAAK,YAAY,KAAK,OAAO,KAAK,OAAO,KAAK,CAAC;CAC3E;;CAGA,MAAa,OAAO,IAA2B;EAC3C,MAAM,MAAM,KAAK,UAAU;EAC3B,MAAM,KAAK,SAAS,IAAI,KAAK,YAAY,KAAK,QAAQ,OAAO,GAAG,CAAC;CACrE;;;;;;;;;;;;;;;;;CAkBA,MAAa,OACT,IACA,MACA,OACgB;EAChB,MAAM,MAAM,KAAK,UAAU;EAC3B,OAAO,KAAK,SAAS,IAAI,KAAK,YAAY;GACtC,MAAM,QAAQ,KAAK,KAAK,WAAW,MAAM,KAAK,QAAQ,IAAI,GAAG,CAAC,CAAC;GAC/D,MAAM,KAAK,OAAO,KAAK,OAAO,OAAO,UAAU,aAAa,MAAM,KAAK,IAAI,KAAK;GAChF,OAAO;EACX,CAAC;CACL;;;;;;;;;;;;CAaA,OAAc,UAAgE;EAC1E,MAAM,UAAoB,CAAC;EAC3B,IAAI;GACA,WAAW,MAAM,CAAC,KAAK,QAAQ,KAAK,QAAQ,QAAQ,KAAK,OAAO,GAAG;IAC/D,MAAM,QAAQ,KAAK,WAAW,GAAG;IACjC,IAAI,UAAU,QAAW,QAAQ,KAAK,GAAG;SACpC,MAAM,CAAC,IAAI,MAAM,KAAK,QAAQ,MAAM,GAAG,KAAK;GACrD;EACJ,UAAU;GAEN,MAAM,QAAQ,IAAI,QAAQ,IAAI,OAAO,QAAQ,KAAK,eAAe,GAAG,CAAC,CAAC;EAC1E;CACJ;CAEA,MAAM,OAAO,KAAa,OAAgB,OAA0C;EAChF,MAAM,WAA8B,UAAU,SAAY,EAAE,MAAM,IAAI;GAAE;GAAO,WAAW,KAAK,IAAI,IAAI;EAAM;EAC7G,MAAM,KAAK,QAAQ,IAAI,KAAK,KAAK,UAAU,QAAQ,GAAG,KAAK;CAC/D;CAGA,MAAM,eAAe,KAA4B;EAC7C,MAAM,KAAK,SAAS,IAAI,KAAK,YAAY;GACrC,MAAM,MAAM,MAAM,KAAK,QAAQ,IAAI,GAAG;GACtC,IAAI,QAAQ,UAAa,KAAK,WAAW,GAAG,MAAM,QAAW,MAAM,KAAK,QAAQ,OAAO,GAAG;EAC9F,CAAC;CACL;CAEA,WAAW,KAAsD;EAC7D,IAAI,QAAQ,QAAW,OAAO;EAE9B,MAAM,WAAW,KAAK,MAAM,GAAG;EAC/B,IAAI,SAAS,cAAc,UAAa,SAAS,aAAa,KAAK,IAAI,GAAG,OAAO;EACjF,OAAO,SAAS;CACpB;AACJ;;;;AC7GA,SAAS,UAAU,MAAiC;CAChD,OAAO,KAAK,KAAK,YAAY,GAAG,mBAAmB,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE;AAC3E;;;;;AAMA,IAAa,KAAb,MAAgB;;CAEZ,AAAgB;CAChB,AAAS;CACT,AAAS;CACT,AAAS,WAAW,IAAI,WAAW;CAEnC,YAAY,QAA0B,GAAG,MAAgB;EACrD,KAAK,SAAS;EACd,KAAK,WAAW,IAAI,cAAc,MAAM;EACxC,KAAK,QAAQ;CACjB;;;;;CAMA,AAAO,WAAoB,MAAsD;EAC7E,OAAO,IAAI,aAAsB,KAAK,UAAU,UAAU,CAAC,GAAG,KAAK,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ;CACnG;AACJ;;;;ACvDA,SAAS,cAAc,MAAyB,KAA8B;CAE1E,MAAM,UAAU,KAAK,QAAQ,UAAU,QAAQ,KAAK,IAAI,UAAU,GAAG,GAAG,OAAO,iBAAiB;CAChG,OAAO;EAAE,SAAS;EAAM;EAAS,WAAW;EAAG,cAAc,UAAU;CAAI;AAC/E;AAEA,SAAS,WAAW,WAAoC;CACpD,OAAO;EAAE,SAAS;EAAO,SAAS;EAAc;EAAW,cAAc;CAAE;AAC/E;AAGA,SAAS,QAAQ,QAAiC;CAC9C,OAAO,KAAK,IAAI,GAAG,OAAO,SAAS,CAAC;AACxC;AAEA,SAAS,SAAS,QAAuC,KAAuB;CAC5E,QAAQ,UAAU,CAAC,EAAC,CAAE,QAAQ,QAAQ,MAAM,GAAG;AACnD;AAEA,IAAa,gBAAb,MAAmD;CAC/C,AAAiB;CAEjB,YAAY,IAAQ;EAChB,KAAK,OAAO,GAAG,WAAqB,YAAY;CACpD;CAEA,MAAa,OAAO,KAAa,QAAmD;EAChF,MAAM,QAAQ,QAAQ,MAAM;EAE5B,IAAI;EACJ,MAAM,KAAK,KAAK,OACZ,MACC,WAAW;GAER,MAAM,MAAM,KAAK,IAAI;GACrB,MAAM,OAAO,SAAS,QAAQ,GAAG;GACjC,MAAM,UAAU,KAAK,UAAU;GAC/B,IAAI,CAAC,SAAS,KAAK,KAAK,MAAM,OAAO,QAAQ;GAC7C,SAAS,UAAU,cAAc,MAAM,GAAG,IAAI,WAAW,QAAQ,KAAK,MAAM;GAC5E,OAAO;EACX,IACC,SAAS,KAAK,QAAQ,QAAQ,QAAQ,KAAK,IAAI,QAAQ,GAAG,GAAG,CAAC,IAAI,KAAK,IAAI,CAChF;EACA,OAAO;CACX;CAEA,MAAa,KAAK,KAAa,QAAmD;EAC9E,MAAM,SAAS,MAAM,KAAK,KAAK,IAAI,GAAG;EACtC,MAAM,MAAM,KAAK,IAAI;EACrB,MAAM,QAAQ,QAAQ,MAAM;EAC5B,MAAM,OAAO,SAAS,QAAQ,GAAG;EACjC,OAAO,KAAK,UAAU,QAAQ,cAAc,MAAM,GAAG,IAAI,WAAW,QAAQ,KAAK,MAAM;CAC3F;CAEA,MAAa,MAAM,KAA4B;EAC3C,MAAM,KAAK,KAAK,OAAO,GAAG;CAC9B;AACJ;;;;ACvDA,SAAgB,eAAe,KAA6B;CACxD,MAAM,MAAM,IAAI,QAAQ,GAAG;CAC3B,OAAO,QAAQ,KAAK;EAAE,MAAM;EAAK,MAAM;CAAU,IAAI;EAAE,MAAM,IAAI,MAAM,GAAG,GAAG;EAAG,MAAM,IAAI,MAAM,MAAM,CAAC;CAAE;AAC7G;;;;ACiBA,MAAM,gBAAqC,IAAI,IAAI,kBAAkB;AAErE,MAAM,kBAAkB,OAAO,2BAA2B;AAC1D,MAAM,WAAW,OAAO,oBAAoB;;AAG5C,IAAsB,aAAtB,MAAoG;;CAMhG,AAAgB;;CAEhB,AAAgB;;CAEhB,AAAgB;CAEhB,UAAU;CACV,AAAS,eAA6B,CAAC;CACvC,AAAS,0BAAU,IAAI,IAAwC;CAC/D,AAAS;CAET,YAAY,WAAiB,SAAgB,QAAsB;EAC/D,KAAK,SAAS;GAAE;GAAW;EAAQ;EACnC,KAAK,MAAM,IAAI,IAAI,IAAI;EACvB,MAAM,WAAW,UAAU,IAAI,aAAa;EAC5C,KAAK,KAAK,IAAI,GAAG,UAAU,KAAK;EAChC,KAAK,cAAc,IAAI,cAAc,IAAI,GAAG,UAAU,UAAU,CAAC;CACrE;;CAGA,KAAY,mBAA0C;EAClD,OAAO,KAAK;CAChB;;CAGA,CAAQ,YAAkB;EACtB,KAAK,UAAU;CACnB;;CAGA,KAAY,kBAAqC;EAC7C,OAAO,KAAK,aAAa,KAAK,eAAe,WAAW,GAAG;CAC/D;CAiCA,AAAO,OAEH,KACA,QACA,GAAG,MACqC;EACxC,IAAI,KAAK,SACL,MAAM,IAAI,cAAc,kBAAkB,mBAAmB;EAGjE,MAAM,EAAE,MAAM,SAAS,eAAe,GAAG;EAGzC,IAAI,cAAc,IAAI,IAAI,GACtB,MAAM,IAAI,kBAAkB,kBAAkB,2BAA2B,CAAC,IAAI,CAAC;EAGnF,IAAI,SAAS,MAAM,SAAS,IACxB,MAAM,IAAI,kBAAkB,kBAAkB,wBAAwB,CAAC,KAAK,oBAAoB,CAAC;EAErG,IAAI,MAAM,SAAS,GAAG,GAClB,MAAM,IAAI,kBAAkB,kBAAkB,wBAAwB,CAAC,KAAK,wBAAwB,CAAC;EAEzG,KAAK,YAAY,MAAM,MAAM,GAAG;EAEhC,IAAI,CAAC,sBAAsB,MAAM,GAC7B,MAAM,IAAI,kBAAkB,kBAAkB,yBAAyB,CAAC,OAAO,IAAI,CAAC;EAGxF,MAAM,KAAK,IAAI,GAAG,KAAK,GAAG,QAAQ,WAAW,GAAG;EAChD,MAAM,WAAW,kBAAkB,KAAK,UAAU,IAAI,OAAO,MAAM,GAAG,IAAI,CAAC;EAC3E,KAAK,aAAa,OAAO,MAAM,gBAAgB,QAAQ,CAAC;EACxD,KAAK,aAAa,KAAK;GAAE;GAAK;GAAU,MAAM,wBAAwB,QAAQ;EAAE,CAAC;EAEjF,IAAI,SAAS,QACT,OAAO,OAAO,OAAO,MAAM,GAAG,MAAM,SAAS,CAAC;EAGlD,KAAK,UAAU,IAAI,CAAC,CAAC,QAAQ;EAC7B,OAAO;CACX;CAEA,YAAY,MAAc,MAA0B,KAAmB;EACnE,IAAI,SAAS,QAAW;GACpB,IAAI,KAAK,QAAQ,IAAI,GAAG,GAAG,MAAM,IAAI,kBAAkB,kBAAkB,yBAAyB,CAAC,GAAG,CAAC;GACvG,IAAI,OAAO,MAAM,MAAM,IAAI,kBAAkB,kBAAkB,qBAAqB,CAAC,GAAG,CAAC;GACzF;EACJ;EAEA,MAAM,QAAQ,KAAK,QAAQ,IAAI,IAAI;EACnC,IAAI,CAAC,OAAO;GACR,IAAI,QAAQ,MAAM,MAAM,IAAI,kBAAkB,kBAAkB,sBAAsB,CAAC,MAAM,GAAG,CAAC;GACjG;EACJ;EACA,IAAI,OAAO,OAAO,OAAO,IAAI,GAAG,MAAM,IAAI,kBAAkB,kBAAkB,qBAAqB,CAAC,GAAG,CAAC;CAC5G;CAGA,aAAa,YAAoB,UAA+B;EAC5D,KAAK,MAAM,QAAQ,CAAC,aAAa,SAAS,GAAY;GAClD,MAAM,OAAO,KAAK,OAAO;GACzB,IAAI,SAAS,UAAU,SAAS,SAAS,UAAU,MAAM;GACzD,MAAM,IAAI,kBAAkB,kBAAkB,yBAAyB;IACnE;IACA;IACA,SAAS;IACT;GACJ,CAAC;EACL;CACJ;CAEA,UAAU,MAA0C;EAChD,MAAM,WAAW,KAAK,QAAQ,IAAI,IAAI;EACtC,IAAI,UAAU,OAAO;EAGrB,MAAM,QAAQ,OAAO,OAAO,IAAI;EAChC,KAAK,QAAQ,IAAI,MAAM,KAAK;EAC5B,OAAO,OAAO,MAAM,GAAG,OAAO,MAAM,CAAC;EACrC,OAAO;CACX;AACJ;AAEA,SAAgB,cACZ,MACqB;CACrB,OAAO,KAAK;AAChB;AAEA,SAAgB,gBAAgB,MAAmE;CAC/F,KAAK,SAAS,CAAC;AACnB"}
@@ -1,5 +1,5 @@
1
- import { y as StartupPhase } from "./Bus-W1K-uMeO.mjs";
2
- import { s as pluginLoggerOf } from "./PluginHost-CYe-UVpp.mjs";
1
+ import { b as StartupPhase } from "./Bus-VJw-QpoY.mjs";
2
+ import { c as pluginLoggerOf } from "./PluginHost-DTtxWRiL.mjs";
3
3
  import { SeedcordErrorCode, isSeedcordError } from "@seedcord/errors";
4
4
  import { SeedcordError, throwSingleOrAggregate } from "@seedcord/errors/internal";
5
5
  import { Logger } from "@seedcord/logger";
@@ -92,7 +92,7 @@ var PluginLifecycle = class {
92
92
  try {
93
93
  await withTimeout(`Plugin (${key})`, () => running, spec.init.timeout);
94
94
  } catch (caught) {
95
- if (isSeedcordError(caught, void 0, SeedcordErrorCode.LifecycleTaskTimeout)) this.disposeWhenInitResolves(attachment, running);
95
+ if (isSeedcordError(caught, void 0, SeedcordErrorCode.LifecycleTaskTimeout)) this.disposeWhenInitResolves(attachment, Promise.resolve(running));
96
96
  throw caught;
97
97
  }
98
98
  pluginLoggerOf(instance).utils.initialization(key, "end");
@@ -155,4 +155,4 @@ var PluginLifecycle = class {
155
155
 
156
156
  //#endregion
157
157
  export { logUnknownFault as i, settleWithin as n, withTimeout as r, PluginLifecycle as t };
158
- //# sourceMappingURL=PluginLifecycle-DdwRzcDj.mjs.map
158
+ //# sourceMappingURL=PluginLifecycle-hx5zUc-P.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PluginLifecycle-hx5zUc-P.mjs","names":[],"sources":["../src/notices/logUnknownFault.ts","../src/lifecycle/withTimeout.ts","../src/lifecycle/PluginLifecycle.ts"],"sourcesContent":["import type { Logger } from '@seedcord/logger';\nimport type { ErrorsConfig } from '@seedcord/types';\n\nfunction causeLine(error: Error): string {\n const { cause } = error;\n if (!Error.isError(cause)) return '';\n const [first] = cause.message.split('\\n');\n return `\\ncaused by ${cause.name}: ${first ?? cause.message}`;\n}\n\nexport function logUnknownFault(logger: Logger, uuid: string, error: Error, errors: ErrorsConfig | undefined): void {\n if (errors?.errorStack ?? true) logger.error(uuid, error);\n else logger.error(`${uuid} | ${error.message}${causeLine(error)}`);\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport type { Promisable } from 'type-fest';\n\n// calling start() in here turns its synchronous throw into a rejection\nasync function raceTimer(\n start: () => Promisable<unknown>,\n timeoutMs: number,\n onTimeout: (resolve: () => void, reject: (error: unknown) => void) => void\n): Promise<void> {\n let timer: NodeJS.Timeout | undefined;\n try {\n await Promise.race([\n start(),\n new Promise<void>((resolve, reject) => {\n // node's timer can go off before performance.now() reaches timeoutMs\n const expiresAt = performance.now() + timeoutMs;\n const tick = (): void => {\n const remaining = expiresAt - performance.now();\n if (remaining > 0) {\n timer = setTimeout(tick, remaining);\n return;\n }\n onTimeout(resolve, reject);\n };\n timer = setTimeout(tick, timeoutMs);\n })\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n// rejects with a LifecycleTaskTimeout when run outlasts the bound\nexport function withTimeout(name: string, run: () => Promisable<void>, timeoutMs: number): Promise<void> {\n return raceTimer(run, timeoutMs, (_, reject) => {\n reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [name, timeoutMs]));\n });\n}\n\n// the caller does not get work's rejection\nexport function settleWithin(work: Promise<unknown>, timeoutMs: number): Promise<void> {\n return raceTimer(\n () =>\n work.then(\n () => undefined,\n () => undefined\n ),\n timeoutMs,\n (resolve) => resolve()\n );\n}\n","import { SeedcordErrorCode, isSeedcordError } from '@seedcord/errors';\nimport { throwSingleOrAggregate } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\n\nimport { StartupPhase } from '#src/lifecycle/phases';\nimport { pluginLoggerOf } from '#src/plugin/Plugin';\n\nimport { withTimeout } from './withTimeout';\n\nimport type { ShutdownTasks } from '#node/Lifecycle/CoordinatedShutdown';\nimport type { TaskShutdownPhase } from '#src/lifecycle/phases';\nimport type { Attachment } from '#src/plugin/PluginHost';\nimport type { StartupTasks } from './CoordinatedStartup';\nimport type { Promisable } from 'type-fest';\n\ninterface ReadyStep {\n readonly key: string;\n readonly run: () => Promisable<void>;\n readonly timeout: number;\n}\n\nexport class PluginLifecycle {\n private readonly logger = new Logger('Plugins', { channel: 'plugins' });\n private readonly completedInits = new Set<Attachment>();\n private readonly disposePhases = new Set<TaskShutdownPhase>();\n private readonly timedOutInits = new Set<Promise<void>>();\n private attachments: readonly Attachment[] = [];\n private registered = false;\n\n constructor(\n private readonly startup: StartupTasks,\n private readonly shutdown: ShutdownTasks\n ) {}\n\n // one combined task per phase keeps plugin inits sequential while the phase's other tasks run concurrently\n public register(attachments: readonly Attachment[]): void {\n if (this.registered) return;\n this.registered = true;\n this.attachments = [...attachments];\n\n const groups = Map.groupBy(this.attachments, (attachment) => attachment.spec.init.phase);\n\n for (const [phase, group] of groups) {\n // Ready inits run in the combined Ready task, before the ready hooks\n if (phase === StartupPhase.Ready) continue;\n const budget = group.reduce((sum, a) => sum + a.spec.init.timeout, 0);\n this.startup.addTask(phase, 'plugins-init', () => this.runInits(group), budget);\n }\n this.registerReadyTask(groups.get(StartupPhase.Ready) ?? []);\n }\n\n public async rollback(): Promise<void> {\n // log rollback dispose failures so they do not hide the startup error\n await this.disposeCompleted(undefined, (caught) => this.logger.warn('rollback dispose failed', caught));\n // prevents re-dispose when shutdown runs after rollback\n this.completedInits.clear();\n }\n\n public afterTimedOutInits(run: () => void): void {\n if (this.timedOutInits.size === 0) {\n run();\n return;\n }\n void Promise.allSettled(this.timedOutInits).then(run);\n }\n\n private async runInits(group: readonly Attachment[]): Promise<void> {\n for (const attachment of group) {\n const { key, instance, spec } = attachment;\n\n pluginLoggerOf(instance).utils.initialization(key, 'start');\n const running = instance.init();\n try {\n await withTimeout(`Plugin (${key})`, () => running, spec.init.timeout);\n } catch (caught) {\n if (isSeedcordError(caught, undefined, SeedcordErrorCode.LifecycleTaskTimeout)) {\n this.disposeWhenInitResolves(attachment, Promise.resolve(running));\n }\n throw caught;\n }\n pluginLoggerOf(instance).utils.initialization(key, 'end');\n\n this.completedInits.add(attachment);\n if (instance.dispose) this.registerDisposeTask(spec.dispose.phase);\n }\n }\n\n // disposeCompleted skips a plugin missing from completedInits\n private disposeWhenInitResolves({ key, instance, spec }: Attachment, running: Promise<void>): void {\n const dispose = instance.dispose?.bind(instance);\n\n const settled = running.then(\n async () => {\n if (!dispose) return;\n await withTimeout(`Plugin:${key}:dispose`, dispose, spec.dispose.timeout).catch((caught: unknown) =>\n this.logger.warn(`${key} dispose failed after a timed-out init`, caught)\n );\n },\n (caught: unknown) => this.logger.warn(`${key} init failed after its timeout`, caught)\n );\n this.timedOutInits.add(settled);\n void settled.finally(() => this.timedOutInits.delete(settled));\n }\n\n private registerReadyTask(readyInits: readonly Attachment[]): void {\n const steps = this.attachments.reduce<ReadyStep[]>((acc, { key, instance, spec }) => {\n const ready = instance.ready?.bind(instance);\n\n if (ready) acc.push({ key, run: ready, timeout: spec.ready.timeout });\n return acc;\n }, []);\n if (readyInits.length === 0 && steps.length === 0) return;\n const budget =\n readyInits.reduce((sum, a) => sum + a.spec.init.timeout, 0) +\n steps.reduce((sum, step) => sum + step.timeout, 0);\n this.startup.addTask(\n StartupPhase.Ready,\n 'plugins-ready',\n async () => {\n await this.runInits(readyInits);\n for (const step of steps) {\n await withTimeout(`Plugin:${step.key}:ready`, step.run, step.timeout);\n }\n },\n budget\n );\n }\n\n private registerDisposeTask(phase: TaskShutdownPhase): void {\n if (this.disposePhases.has(phase)) return;\n this.disposePhases.add(phase);\n\n // the budget covers every dispose in this phase because they all run in this one task\n const budget = this.attachments.reduce(\n (sum, a) => (a.instance.dispose && a.spec.dispose.phase === phase ? sum + a.spec.dispose.timeout : sum),\n 0\n );\n this.shutdown.addTask(phase, 'plugins-dispose', () => this.runDisposals(phase), budget);\n }\n\n private async runDisposals(phase: TaskShutdownPhase): Promise<void> {\n const failures: unknown[] = [];\n await this.disposeCompleted(phase, (caught) => failures.push(caught));\n throwSingleOrAggregate(failures, SeedcordErrorCode.PluginDisposeFailures);\n }\n\n private async disposeCompleted(\n phase: TaskShutdownPhase | undefined,\n onError: (caught: unknown) => void\n ): Promise<void> {\n for (const attachment of [...this.attachments].reverse()) {\n if (!this.completedInits.has(attachment)) continue;\n const { key, instance, spec } = attachment;\n const dispose = instance.dispose?.bind(instance);\n if (!dispose) continue;\n if (phase !== undefined && spec.dispose.phase !== phase) continue;\n\n try {\n await withTimeout(`Plugin:${key}:dispose`, dispose, spec.dispose.timeout);\n } catch (caught) {\n onError(caught);\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { f as Transport, n as PluginHost } from "./PluginHost-DWBgBS6Q.mjs";
2
- import { i as StartupTasks, n as ShutdownTasks, r as CoordinatedStartup, t as CoordinatedShutdown } from "./CoordinatedShutdown-BVtNNSsF.mjs";
1
+ import { f as Transport, n as PluginHost } from "./PluginHost-DFnkIldj.mjs";
2
+ import { i as StartupTasks, n as ShutdownTasks, r as CoordinatedStartup, t as CoordinatedShutdown } from "./CoordinatedShutdown-fPKLWjc5.mjs";
3
3
  import { HostShutdown, HostStartup } from "@seedcord/types/internal";
4
4
  import { Config } from "@seedcord/types";
5
5
  import { Promisable } from "type-fest";
@@ -38,4 +38,4 @@ declare abstract class ServerHost<BotT extends Transport> extends PluginHost<Bot
38
38
  declare function shutdownOf(host: Pick<ServerHost<Transport>, typeof HostShutdown>): CoordinatedShutdown;
39
39
  //#endregion
40
40
  export { shutdownOf as n, ServerHost as t };
41
- //# sourceMappingURL=ServerHost-whpI0h4d.d.mts.map
41
+ //# sourceMappingURL=ServerHost-B5o3ScCF.d.mts.map
@@ -1,6 +1,6 @@
1
- import { b as PublishDefault, v as ShutdownPhase } from "./Bus-W1K-uMeO.mjs";
2
- import { n as attachmentsOf, r as sealAttachments, t as PluginHost } from "./PluginHost-CYe-UVpp.mjs";
3
- import { i as logUnknownFault, t as PluginLifecycle } from "./PluginLifecycle-DdwRzcDj.mjs";
1
+ import { x as PublishDefault, y as ShutdownPhase } from "./Bus-VJw-QpoY.mjs";
2
+ import { n as attachmentsOf, r as sealAttachments, t as PluginHost } from "./PluginHost-DTtxWRiL.mjs";
3
+ import { i as logUnknownFault, t as PluginLifecycle } from "./PluginLifecycle-hx5zUc-P.mjs";
4
4
  import { t as _usingCtx } from "./usingCtx-Cf2cZi96.mjs";
5
5
  import { SeedcordErrorCode } from "@seedcord/errors";
6
6
  import { SeedcordError } from "@seedcord/errors/internal";
@@ -176,4 +176,4 @@ function shutdownOf(host) {
176
176
 
177
177
  //#endregion
178
178
  export { shutdownOf as n, assertDeclaredRuntime as r, ServerHost as t };
179
- //# sourceMappingURL=ServerHost-DHg-Y2_q.mjs.map
179
+ //# sourceMappingURL=ServerHost-CDaEDR4C.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ServerHost-DHg-Y2_q.mjs","names":[],"sources":["../src/node/assertRuntimeVersion.ts","../src/node/processErrors.ts","../src/node/ServerHost.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { isValid, isValidRange, satisfies } from 'verkit';\n\nexport function assertDeclaredRuntime(): void {\n const { bun, node } = process.versions;\n // tsdown-config bakes both ranges in from this package's engines\n const runtime =\n bun === undefined\n ? { name: 'Node', range: process.env.PACKAGE_NODE_RANGE ?? '', running: node }\n : { name: 'Bun', range: process.env.PACKAGE_BUN_RANGE ?? '', running: bun };\n\n if (\n !runtime.range ||\n !isValidRange(runtime.range) ||\n !isValid(runtime.running) ||\n satisfies(runtime.running, runtime.range, { includePrerelease: true })\n )\n return;\n\n throw new SeedcordError(SeedcordErrorCode.UnsupportedRuntimeVersion, [\n runtime.name,\n runtime.range,\n runtime.running\n ]);\n}\n","import crypto from 'node:crypto';\n\nimport { Logger } from '@seedcord/logger';\nimport { asError } from '@seedcord/utils/internal';\n\nimport { logUnknownFault } from '#src/notices/logUnknownFault';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\n\nconst logger = new Logger('Faults', { channel: 'errors' });\n\n/** @internal */\nexport function registerProcessErrors(core: CoreBase, shutdown: CoordinatedShutdown): () => void {\n function report(caught: unknown, origin: string): void {\n const error = asError(caught);\n const uuid = crypto.randomUUID();\n\n logUnknownFault(logger, uuid, error, core.config.errors);\n\n core.bus[PublishDefault]('unknownException', { uuid, dispatchId: null, error, origin });\n }\n\n const onRejection = (reason: unknown): void => {\n report(reason, 'process:unhandledRejection');\n };\n\n const onException = (caught: unknown): void => {\n report(caught, 'process:uncaughtException');\n // node does not exit on its own once a listener is registered\n void shutdown.run(1);\n };\n\n process.on('unhandledRejection', onRejection);\n process.on('uncaughtException', onException);\n\n return () => {\n process.off('unhandledRejection', onRejection);\n process.off('uncaughtException', onException);\n };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { LoggerChannelRegistry } from '@seedcord/logger';\nimport { installNodeConsole, installNodeDefaults } from '@seedcord/logger/node';\nimport { HostShutdown, HostStartup } from '@seedcord/types/internal';\nimport { runsUnderSeedcordDev } from '@seedcord/utils/internal';\n\nimport { assertDeclaredRuntime } from '#node/assertRuntimeVersion';\nimport { ShutdownPhase } from '#src/lifecycle/phases';\nimport { PluginLifecycle } from '#src/lifecycle/PluginLifecycle';\nimport { attachmentsOf, PluginHost, sealAttachments } from '#src/plugin/PluginHost';\n\nimport { registerProcessErrors } from './processErrors';\n\nimport type { CoordinatedShutdown, ShutdownTasks } from '#node/Lifecycle/CoordinatedShutdown';\nimport type { CoordinatedStartup, StartupTasks } from '#src/lifecycle/CoordinatedStartup';\nimport type { Transport } from '#src/plugin/options';\nimport type { Config } from '@seedcord/types';\nimport type { Promisable } from 'type-fest';\n\n/** Base class for a transport `Seedcord` class that runs as a long-lived node or bun process. */\nexport abstract class ServerHost<BotT extends Transport> extends PluginHost<BotT, 'server'> {\n /** @internal */\n readonly [HostShutdown]: CoordinatedShutdown;\n /** @internal */\n readonly [HostStartup]: CoordinatedStartup;\n\n /** Add a task that runs while the bot shuts down. */\n public readonly shutdown: ShutdownTasks;\n\n /** Add a task that runs while the bot starts. */\n public readonly startup: StartupTasks;\n\n readonly #lifecycle: PluginLifecycle;\n #startFailed = false;\n #initPromise?: Promise<this> | undefined;\n\n static #isInstantiated = false;\n static #liveHost?: object | undefined;\n static #liveShutdown?: CoordinatedShutdown | undefined;\n static #liveProcessErrors?: (() => void) | undefined;\n\n constructor(transport: BotT, shutdown: CoordinatedShutdown, startup: CoordinatedStartup, driver: Config['kv']) {\n // a `sideEffects: false` build would drop the same call in the node entry\n assertDeclaredRuntime();\n super(transport, 'server', driver);\n\n if (ServerHost.#isInstantiated) throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);\n\n // init() opens the log file\n installNodeConsole();\n\n ServerHost.#isInstantiated = true;\n ServerHost.#liveHost = this;\n ServerHost.#liveShutdown = shutdown;\n this[HostShutdown] = shutdown;\n this[HostStartup] = startup;\n // a getter returning the slot would expose run() and the signal handlers too\n this.shutdown = { addTask: shutdown.addTask.bind(shutdown) };\n this.startup = { addTask: startup.addTask.bind(startup) };\n this.#lifecycle = new PluginLifecycle(this.startup, this.shutdown);\n shutdown.addTask(ShutdownPhase.Release, 'close-kv', async () => {\n await using _driver = this.kv.driver;\n });\n }\n\n /** @internal */\n protected init(): Promise<this> {\n // a retry after a rejection runs #runInit again and hits its restart guard\n this.#initPromise ??= this.#runInit().catch(async (caught: unknown) => {\n this.#initPromise = undefined;\n await this.#release(1);\n throw caught;\n });\n return this.#initPromise;\n }\n\n async #runInit(): Promise<this> {\n // a rerun after a failed startup would re-init the rolled-back plugins\n if (this.#startFailed) throw new SeedcordError(SeedcordErrorCode.LifecycleRestartAfterFailure);\n\n installNodeDefaults(this.config.logger);\n sealAttachments(this);\n this.#lifecycle.register(attachmentsOf(this));\n\n // codegen and the build construct the bot without starting it.\n // under seedcord dev the CLI handles SIGINT and SIGTERM\n if (!runsUnderSeedcordDev()) this[HostShutdown].registerSignalHandlers();\n if (this.config.errors?.catchProcessErrors ?? true) {\n ServerHost.#liveProcessErrors = registerProcessErrors(this, this[HostShutdown]);\n }\n\n const startupSettled: PromiseWithResolvers<void> = Promise.withResolvers();\n this[HostShutdown].gateOnStartup(startupSettled.promise);\n\n try {\n // nothing a plugin adds through startup.addTask runs after this\n await this[HostStartup].run(() => this.openHealth());\n } catch (caught) {\n this.#startFailed = true;\n await this.#lifecycle.rollback();\n throw caught;\n } finally {\n startupSettled.resolve();\n }\n\n return this;\n }\n\n /** @internal */\n protected openHealth(): Promisable<void> {}\n\n /**\n * Runs the coordinated shutdown without calling `process.exit`. A new `Seedcord` can be\n * constructed afterwards. `await using` calls this when its scope ends.\n *\n * @example\n * ```ts\n * // the bot shuts down when this block ends\n * await using bot = await new Seedcord(config).start();\n * console.log(await bot.rest.get(Routes.user('@me')));\n * ```\n */\n public async [Symbol.asyncDispose](): Promise<void> {\n await this.#release(0);\n }\n\n async #release(exitCode: number): Promise<void> {\n try {\n await this[HostShutdown].run(exitCode, false);\n } finally {\n ServerHost.reset(this);\n }\n }\n\n /** @internal */\n protected static reset(host?: object): boolean {\n if (host !== undefined && ServerHost.#liveHost !== host) return false;\n\n ServerHost.#liveShutdown?.removeSignalHandlers();\n ServerHost.#liveShutdown = undefined;\n ServerHost.#liveProcessErrors?.();\n ServerHost.#liveProcessErrors = undefined;\n ServerHost.#liveHost = undefined;\n ServerHost.#isInstantiated = false;\n // configure() keeps the dev TUI's capture sink\n LoggerChannelRegistry.instance.configure({});\n return true;\n }\n}\n\n// the public `shutdown` field carries addTask alone\nexport function shutdownOf(host: Pick<ServerHost<Transport>, typeof HostShutdown>): CoordinatedShutdown {\n return host[HostShutdown];\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ServerHost-CDaEDR4C.mjs","names":[],"sources":["../src/node/assertRuntimeVersion.ts","../src/node/processErrors.ts","../src/node/ServerHost.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { isValid, isValidRange, satisfies } from 'verkit';\n\nexport function assertDeclaredRuntime(): void {\n const { bun, node } = process.versions;\n // tsdown-config bakes both ranges in from this package's engines\n const runtime =\n bun === undefined\n ? { name: 'Node', range: process.env.PACKAGE_NODE_RANGE ?? '', running: node }\n : { name: 'Bun', range: process.env.PACKAGE_BUN_RANGE ?? '', running: bun };\n\n if (\n !runtime.range ||\n !isValidRange(runtime.range) ||\n !isValid(runtime.running) ||\n satisfies(runtime.running, runtime.range, { includePrerelease: true })\n )\n return;\n\n throw new SeedcordError(SeedcordErrorCode.UnsupportedRuntimeVersion, [\n runtime.name,\n runtime.range,\n runtime.running\n ]);\n}\n","import crypto from 'node:crypto';\n\nimport { Logger } from '@seedcord/logger';\nimport { asError } from '@seedcord/utils/internal';\n\nimport { logUnknownFault } from '#src/notices/logUnknownFault';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\n\nconst logger = new Logger('Faults', { channel: 'errors' });\n\n/** @internal */\nexport function registerProcessErrors(core: CoreBase, shutdown: CoordinatedShutdown): () => void {\n function report(caught: unknown, origin: string): void {\n const error = asError(caught);\n const uuid = crypto.randomUUID();\n\n logUnknownFault(logger, uuid, error, core.config.errors);\n\n core.bus[PublishDefault]('unknownException', { uuid, dispatchId: null, error, origin });\n }\n\n const onRejection = (reason: unknown): void => {\n report(reason, 'process:unhandledRejection');\n };\n\n const onException = (caught: unknown): void => {\n report(caught, 'process:uncaughtException');\n // node does not exit on its own once a listener is registered\n void shutdown.run(1);\n };\n\n process.on('unhandledRejection', onRejection);\n process.on('uncaughtException', onException);\n\n return () => {\n process.off('unhandledRejection', onRejection);\n process.off('uncaughtException', onException);\n };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { LoggerChannelRegistry } from '@seedcord/logger';\nimport { installNodeConsole, installNodeDefaults } from '@seedcord/logger/node';\nimport { HostShutdown, HostStartup } from '@seedcord/types/internal';\nimport { runsUnderSeedcordDev } from '@seedcord/utils/internal';\n\nimport { assertDeclaredRuntime } from '#node/assertRuntimeVersion';\nimport { ShutdownPhase } from '#src/lifecycle/phases';\nimport { PluginLifecycle } from '#src/lifecycle/PluginLifecycle';\nimport { attachmentsOf, PluginHost, sealAttachments } from '#src/plugin/PluginHost';\n\nimport { registerProcessErrors } from './processErrors';\n\nimport type { CoordinatedShutdown, ShutdownTasks } from '#node/Lifecycle/CoordinatedShutdown';\nimport type { CoordinatedStartup, StartupTasks } from '#src/lifecycle/CoordinatedStartup';\nimport type { Transport } from '#src/plugin/options';\nimport type { Config } from '@seedcord/types';\nimport type { Promisable } from 'type-fest';\n\n/** Base class for a transport `Seedcord` class that runs as a long-lived node or bun process. */\nexport abstract class ServerHost<BotT extends Transport> extends PluginHost<BotT, 'server'> {\n /** @internal */\n readonly [HostShutdown]: CoordinatedShutdown;\n /** @internal */\n readonly [HostStartup]: CoordinatedStartup;\n\n /** Add a task that runs while the bot shuts down. */\n public readonly shutdown: ShutdownTasks;\n\n /** Add a task that runs while the bot starts. */\n public readonly startup: StartupTasks;\n\n readonly #lifecycle: PluginLifecycle;\n #startFailed = false;\n #initPromise?: Promise<this> | undefined;\n\n static #isInstantiated = false;\n static #liveHost?: object | undefined;\n static #liveShutdown?: CoordinatedShutdown | undefined;\n static #liveProcessErrors?: (() => void) | undefined;\n\n constructor(transport: BotT, shutdown: CoordinatedShutdown, startup: CoordinatedStartup, driver: Config['kv']) {\n // a `sideEffects: false` build would drop the same call in the node entry\n assertDeclaredRuntime();\n super(transport, 'server', driver);\n\n if (ServerHost.#isInstantiated) throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);\n\n // init() opens the log file\n installNodeConsole();\n\n ServerHost.#isInstantiated = true;\n ServerHost.#liveHost = this;\n ServerHost.#liveShutdown = shutdown;\n this[HostShutdown] = shutdown;\n this[HostStartup] = startup;\n // a getter returning the slot would expose run() and the signal handlers too\n this.shutdown = { addTask: shutdown.addTask.bind(shutdown) };\n this.startup = { addTask: startup.addTask.bind(startup) };\n this.#lifecycle = new PluginLifecycle(this.startup, this.shutdown);\n shutdown.addTask(ShutdownPhase.Release, 'close-kv', async () => {\n await using _driver = this.kv.driver;\n });\n }\n\n /** @internal */\n protected init(): Promise<this> {\n // a retry after a rejection runs #runInit again and hits its restart guard\n this.#initPromise ??= this.#runInit().catch(async (caught: unknown) => {\n this.#initPromise = undefined;\n await this.#release(1);\n throw caught;\n });\n return this.#initPromise;\n }\n\n async #runInit(): Promise<this> {\n // a rerun after a failed startup would re-init the rolled-back plugins\n if (this.#startFailed) throw new SeedcordError(SeedcordErrorCode.LifecycleRestartAfterFailure);\n\n installNodeDefaults(this.config.logger);\n sealAttachments(this);\n this.#lifecycle.register(attachmentsOf(this));\n\n // codegen and the build construct the bot without starting it.\n // under seedcord dev the CLI handles SIGINT and SIGTERM\n if (!runsUnderSeedcordDev()) this[HostShutdown].registerSignalHandlers();\n if (this.config.errors?.catchProcessErrors ?? true) {\n ServerHost.#liveProcessErrors = registerProcessErrors(this, this[HostShutdown]);\n }\n\n const startupSettled: PromiseWithResolvers<void> = Promise.withResolvers();\n this[HostShutdown].gateOnStartup(startupSettled.promise);\n\n try {\n // nothing a plugin adds through startup.addTask runs after this\n await this[HostStartup].run(() => this.openHealth());\n } catch (caught) {\n this.#startFailed = true;\n await this.#lifecycle.rollback();\n throw caught;\n } finally {\n startupSettled.resolve();\n }\n\n return this;\n }\n\n /** @internal */\n protected openHealth(): Promisable<void> {}\n\n /**\n * Runs the coordinated shutdown without calling `process.exit`. A new `Seedcord` can be\n * constructed afterwards. `await using` calls this when its scope ends.\n *\n * @example\n * ```ts\n * // the bot shuts down when this block ends\n * await using bot = await new Seedcord(config).start();\n * console.log(await bot.rest.get(Routes.user('@me')));\n * ```\n */\n public async [Symbol.asyncDispose](): Promise<void> {\n await this.#release(0);\n }\n\n async #release(exitCode: number): Promise<void> {\n try {\n await this[HostShutdown].run(exitCode, false);\n } finally {\n ServerHost.reset(this);\n }\n }\n\n /** @internal */\n protected static reset(host?: object): boolean {\n if (host !== undefined && ServerHost.#liveHost !== host) return false;\n\n ServerHost.#liveShutdown?.removeSignalHandlers();\n ServerHost.#liveShutdown = undefined;\n ServerHost.#liveProcessErrors?.();\n ServerHost.#liveProcessErrors = undefined;\n ServerHost.#liveHost = undefined;\n ServerHost.#isInstantiated = false;\n // configure() keeps the dev TUI's capture sink\n LoggerChannelRegistry.instance.configure({});\n return true;\n }\n}\n\n// the public `shutdown` field carries addTask alone\nexport function shutdownOf(host: Pick<ServerHost<Transport>, typeof HostShutdown>): CoordinatedShutdown {\n return host[HostShutdown];\n}\n"],"mappings":"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"}