@seedcord/core 0.3.1 → 0.4.1

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 (38) hide show
  1. package/dist/{Bus-BvF4VShN.mjs → Bus-yJGE6Fs2.mjs} +111 -92
  2. package/dist/Bus-yJGE6Fs2.mjs.map +1 -0
  3. package/dist/Pluggable-BI_LQuPm.mjs +280 -0
  4. package/dist/Pluggable-BI_LQuPm.mjs.map +1 -0
  5. package/dist/Pluggable-bFCmT4oB.d.mts +148 -0
  6. package/dist/{Plugin-D9ykXA5g.mjs → Plugin-B0SRHzMX.mjs} +7 -3
  7. package/dist/Plugin-B0SRHzMX.mjs.map +1 -0
  8. package/dist/{Plugin-CQ9IEj5X.d.mts → Plugin-BW2JH2_h.d.mts} +2 -2
  9. package/dist/{cursor-BANzac53.d.mts → cursor-CO_MTgdZ.d.mts} +8 -252
  10. package/dist/{cursor-CuoA9cRh.mjs → cursor-CaT051ii.mjs} +307 -425
  11. package/dist/cursor-CaT051ii.mjs.map +1 -0
  12. package/dist/{index-rt8rZg4d.d.mts → index-CvneCObW.d.mts} +2 -2
  13. package/dist/index.d.mts +78 -19
  14. package/dist/index.mjs +223 -21
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/internal.index.d.mts +13 -6
  17. package/dist/internal.index.mjs +8 -455
  18. package/dist/internal.index.mjs.map +1 -1
  19. package/dist/node-internal.index.d.mts +6 -133
  20. package/dist/node-internal.index.mjs +16 -473
  21. package/dist/node-internal.index.mjs.map +1 -1
  22. package/dist/node.index.d.mts +2 -0
  23. package/dist/node.index.mjs +256 -0
  24. package/dist/node.index.mjs.map +1 -0
  25. package/dist/{phases-hBxMCD2V.d.mts → phases-C5Steaa2.d.mts} +151 -119
  26. package/dist/plugin.index.d.mts +1 -1
  27. package/dist/plugin.index.mjs +1 -1
  28. package/dist/publishDefault-D826XXDQ.mjs +14 -0
  29. package/dist/publishDefault-D826XXDQ.mjs.map +1 -0
  30. package/dist/{slashRouteLeaves-De1SxqnX.mjs → slashRouteLeaves-BQmbFm-S.mjs} +10 -2
  31. package/dist/slashRouteLeaves-BQmbFm-S.mjs.map +1 -0
  32. package/dist/{subscribers-wnMyNYZd.mjs → subscribers-DVa-Pr45.mjs} +3 -3
  33. package/dist/{subscribers-wnMyNYZd.mjs.map → subscribers-DVa-Pr45.mjs.map} +1 -1
  34. package/package.json +19 -12
  35. package/dist/Bus-BvF4VShN.mjs.map +0 -1
  36. package/dist/Plugin-D9ykXA5g.mjs.map +0 -1
  37. package/dist/cursor-CuoA9cRh.mjs.map +0 -1
  38. package/dist/slashRouteLeaves-De1SxqnX.mjs.map +0 -1
@@ -0,0 +1,280 @@
1
+ import { n as asError, t as PublishDefault } from "./publishDefault-D826XXDQ.mjs";
2
+ import { n as StartupPhase } from "./phases-BauKJOTT.mjs";
3
+ import { n as pluginLoggerOf, r as resolvedLifecycleSpecOf } from "./Plugin-B0SRHzMX.mjs";
4
+ import { SeedcordErrorCode } from "@seedcord/errors";
5
+ import { SeedcordAggregateError, SeedcordError, SeedcordTypeError } from "@seedcord/errors/internal";
6
+ import { FRAMEWORK_CHANNELS, Logger } from "@seedcord/logger";
7
+ import { HostPluginKeys, HostShutdown, HostStartup } from "@seedcord/types/internal";
8
+ import crypto from "node:crypto";
9
+
10
+ //#region src/node/Lifecycle/withTimeout.ts
11
+ async function raceTimer(work, timeoutMs, onTimeout) {
12
+ let timer;
13
+ try {
14
+ await Promise.race([work, new Promise((resolve, reject) => {
15
+ timer = setTimeout(() => onTimeout(resolve, reject), timeoutMs);
16
+ })]);
17
+ } finally {
18
+ if (timer) clearTimeout(timer);
19
+ }
20
+ }
21
+ function withTimeout(name, run, timeoutMs) {
22
+ return raceTimer(run(), timeoutMs, (_, reject) => {
23
+ reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [name, timeoutMs]));
24
+ });
25
+ }
26
+ function settleWithin(work, timeoutMs) {
27
+ return raceTimer(work, timeoutMs, (resolve) => resolve());
28
+ }
29
+
30
+ //#endregion
31
+ //#region src/node/assertNodeVersion.ts
32
+ const RANGE = new RegExp(String.raw`^>=(\d+)(?:\.(\d+))?`);
33
+ const VERSION = /^v?(\d+)\.(\d+)/;
34
+ const SPACES = /\s/g;
35
+ /** @internal */
36
+ function assertNodeVersion(range, running) {
37
+ const required = RANGE.exec(range.replaceAll(SPACES, ""));
38
+ const current = VERSION.exec(running);
39
+ if (!required || !current) return;
40
+ const [, requiredMajor, requiredMinor = "0"] = required;
41
+ const [, currentMajor, currentMinor] = current;
42
+ if (Number(currentMajor) > Number(requiredMajor) || Number(currentMajor) === Number(requiredMajor) && Number(currentMinor) >= Number(requiredMinor)) return;
43
+ throw new SeedcordError(SeedcordErrorCode.UnsupportedNodeVersion, [range, running]);
44
+ }
45
+
46
+ //#endregion
47
+ //#region src/node/processErrors.ts
48
+ const logger = new Logger("Faults", { channel: "errors" });
49
+ /** @internal */
50
+ function registerProcessErrors(core, shutdown) {
51
+ function report(caught, routeId) {
52
+ const error = asError(caught);
53
+ const uuid = crypto.randomUUID();
54
+ if (core.config.errors?.errorStack ?? false) logger.error(uuid, error);
55
+ else logger.error(`${uuid} | ${error.message}`);
56
+ core.bus[PublishDefault]("unknownException", {
57
+ uuid,
58
+ error,
59
+ routeId
60
+ });
61
+ }
62
+ const onRejection = (reason) => {
63
+ report(reason, "process:unhandledRejection");
64
+ };
65
+ const onException = (caught) => {
66
+ report(caught, "process:uncaughtException");
67
+ shutdown.run(1);
68
+ };
69
+ process.on("unhandledRejection", onRejection);
70
+ process.on("uncaughtException", onException);
71
+ return () => {
72
+ process.off("unhandledRejection", onRejection);
73
+ process.off("uncaughtException", onException);
74
+ };
75
+ }
76
+
77
+ //#endregion
78
+ //#region src/node/Pluggable.ts
79
+ const RESERVED_KEYS = new Set(FRAMEWORK_CHANNELS);
80
+ /**
81
+ * Base class for a plugin host, a transport `Seedcord` class.
82
+ *
83
+ * You attach plugins while configuring the bot. Within one startup phase, their `init()` calls run
84
+ * one after another in attach order.
85
+ */
86
+ var Pluggable = class Pluggable {
87
+ /** @internal */
88
+ [HostShutdown];
89
+ /** @internal */
90
+ [HostStartup];
91
+ /** Add a task that runs while the bot shuts down. */
92
+ shutdown;
93
+ /** Add a task that runs while the bot starts. */
94
+ startup;
95
+ isInitialized = false;
96
+ startFailed = false;
97
+ plugins = [];
98
+ pluginLogger = new Logger("Plugins", { channel: "plugins" });
99
+ attachments = [];
100
+ completedInits = /* @__PURE__ */ new Set();
101
+ disposePhases = /* @__PURE__ */ new Set();
102
+ pluginTasksRegistered = false;
103
+ initPromise;
104
+ static isInstantiated = false;
105
+ static liveHost;
106
+ static liveShutdown;
107
+ static liveProcessErrors;
108
+ constructor(shutdown, startup) {
109
+ assertNodeVersion(">=24.11", process.version);
110
+ if (Pluggable.isInstantiated) {
111
+ shutdown.removeSignalHandlers();
112
+ throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);
113
+ }
114
+ Pluggable.isInstantiated = true;
115
+ Pluggable.liveHost = this;
116
+ Pluggable.liveShutdown = shutdown;
117
+ this[HostShutdown] = shutdown;
118
+ this[HostStartup] = startup;
119
+ this.shutdown = { addTask: shutdown.addTask.bind(shutdown) };
120
+ this.startup = { addTask: startup.addTask.bind(startup) };
121
+ }
122
+ /** @internal */
123
+ init() {
124
+ this.initPromise ??= this.runInit().catch((caught) => {
125
+ this.initPromise = void 0;
126
+ throw caught;
127
+ });
128
+ return this.initPromise;
129
+ }
130
+ async runInit() {
131
+ if (this.isInitialized) return this;
132
+ if (this.startFailed) throw new SeedcordError(SeedcordErrorCode.LifecycleRestartAfterFailure);
133
+ this.registerPluginTasks();
134
+ if (this.config.errors?.catchProcessErrors ?? true) Pluggable.liveProcessErrors = registerProcessErrors(this, this[HostShutdown]);
135
+ const startupSettled = Promise.withResolvers();
136
+ this[HostShutdown].gateOnStartup(startupSettled.promise);
137
+ try {
138
+ await this[HostStartup].run();
139
+ } catch (caught) {
140
+ await this.rollback();
141
+ throw caught;
142
+ } finally {
143
+ startupSettled.resolve();
144
+ }
145
+ this.isInitialized = true;
146
+ return this;
147
+ }
148
+ /** @internal codegen reads this to emit the `Core` augmentation */
149
+ get [HostPluginKeys]() {
150
+ return this.attachments.map((attachment) => attachment.key);
151
+ }
152
+ /** @internal */
153
+ static reset(host) {
154
+ if (host !== void 0 && Pluggable.liveHost !== host) return false;
155
+ Pluggable.liveShutdown?.removeSignalHandlers();
156
+ Pluggable.liveShutdown = void 0;
157
+ Pluggable.liveProcessErrors?.();
158
+ Pluggable.liveProcessErrors = void 0;
159
+ Pluggable.liveHost = void 0;
160
+ Pluggable.isInstantiated = false;
161
+ return true;
162
+ }
163
+ /**
164
+ * Attaches a plugin under `key`. Read the instance back as `core[key]`. `seedcord codegen`
165
+ * writes the `Core` augmentation that types it there.
166
+ *
167
+ * Startup runs each plugin's `init()` in attach order within its phase.
168
+ *
169
+ * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.
170
+ * Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).
171
+ * A narrower one fails to compile here.
172
+ *
173
+ * @param key - Also the channel the plugin logs on. Reserved channel names throw.
174
+ * @param args - Whatever your constructor takes after the host.
175
+ * @throws A **SeedcordError** if you attach after the bot has started. A taken or reserved key throws too.
176
+ * @example
177
+ * ```ts
178
+ * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... });
179
+ * ```
180
+ */
181
+ attach(key, Plugin, ...args) {
182
+ if (this.isInitialized) throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);
183
+ if (RESERVED_KEYS.has(key)) throw new SeedcordTypeError(SeedcordErrorCode.CorePluginReservedChannel, [key]);
184
+ if (key in this) throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [key]);
185
+ const instance = new Plugin(this, ...args);
186
+ pluginLoggerOf(instance).setChannel(key);
187
+ this.plugins.push(instance);
188
+ this.attachments.push({
189
+ key,
190
+ instance
191
+ });
192
+ return Object.assign(this, { [key]: instance });
193
+ }
194
+ registerPluginTasks() {
195
+ if (this.pluginTasksRegistered) return;
196
+ this.pluginTasksRegistered = true;
197
+ const groups = /* @__PURE__ */ new Map();
198
+ for (const attachment of this.attachments) {
199
+ const phase = resolvedLifecycleSpecOf(attachment.instance).init.phase;
200
+ const group = groups.get(phase) ?? [];
201
+ group.push(attachment);
202
+ groups.set(phase, group);
203
+ }
204
+ for (const [phase, group] of groups) {
205
+ if (phase === 3) continue;
206
+ const budget = group.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0);
207
+ this.startup.addTask(phase, "plugins-init", () => this.runInits(group), budget);
208
+ }
209
+ this.registerReadyTask(groups.get(3) ?? []);
210
+ }
211
+ async runInits(group) {
212
+ for (const attachment of group) {
213
+ const { key, instance } = attachment;
214
+ const spec = resolvedLifecycleSpecOf(instance);
215
+ pluginLoggerOf(instance).utils.initialization(key, "start");
216
+ await withTimeout(`Plugin (${key})`, () => instance.init(), spec.init.timeout);
217
+ pluginLoggerOf(instance).utils.initialization(key, "end");
218
+ this.completedInits.add(attachment);
219
+ if (instance.dispose) this.registerDisposeTask(spec.dispose.phase);
220
+ }
221
+ }
222
+ registerReadyTask(readyInits) {
223
+ const steps = this.attachments.reduce((acc, a) => {
224
+ const ready = a.instance.ready?.bind(a.instance);
225
+ if (ready) acc.push({
226
+ key: a.key,
227
+ run: ready,
228
+ timeout: resolvedLifecycleSpecOf(a.instance).ready.timeout
229
+ });
230
+ return acc;
231
+ }, []);
232
+ if (readyInits.length === 0 && steps.length === 0) return;
233
+ const budget = readyInits.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).init.timeout, 0) + steps.reduce((sum, step) => sum + step.timeout, 0);
234
+ this.startup.addTask(3, "plugins-ready", async () => {
235
+ await this.runInits(readyInits);
236
+ for (const step of steps) await withTimeout(`Plugin:${step.key}:ready`, step.run, step.timeout);
237
+ }, budget);
238
+ }
239
+ registerDisposeTask(phase) {
240
+ if (this.disposePhases.has(phase)) return;
241
+ this.disposePhases.add(phase);
242
+ const budget = this.attachments.reduce((sum, a) => {
243
+ const spec = resolvedLifecycleSpecOf(a.instance);
244
+ return a.instance.dispose && spec.dispose.phase === phase ? sum + spec.dispose.timeout : sum;
245
+ }, 0);
246
+ this.shutdown.addTask(phase, "plugins-dispose", () => this.runDisposals(phase), budget);
247
+ }
248
+ async runDisposals(phase) {
249
+ const failures = [];
250
+ await this.disposeCompleted(phase, (caught) => failures.push(caught));
251
+ if (failures.length === 1) throw failures[0];
252
+ if (failures.length > 1) throw new SeedcordAggregateError(SeedcordErrorCode.PluginDisposeFailures, failures, [failures.length]);
253
+ }
254
+ async disposeCompleted(phase, onError) {
255
+ for (const attachment of [...this.attachments].reverse()) {
256
+ if (!this.completedInits.has(attachment)) continue;
257
+ const dispose = attachment.instance.dispose?.bind(attachment.instance);
258
+ if (!dispose) continue;
259
+ const spec = resolvedLifecycleSpecOf(attachment.instance);
260
+ if (phase !== void 0 && spec.dispose.phase !== phase) continue;
261
+ try {
262
+ await withTimeout(`Plugin:${attachment.key}:dispose`, dispose, spec.dispose.timeout);
263
+ } catch (caught) {
264
+ onError(caught);
265
+ }
266
+ }
267
+ }
268
+ async rollback() {
269
+ this.startFailed = true;
270
+ await this.disposeCompleted(void 0, (caught) => this.pluginLogger.warn("rollback dispose failed", caught));
271
+ this.completedInits.clear();
272
+ }
273
+ };
274
+ function shutdownOf(host) {
275
+ return host[HostShutdown];
276
+ }
277
+
278
+ //#endregion
279
+ export { withTimeout as a, settleWithin as i, shutdownOf as n, assertNodeVersion as r, Pluggable as t };
280
+ //# sourceMappingURL=Pluggable-BI_LQuPm.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pluggable-BI_LQuPm.mjs","names":[],"sources":["../src/node/Lifecycle/withTimeout.ts","../src/node/assertNodeVersion.ts","../src/node/processErrors.ts","../src/node/Pluggable.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nasync function raceTimer(\n work: Promise<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 work,\n new Promise<void>((resolve, reject) => {\n timer = setTimeout(() => onTimeout(resolve, reject), 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: () => Promise<void>, timeoutMs: number): Promise<void> {\n return raceTimer(run(), timeoutMs, (_, reject) => {\n reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [name, timeoutMs]));\n });\n}\n\n// resolves when work settles or the bound elapses, whichever is first\nexport function settleWithin(work: Promise<unknown>, timeoutMs: number): Promise<void> {\n return raceTimer(work, timeoutMs, (resolve) => resolve());\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\n// every seedcord package declares engines.node as `>=major` or `>=major.minor`\nconst RANGE = new RegExp(String.raw`^>=(\\d+)(?:\\.(\\d+))?`);\nconst VERSION = /^v?(\\d+)\\.(\\d+)/;\nconst SPACES = /\\s/g;\n\n/** @internal */\nexport function assertNodeVersion(range: string, running: string): void {\n const required = RANGE.exec(range.replaceAll(SPACES, ''));\n const current = VERSION.exec(running);\n if (!required || !current) return;\n\n const [, requiredMajor, requiredMinor = '0'] = required;\n const [, currentMajor, currentMinor] = current;\n\n const meetsRange =\n Number(currentMajor) > Number(requiredMajor) ||\n (Number(currentMajor) === Number(requiredMajor) && Number(currentMinor) >= Number(requiredMinor));\n if (meetsRange) return;\n\n throw new SeedcordError(SeedcordErrorCode.UnsupportedNodeVersion, [range, running]);\n}\n","import crypto from 'node:crypto';\n\nimport { Logger } from '@seedcord/logger';\n\nimport { asError } from '#stops/asError';\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, routeId: string): void {\n const error = asError(caught);\n const uuid = crypto.randomUUID();\n\n if (core.config.errors?.errorStack ?? false) logger.error(uuid, error);\n else logger.error(`${uuid} | ${error.message}`);\n\n core.bus[PublishDefault]('unknownException', { uuid, error, routeId });\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 { SeedcordAggregateError, SeedcordError, SeedcordTypeError } from '@seedcord/errors/internal';\nimport { FRAMEWORK_CHANNELS, Logger } from '@seedcord/logger';\nimport { HostPluginKeys, HostShutdown, HostStartup } from '@seedcord/types/internal';\n\nimport { assertNodeVersion } from '#node/assertNodeVersion';\nimport { StartupPhase } from '#src/lifecycle/phases';\nimport { pluginLoggerOf, resolvedLifecycleSpecOf } from '#src/plugin/Plugin';\n\nimport { withTimeout } from './Lifecycle/withTimeout';\nimport { registerProcessErrors } from './processErrors';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\nimport type { CoordinatedStartup } from '#node/Lifecycle/CoordinatedStartup';\nimport type { ShutdownPhase } from '#src/lifecycle/phases';\nimport type { ChannelKeyAssert, Runtime, RuntimeAssert, Transport, TransportAssert } from '#src/plugin/options';\nimport type { CoreParamAssert, PluginArgs, PluginCtor, PluginLike } from '#src/plugin/Plugin';\nimport type { Bus } from '#subscribers/Bus';\nimport type { REST } from '@discordjs/rest';\nimport type { Config, IRateLimiter } from '@seedcord/types';\n\ninterface Attachment {\n readonly key: string;\n readonly instance: PluginLike;\n}\n\nconst RESERVED_KEYS: ReadonlySet<string> = new Set(FRAMEWORK_CHANNELS);\n\n/**\n * Base class for a plugin host, a transport `Seedcord` class.\n *\n * You attach plugins while configuring the bot. Within one startup phase, their `init()` calls run\n * one after another in attach order.\n */\n// no defaults, a default runtime of the full union contains 'edge' and RuntimeAssert rejects every plugin on that\nexport abstract class Pluggable<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 public abstract readonly rateLimiter: IRateLimiter;\n public abstract readonly bus: Bus;\n\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: Pick<CoordinatedShutdown, 'addTask'>;\n\n /** Add a task that runs while the bot starts. */\n public readonly startup: Pick<CoordinatedStartup, 'addTask'>;\n\n protected isInitialized = false;\n protected startFailed = false;\n protected readonly plugins: PluginLike[] = [];\n\n private readonly pluginLogger = new Logger('Plugins', { channel: 'plugins' });\n\n private readonly attachments: Attachment[] = [];\n private readonly completedInits = new Set<Attachment>();\n private readonly disposePhases = new Set<ShutdownPhase>();\n private pluginTasksRegistered = false;\n private initPromise?: Promise<this> | undefined;\n\n private static isInstantiated = false;\n private static liveHost?: object | undefined;\n private static liveShutdown?: CoordinatedShutdown | undefined;\n private static liveProcessErrors?: (() => void) | undefined;\n\n constructor(shutdown: CoordinatedShutdown, startup: CoordinatedStartup) {\n // a `sideEffects: false` build would drop the same call in the node entry\n assertNodeVersion(process.env.PACKAGE_NODE_RANGE ?? '', process.version);\n\n if (Pluggable.isInstantiated) {\n // signal handlers from the caller's shutdown stay on the process unless dropped here\n shutdown.removeSignalHandlers();\n throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);\n }\n\n Pluggable.isInstantiated = true;\n Pluggable.liveHost = this;\n Pluggable.liveShutdown = shutdown;\n this[HostShutdown] = shutdown;\n this[HostStartup] = startup;\n // a getter returning the slot would hand back run() and the signal handlers with it\n this.shutdown = { addTask: shutdown.addTask.bind(shutdown) };\n this.startup = { addTask: startup.addTask.bind(startup) };\n }\n\n /** @internal */\n protected init(): Promise<this> {\n // clearing the slot on a rejection lets a later retry throw its own error\n this.initPromise ??= this.runInit().catch((caught: unknown) => {\n this.initPromise = undefined;\n throw caught;\n });\n return this.initPromise;\n }\n\n private async runInit(): Promise<this> {\n if (this.isInitialized) return 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 this.registerPluginTasks();\n\n if (this.config.errors?.catchProcessErrors ?? true) {\n Pluggable.liveProcessErrors = registerProcessErrors(this, this[HostShutdown]);\n }\n\n const startupSettled: PromiseWithResolvers<void> = Promise.withResolvers();\n this[HostShutdown].gateOnStartup(startupSettled.promise);\n\n try {\n await this[HostStartup].run();\n } catch (caught) {\n await this.rollback();\n throw caught;\n } finally {\n startupSettled.resolve();\n }\n\n this.isInitialized = true;\n return this;\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 /** @internal */\n protected static reset(host?: object): boolean {\n if (host !== undefined && Pluggable.liveHost !== host) return false;\n\n Pluggable.liveShutdown?.removeSignalHandlers();\n Pluggable.liveShutdown = undefined;\n Pluggable.liveProcessErrors?.();\n Pluggable.liveProcessErrors = undefined;\n Pluggable.liveHost = undefined;\n Pluggable.isInstantiated = false;\n return true;\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 * Startup runs each plugin's `init()` in attach order within its phase.\n *\n * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.\n * Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).\n * A narrower one fails to compile here.\n *\n * @param key - Also the channel the plugin logs on. Reserved channel names throw.\n * @param args - Whatever your constructor takes after the host.\n * @throws A **SeedcordError** if you attach after the bot has started. A taken or reserved key throws too.\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: ChannelKeyAssert<Key>,\n Plugin: Ctor &\n TransportAssert<InstanceType<Ctor>, BotT> &\n RuntimeAssert<InstanceType<Ctor>, BotRt> &\n CoreParamAssert<Ctor>,\n ...args: PluginArgs<Ctor>\n ): this & Record<Key, InstanceType<Ctor>> {\n if (this.isInitialized) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginAfterInit);\n }\n // several reserved channels are also members on a host, which the next check would report first\n if (RESERVED_KEYS.has(key)) {\n throw new SeedcordTypeError(SeedcordErrorCode.CorePluginReservedChannel, [key]);\n }\n if (key in this) {\n throw new SeedcordError(SeedcordErrorCode.CorePluginKeyExists, [key]);\n }\n\n const instance = new Plugin(this, ...args);\n pluginLoggerOf(instance).setChannel(key);\n this.plugins.push(instance);\n this.attachments.push({ key, instance });\n\n return Object.assign(this, { [key]: instance } as Record<Key, InstanceType<Ctor>>);\n }\n\n // one combined task per phase keeps plugin inits sequential while the phase's other tasks run concurrently\n private registerPluginTasks(): void {\n if (this.pluginTasksRegistered) return;\n this.pluginTasksRegistered = true;\n\n const groups = new Map<StartupPhase, Attachment[]>();\n for (const attachment of this.attachments) {\n const phase = resolvedLifecycleSpecOf(attachment.instance).init.phase;\n const group = groups.get(phase) ?? [];\n group.push(attachment);\n groups.set(phase, group);\n }\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 + resolvedLifecycleSpecOf(a.instance).init.timeout, 0);\n this.startup.addTask(phase, 'plugins-init', () => this.runInits(group), budget);\n }\n\n this.registerReadyTask(groups.get(StartupPhase.Ready) ?? []);\n }\n\n private async runInits(group: readonly Attachment[]): Promise<void> {\n for (const attachment of group) {\n const { key, instance } = attachment;\n const spec = resolvedLifecycleSpecOf(instance);\n\n pluginLoggerOf(instance).utils.initialization(key, 'start');\n await withTimeout(`Plugin (${key})`, () => instance.init(), spec.init.timeout);\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 private registerReadyTask(readyInits: readonly Attachment[]): void {\n const steps = this.attachments.reduce<{ key: string; run: () => Promise<void>; timeout: number }[]>(\n (acc, a) => {\n const ready = a.instance.ready?.bind(a.instance);\n if (ready) {\n acc.push({\n key: a.key,\n run: ready,\n timeout: resolvedLifecycleSpecOf(a.instance).ready.timeout\n });\n }\n return acc;\n },\n []\n );\n if (readyInits.length === 0 && steps.length === 0) return;\n\n const budget =\n readyInits.reduce((sum, a) => sum + resolvedLifecycleSpecOf(a.instance).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: ShutdownPhase): 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((sum, a) => {\n const spec = resolvedLifecycleSpecOf(a.instance);\n return a.instance.dispose && spec.dispose.phase === phase ? sum + spec.dispose.timeout : sum;\n }, 0);\n this.shutdown.addTask(phase, 'plugins-dispose', () => this.runDisposals(phase), budget);\n }\n\n private async runDisposals(phase: ShutdownPhase): Promise<void> {\n const failures: unknown[] = [];\n // keep disposing the rest even when one fails\n await this.disposeCompleted(phase, (caught) => failures.push(caught));\n if (failures.length === 1) throw failures[0];\n if (failures.length > 1) {\n throw new SeedcordAggregateError(SeedcordErrorCode.PluginDisposeFailures, failures, [failures.length]);\n }\n }\n\n private async disposeCompleted(\n phase: ShutdownPhase | 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 dispose = attachment.instance.dispose?.bind(attachment.instance);\n if (!dispose) continue;\n const spec = resolvedLifecycleSpecOf(attachment.instance);\n if (phase !== undefined && spec.dispose.phase !== phase) continue;\n\n try {\n await withTimeout(`Plugin:${attachment.key}:dispose`, dispose, spec.dispose.timeout);\n } catch (caught) {\n onError(caught);\n }\n }\n }\n\n private async rollback(): Promise<void> {\n this.startFailed = true;\n // log rollback dispose failures so they do not hide the startup error\n await this.disposeCompleted(undefined, (caught) => this.pluginLogger.warn('rollback dispose failed', caught));\n // prevents re-dispose when shutdown runs after rollback\n this.completedInits.clear();\n }\n}\n\n// the public `shutdown` field carries addTask alone\nexport function shutdownOf(host: Pick<Pluggable<Transport, Runtime>, typeof HostShutdown>): CoordinatedShutdown {\n return host[HostShutdown];\n}\n"],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ import { i as Bus, n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-C5Steaa2.mjs";
2
+ import { a as PluginCtor, d as Transport, f as TransportAssert, i as PluginArgs, l as Runtime, o as PluginLike, s as ChannelKeyAssert, t as CoreParamAssert, u as RuntimeAssert } from "./Plugin-BW2JH2_h.mjs";
3
+ import { Logger } from "@seedcord/logger";
4
+ import { REST } from "@discordjs/rest";
5
+ import { HostPluginKeys, HostShutdown, HostStartup } from "@seedcord/types/internal";
6
+ import { Config, IRateLimiter } from "@seedcord/types";
7
+ //#region src/node/Lifecycle/LifecycleTypes.d.ts
8
+ interface LifecycleTask {
9
+ name: string;
10
+ task: () => Promise<void>;
11
+ timeout: number;
12
+ }
13
+ //#endregion
14
+ //#region src/node/Lifecycle/CoordinatedLifecycle.d.ts
15
+ declare abstract class CoordinatedLifecycle<TPhase extends number> {
16
+ protected readonly phaseOrder: TPhase[];
17
+ protected readonly phaseEnum: Record<number, string>;
18
+ protected readonly logger: Logger;
19
+ protected readonly tasksMap: Map<TPhase, LifecycleTask[]>;
20
+ protected constructor(loggerName: string, phaseOrder: TPhase[], phaseEnum: Record<number, string>);
21
+ addTask(phase: TPhase, taskName: string, task: () => Promise<void>, timeoutMs: number): void;
22
+ removeTask(phase: TPhase, taskName: string): boolean;
23
+ protected runPhase(phase: TPhase): Promise<void>;
24
+ protected runTaskWithTimeout(phase: TPhase, task: LifecycleTask): Promise<void>;
25
+ protected isAborted(): boolean;
26
+ protected abstract canAddTask(): boolean;
27
+ protected abstract canRemoveTask(): boolean;
28
+ protected abstract getTaskType(): string;
29
+ protected abstract executeTasksInPhase(phase: TPhase, tasks: LifecycleTask[]): Promise<PromiseSettledResult<void>[]>;
30
+ }
31
+ //#endregion
32
+ //#region src/node/Lifecycle/CoordinatedShutdown.d.ts
33
+ declare class CoordinatedShutdown extends CoordinatedLifecycle<ShutdownPhase> {
34
+ private isShuttingDown;
35
+ private hasShutdown;
36
+ private exitCode;
37
+ private onSigTerm;
38
+ private onSigInt;
39
+ private startupGate?;
40
+ constructor();
41
+ protected canAddTask(): boolean;
42
+ protected canRemoveTask(): boolean;
43
+ protected getTaskType(): string;
44
+ protected executeTasksInPhase(phase: ShutdownPhase, tasks: LifecycleTask[]): Promise<PromiseSettledResult<void>[]>;
45
+ private registerSignalHandlers;
46
+ /** @internal */
47
+ removeSignalHandlers(): void;
48
+ /** @internal run() awaits this so boot finishes registering its dispose tasks first */
49
+ gateOnStartup(settled: Promise<void>): void;
50
+ addTask(phase: ShutdownPhase, taskName: string, task: () => Promise<void>, timeoutMs?: number): void;
51
+ /** @internal */
52
+ removeTask(phase: ShutdownPhase, taskName: string): boolean;
53
+ /** @internal */
54
+ run(exitCode?: number, exitProcess?: boolean): Promise<void>;
55
+ }
56
+ //#endregion
57
+ //#region src/node/Lifecycle/CoordinatedStartup.d.ts
58
+ declare class CoordinatedStartup extends CoordinatedLifecycle<StartupPhase> {
59
+ private isStartingUp;
60
+ private hasStarted;
61
+ constructor();
62
+ addTask(phase: StartupPhase, taskName: string, task: () => Promise<void>, timeoutMs?: number): void;
63
+ protected canAddTask(): boolean;
64
+ protected canRemoveTask(): boolean;
65
+ protected getTaskType(): string;
66
+ protected executeTasksInPhase(phase: StartupPhase, tasks: LifecycleTask[]): Promise<PromiseSettledResult<void>[]>;
67
+ /** @internal */
68
+ run(): Promise<void>;
69
+ protected isAborted(): boolean;
70
+ /** @internal */
71
+ abort(): void;
72
+ /** @internal */
73
+ get isReady(): boolean;
74
+ }
75
+ //#endregion
76
+ //#region src/node/Pluggable.d.ts
77
+ /**
78
+ * Base class for a plugin host, a transport `Seedcord` class.
79
+ *
80
+ * You attach plugins while configuring the bot. Within one startup phase, their `init()` calls run
81
+ * one after another in attach order.
82
+ */
83
+ declare abstract class Pluggable<BotT extends Transport, BotRt extends Runtime> implements CoreBase {
84
+ abstract readonly config: Config;
85
+ abstract readonly rest: REST;
86
+ abstract readonly applicationId: string;
87
+ abstract readonly rateLimiter: IRateLimiter;
88
+ abstract readonly bus: Bus;
89
+ /** @internal */
90
+ readonly [HostShutdown]: CoordinatedShutdown;
91
+ /** @internal */
92
+ readonly [HostStartup]: CoordinatedStartup;
93
+ /** Add a task that runs while the bot shuts down. */
94
+ readonly shutdown: Pick<CoordinatedShutdown, 'addTask'>;
95
+ /** Add a task that runs while the bot starts. */
96
+ readonly startup: Pick<CoordinatedStartup, 'addTask'>;
97
+ protected isInitialized: boolean;
98
+ protected startFailed: boolean;
99
+ protected readonly plugins: PluginLike[];
100
+ private readonly pluginLogger;
101
+ private readonly attachments;
102
+ private readonly completedInits;
103
+ private readonly disposePhases;
104
+ private pluginTasksRegistered;
105
+ private initPromise?;
106
+ private static isInstantiated;
107
+ private static liveHost?;
108
+ private static liveShutdown?;
109
+ private static liveProcessErrors?;
110
+ constructor(shutdown: CoordinatedShutdown, startup: CoordinatedStartup);
111
+ /** @internal */
112
+ protected init(): Promise<this>;
113
+ private runInit;
114
+ /** @internal codegen reads this to emit the `Core` augmentation */
115
+ get [HostPluginKeys](): readonly string[];
116
+ /** @internal */
117
+ protected static reset(host?: object): boolean;
118
+ /**
119
+ * Attaches a plugin under `key`. Read the instance back as `core[key]`. `seedcord codegen`
120
+ * writes the `Core` augmentation that types it there.
121
+ *
122
+ * Startup runs each plugin's `init()` in attach order within its phase.
123
+ *
124
+ * Attaching a plugin whose `transport` or `runtime` differs from this host fails to compile.
125
+ * Your constructor takes `CoreBase` as its first parameter (you don't need to pass it though).
126
+ * A narrower one fails to compile here.
127
+ *
128
+ * @param key - Also the channel the plugin logs on. Reserved channel names throw.
129
+ * @param args - Whatever your constructor takes after the host.
130
+ * @throws A **SeedcordError** if you attach after the bot has started. A taken or reserved key throws too.
131
+ * @example
132
+ * ```ts
133
+ * seedcord.attach('db', Mongoose, { uri: 'mongodb://...', name: 'seedcord', dir: ... });
134
+ * ```
135
+ */
136
+ attach<Key extends string, Ctor extends PluginCtor>(this: this, key: ChannelKeyAssert<Key>, Plugin: Ctor & TransportAssert<InstanceType<Ctor>, BotT> & RuntimeAssert<InstanceType<Ctor>, BotRt> & CoreParamAssert<Ctor>, ...args: PluginArgs<Ctor>): this & Record<Key, InstanceType<Ctor>>;
137
+ private registerPluginTasks;
138
+ private runInits;
139
+ private registerReadyTask;
140
+ private registerDisposeTask;
141
+ private runDisposals;
142
+ private disposeCompleted;
143
+ private rollback;
144
+ }
145
+ declare function shutdownOf(host: Pick<Pluggable<Transport, Runtime>, typeof HostShutdown>): CoordinatedShutdown;
146
+ //#endregion
147
+ export { LifecycleTask as a, CoordinatedShutdown as i, shutdownOf as n, CoordinatedStartup as r, Pluggable as t };
148
+ //# sourceMappingURL=Pluggable-bFCmT4oB.d.mts.map
@@ -1,8 +1,8 @@
1
1
  import { n as StartupPhase, t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
2
2
  import { t as getDevChannel } from "./devChannel-CQU7hq7Q.mjs";
3
3
  import { SeedcordErrorCode } from "@seedcord/errors";
4
+ import { SeedcordError, SeedcordRangeError } from "@seedcord/errors/internal";
4
5
  import { Logger } from "@seedcord/logger";
5
- import { SeedcordError } from "@seedcord/errors/internal";
6
6
 
7
7
  //#region src/plugin/lifecycle.ts
8
8
  const DEFAULT_INIT_TIMEOUT_MS = 15e3;
@@ -10,7 +10,11 @@ const DEFAULT_READY_TIMEOUT_MS = 15e3;
10
10
  const DEFAULT_DISPOSE_TIMEOUT_MS = 1e4;
11
11
  function timeoutOf(field, declared, fallback, pluginName) {
12
12
  if (declared === void 0) return fallback;
13
- if (!Number.isFinite(declared) || declared <= 0) throw new SeedcordError(SeedcordErrorCode.PluginOptionsRejected, [pluginName, `lifecycle ${field}.timeout must be a positive number of milliseconds`]);
13
+ if (!Number.isFinite(declared) || declared <= 0) throw new SeedcordRangeError(SeedcordErrorCode.PluginInvalidLifecycleTimeout, [
14
+ pluginName,
15
+ field,
16
+ declared
17
+ ]);
14
18
  return declared;
15
19
  }
16
20
  /** @internal */
@@ -111,4 +115,4 @@ function pluginLoggerOf(plugin) {
111
115
 
112
116
  //#endregion
113
117
  export { pluginLoggerOf as n, resolvedLifecycleSpecOf as r, Plugin as t };
114
- //# sourceMappingURL=Plugin-D9ykXA5g.mjs.map
118
+ //# sourceMappingURL=Plugin-B0SRHzMX.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Plugin-B0SRHzMX.mjs","names":[],"sources":["../src/plugin/lifecycle.ts","../src/plugin/Plugin.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordRangeError } from '@seedcord/errors/internal';\n\nimport { ShutdownPhase, StartupPhase } 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?: ShutdownPhase; 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: ShutdownPhase; 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.PluginInvalidLifecycleTimeout, [pluginName, field, declared]);\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 { SeedcordError } 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 { ResolvedPluginLifecycleSpec, PluginLifecycleSpec } from './lifecycle';\nimport type { TransportOf, PluginOptions, RuntimeOf } from './options';\nimport type { Tail, HmrAware, HmrUpdateEvent } from '@seedcord/types';\n\nexport interface Initializeable {\n init(): Promise<void>;\n}\n\n/** @internal */\nconst resolvedSpecSlot = Symbol('seedcord.plugin.spec');\n/** @internal */\nconst loggerSlot = Symbol('seedcord.plugin.logger');\n\n/**\n * Base class for a seedcord plugin. Extend it and implement `init()`. `this.core` is the running\n * bot.\n *\n * Your constructor takes the host first and passes it to `super()`. Add your own parameters after\n * it. `attach()` types them at the call site.\n *\n * @typeParam Opts - Where this plugin may attach, checked only at compile time.\n * @typeParam TCore - The `Core` each transport binds `this.core` to.\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, { 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<Opts extends PluginOptions = {}, TCore extends CoreBase = CoreBase>\n implements Initializeable, HmrAware\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\n /** Logs under the plugin's class name, on the channel its attach key sets. */\n protected readonly logger: Logger;\n\n // CoreBase here keeps the augmented Core out of ConstructorParameters, which attach reads\n constructor(\n private readonly host: CoreBase,\n spec?: PluginLifecycleSpec\n ) {\n this.logger = new Logger(this.constructor.name);\n this[loggerSlot] = this.logger;\n this[resolvedSpecSlot] = resolveLifecycleSpec(spec, this.constructor.name);\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 * Runs in `StartupPhase.Configuration` by default.\n * Move it to a different phase with the {@link PluginLifecycleSpec}.\n */\n abstract init(): Promise<void>;\n\n /**\n * Runs in the Ready phase, after every attached plugin's `init()` has resolved. The other Ready\n * tasks run alongside it, including the http server binding its port.\n *\n * Override it when your work needs a logged-in client or a resolved application id.\n */\n ready?(): Promise<void>;\n\n /** Runs during teardown, in `ShutdownPhase.Disconnect` by default. It never runs when `init()` has thrown. */\n dispose?(): Promise<void>;\n\n /**\n * Throws a `SeedcordError` that contains the plugin class name and the reason.\n *\n * @param reason - Why the options are invalid.\n */\n protected rejectOptions(reason: string): never {\n throw new SeedcordError(SeedcordErrorCode.PluginOptionsRejected, [this.constructor.name, reason]);\n }\n\n /** Override to reload plugin state on an HMR update. */\n public onHmr(_event: HmrUpdateEvent): Promise<void> {\n return Promise.resolve();\n }\n\n /**\n * Registers critical file patterns that should trigger a full restart when changed in Dev HMR.\n * @param patterns - Glob patterns relative to the project root\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 pluginLoggerOf(plugin: PluginLike): Logger {\n return plugin[loggerSlot];\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\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> = Tail<ConstructorParameters<Ctor>>;\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`\n/** @internal */\nexport type CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0]\n ? unknown\n : CoreParamTooNarrow;\n"],"mappings":";;;;;;;AAmCA,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,+BAA+B;EAAC;EAAY;EAAO;CAAQ,CAAC;CAE/G,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;;;;;ACnDA,MAAM,mBAAmB,OAAO,sBAAsB;;AAEtD,MAAM,aAAa,OAAO,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BlD,IAAsB,SAAtB,MAEA;CAiByB;;CATrB,CAAU;;CAEV,CAAU;;CAGV,AAAmB;CAGnB,YACI,AAAiB,MACjB,MACF;EAFmB;EAGjB,KAAK,SAAS,IAAI,OAAO,KAAK,YAAY,IAAI;EAC9C,KAAK,cAAc,KAAK;EACxB,KAAK,oBAAoB,qBAAqB,MAAM,KAAK,YAAY,IAAI;CAC7E;;CAGA,IAAc,OAAc;EAExB,OAAO,KAAK;CAChB;;;;;;CAwBA,AAAU,cAAc,QAAuB;EAC3C,MAAM,IAAI,cAAc,kBAAkB,uBAAuB,CAAC,KAAK,YAAY,MAAM,MAAM,CAAC;CACpG;;CAGA,AAAO,MAAM,QAAuC;EAChD,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,eAAe,QAA4B;CACvD,OAAO,OAAO;AAClB"}
@@ -1,4 +1,4 @@
1
- import { n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-hBxMCD2V.mjs";
1
+ import { n as StartupPhase, r as CoreBase, t as ShutdownPhase } from "./phases-C5Steaa2.mjs";
2
2
  import { Logger } from "@seedcord/logger";
3
3
  import { FrameworkChannel, HmrAware, HmrUpdateEvent, Tail, TypedExclude } from "@seedcord/types";
4
4
  //#region src/plugin/brands.d.ts
@@ -190,4 +190,4 @@ type CoreParamTooNarrow = Record<'this plugin constructor must take CoreBase as
190
190
  type CoreParamAssert<Ctor extends PluginCtor> = CoreBase extends ConstructorParameters<Ctor>[0] ? unknown : CoreParamTooNarrow;
191
191
  //#endregion
192
192
  export { PluginCtor as a, PluginOptions as c, Transport as d, TransportAssert as f, PluginArgs as i, Runtime as l, Initializeable as n, PluginLike as o, PluginLifecycleSpec as p, Plugin as r, ChannelKeyAssert as s, CoreParamAssert as t, RuntimeAssert as u };
193
- //# sourceMappingURL=Plugin-CQ9IEj5X.d.mts.map
193
+ //# sourceMappingURL=Plugin-BW2JH2_h.d.mts.map