@seedcord/core 0.9.4 → 0.11.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +28 -10
  2. package/dist/CoordinatedShutdown-H5YjZn8A.d.mts +78 -0
  3. package/dist/CoordinatedStartup-CGnJrqcl.mjs +168 -0
  4. package/dist/CoordinatedStartup-CGnJrqcl.mjs.map +1 -0
  5. package/dist/{HmrModuleHandler-DOAtah3j.mjs → HmrModuleHandler-8ZC-UuZh.mjs} +9 -22
  6. package/dist/HmrModuleHandler-8ZC-UuZh.mjs.map +1 -0
  7. package/dist/PluginHost-B8kxLOeb.mjs +245 -0
  8. package/dist/PluginHost-B8kxLOeb.mjs.map +1 -0
  9. package/dist/{Plugin-Dhx_ivqj.d.mts → PluginHost-DB4j8unu.d.mts} +110 -17
  10. package/dist/PluginLifecycle-AhQ46NIr.mjs +158 -0
  11. package/dist/PluginLifecycle-AhQ46NIr.mjs.map +1 -0
  12. package/dist/ServerHost-CvGQr7oN.mjs +191 -0
  13. package/dist/ServerHost-CvGQr7oN.mjs.map +1 -0
  14. package/dist/ServerHost-DYeh0KTk.d.mts +40 -0
  15. package/dist/hmr.index.d.mts +0 -1
  16. package/dist/hmr.index.mjs +1 -1
  17. package/dist/{cursor-BTU5bGoo.d.mts → index-Du71Xx69.d.mts} +253 -6
  18. package/dist/index.d.mts +19 -84
  19. package/dist/index.mjs +17 -37
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/internal.index.d.mts +185 -22
  22. package/dist/internal.index.mjs +713 -19
  23. package/dist/internal.index.mjs.map +1 -1
  24. package/dist/node-internal.index.d.mts +25 -70
  25. package/dist/node-internal.index.mjs +84 -298
  26. package/dist/node-internal.index.mjs.map +1 -1
  27. package/dist/node.index.d.mts +3 -2
  28. package/dist/node.index.mjs +53 -200
  29. package/dist/node.index.mjs.map +1 -1
  30. package/dist/{phases-D_zQBXga.d.mts → phases-FTM69J3a.d.mts} +4 -4
  31. package/dist/plugin.index.d.mts +2 -2
  32. package/dist/plugin.index.mjs +2 -2
  33. package/dist/publishDefault-D3dAyzZ9.mjs +7 -0
  34. package/dist/publishDefault-D3dAyzZ9.mjs.map +1 -0
  35. package/dist/subscribers-Ce3SWy23.mjs +1913 -0
  36. package/dist/subscribers-Ce3SWy23.mjs.map +1 -0
  37. package/package.json +17 -16
  38. package/dist/Bus-BxPa-Uvp.mjs +0 -866
  39. package/dist/Bus-BxPa-Uvp.mjs.map +0 -1
  40. package/dist/HmrModuleHandler-DOAtah3j.mjs.map +0 -1
  41. package/dist/Pluggable-CXyUZIvT.mjs +0 -310
  42. package/dist/Pluggable-CXyUZIvT.mjs.map +0 -1
  43. package/dist/Pluggable-CwKbLui3.d.mts +0 -162
  44. package/dist/Plugin-CmNW5n6a.mjs +0 -122
  45. package/dist/Plugin-CmNW5n6a.mjs.map +0 -1
  46. package/dist/contextMenuLeaves-COzDYa6c.d.mts +0 -17
  47. package/dist/cursor-CgNBnJfu.mjs +0 -802
  48. package/dist/cursor-CgNBnJfu.mjs.map +0 -1
  49. package/dist/devChannel-CQU7hq7Q.mjs +0 -14
  50. package/dist/devChannel-CQU7hq7Q.mjs.map +0 -1
  51. package/dist/index-B_-EjyvS.d.mts +0 -135
  52. package/dist/publishDefault-BuSZPbjT.mjs +0 -14
  53. package/dist/publishDefault-BuSZPbjT.mjs.map +0 -1
  54. package/dist/slashRouteLeaves-k1hJnXDO.mjs +0 -31
  55. package/dist/slashRouteLeaves-k1hJnXDO.mjs.map +0 -1
  56. package/dist/subscribers-xGy7a7xR.mjs +0 -167
  57. package/dist/subscribers-xGy7a7xR.mjs.map +0 -1
  58. package/dist/types-BG0BimVr.d.mts +0 -13
@@ -1,76 +1,12 @@
1
- import { n as StartupPhase, t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
2
- import { a as withTimeout, i as settleWithin, t as Pluggable } from "./Pluggable-CXyUZIvT.mjs";
1
+ import { t as ShutdownPhase } from "./phases-BauKJOTT.mjs";
2
+ import { n as settleWithin } from "./PluginLifecycle-AhQ46NIr.mjs";
3
+ import { n as CoordinatedLifecycle, t as CoordinatedStartup } from "./CoordinatedStartup-CGnJrqcl.mjs";
4
+ import { t as ServerHost } from "./ServerHost-CvGQr7oN.mjs";
3
5
  import { SeedcordErrorCode, paint } from "@seedcord/errors";
4
- import { SeedcordAggregateError, SeedcordError, SeedcordRangeError, SeedcordTypeError } from "@seedcord/errors/internal";
5
- import { Logger } from "@seedcord/logger";
6
+ import { SeedcordRangeError } from "@seedcord/errors/internal";
6
7
 
7
- //#region src/node/Lifecycle/CoordinatedLifecycle.ts
8
- var CoordinatedLifecycle = class {
9
- phaseOrder;
10
- phaseEnum;
11
- logger;
12
- tasksMap = /* @__PURE__ */ new Map();
13
- constructor(loggerName, phaseOrder, phaseEnum) {
14
- this.phaseOrder = phaseOrder;
15
- this.phaseEnum = phaseEnum;
16
- this.logger = new Logger(loggerName, { channel: "lifecycle" });
17
- this.phaseOrder.forEach((phase) => this.tasksMap.set(phase, []));
18
- }
19
- addTask(phase, taskName, task, timeoutMs) {
20
- if (!this.canAddTask()) return;
21
- const tasks = this.tasksMap.get(phase);
22
- if (!tasks) throw new SeedcordTypeError(SeedcordErrorCode.LifecycleUnknownPhase, [phase]);
23
- tasks.push({
24
- name: taskName,
25
- task,
26
- timeout: timeoutMs
27
- });
28
- this.logger.debug(`${paint.italic("Added")} ${this.getTaskType()} task ${paint.sky.bold(taskName)} to phase ${paint.iris.bold(this.phaseEnum[phase])}`);
29
- }
30
- removeTask(phase, taskName) {
31
- if (!this.canRemoveTask()) return false;
32
- const tasks = this.tasksMap.get(phase);
33
- if (!tasks) return false;
34
- const initialLength = tasks.length;
35
- const filteredTasks = tasks.filter((task) => task.name !== taskName);
36
- this.tasksMap.set(phase, filteredTasks);
37
- const removed = initialLength !== filteredTasks.length;
38
- if (removed) this.logger.debug(`${paint.italic("Removed")} ${this.getTaskType()} task ${paint.sky.bold(taskName)} from phase ${paint.iris.bold(this.phaseEnum[phase])}`);
39
- return removed;
40
- }
41
- async runPhase(phase) {
42
- const tasks = this.tasksMap.get(phase) ?? [];
43
- if (tasks.length === 0) {
44
- this.logger.trace(`No tasks to run in phase ${paint.iris.bold(this.phaseEnum[phase])}`);
45
- return;
46
- }
47
- this.logger.debug(`${paint.amber.bold("Running")} ${this.getTaskType()} phase ${paint.iris.bold(this.phaseEnum[phase])} with ${paint.sky.bold(String(tasks.length))} tasks`);
48
- const reasons = (await this.executeTasksInPhase(phase, tasks)).reduce((rejected, result) => {
49
- if (result.status === "rejected") rejected.push(result.reason);
50
- return rejected;
51
- }, []);
52
- if (reasons.length > 0) throw new SeedcordAggregateError(SeedcordErrorCode.LifecyclePhaseFailures, reasons, [this.phaseEnum[phase], reasons.length]);
53
- this.logger.debug(`Phase ${paint.iris.bold(this.phaseEnum[phase])} ${paint.mint.bold("completed successfully")}`);
54
- }
55
- async runTaskWithTimeout(phase, task) {
56
- this.logger.trace(`${paint.italic("Starting")} task ${paint.sky.bold(task.name)} in phase ${paint.iris.bold(this.phaseEnum[phase])}`);
57
- try {
58
- await withTimeout(task.name, task.task, task.timeout);
59
- this.logger.trace(`${paint.italic("Completed")} task ${paint.sky.bold(task.name)} in phase ${paint.iris.bold(this.phaseEnum[phase])}`);
60
- } catch (error) {
61
- if (this.isAborted()) return;
62
- this.logger.error(`${paint.italic("Failed")} task ${paint.sky.bold(task.name)} in phase ${paint.iris.bold(this.phaseEnum[phase])}:`, error);
63
- throw error;
64
- }
65
- }
66
- isAborted() {
67
- return false;
68
- }
69
- };
70
-
71
- //#endregion
72
8
  //#region src/node/Lifecycle/CoordinatedShutdown.ts
73
- const PHASE_ORDER$1 = [
9
+ const PHASE_ORDER = [
74
10
  1,
75
11
  2,
76
12
  3,
@@ -79,36 +15,34 @@ const PHASE_ORDER$1 = [
79
15
  const LOG_FLUSH_DELAY_MS = 3e3;
80
16
  const DEFAULT_SHUTDOWN_DEADLINE_MS = 25e3;
81
17
  var CoordinatedShutdown = class extends CoordinatedLifecycle {
82
- isShuttingDown = false;
83
- hasShutdown = false;
84
- exitCode = 0;
85
- onSigTerm = null;
86
- onSigInt = null;
87
- startupGate;
88
- deadlineMs = DEFAULT_SHUTDOWN_DEADLINE_MS;
89
- phasesExpireAt = Infinity;
90
- runningPhase;
18
+ #running;
19
+ #exitCode = 0;
20
+ #onSigTerm = null;
21
+ #onSigInt = null;
22
+ #startupGate;
23
+ #deadlineMs = DEFAULT_SHUTDOWN_DEADLINE_MS;
24
+ #phasesExpireAt = Infinity;
25
+ #runningPhase;
91
26
  constructor(deadlineMs) {
92
- super("Shutdown", PHASE_ORDER$1, ShutdownPhase);
27
+ super("Shutdown", PHASE_ORDER, ShutdownPhase);
93
28
  if (deadlineMs !== void 0) this.setDeadline(deadlineMs);
94
- this.registerSignalHandlers();
95
29
  }
96
30
  /** @internal */
97
31
  setDeadline(deadlineMs) {
98
- if (!Number.isFinite(deadlineMs) || deadlineMs <= 0) throw new SeedcordRangeError(SeedcordErrorCode.LifecycleInvalidShutdownDeadline, [deadlineMs]);
99
- this.deadlineMs = deadlineMs;
32
+ if (!Number.isFinite(deadlineMs) || deadlineMs <= 0) throw new SeedcordRangeError(SeedcordErrorCode.LifecycleInvalidTimeout, ["lifecycle.shutdownDeadline", deadlineMs]);
33
+ this.#deadlineMs = deadlineMs;
100
34
  }
101
- async runPhases(failures) {
102
- for (const phase of PHASE_ORDER$1) {
103
- this.runningPhase = phase;
104
- if (performance.now() >= this.phasesExpireAt) return;
35
+ async #runPhases(failures) {
36
+ for (const phase of PHASE_ORDER) {
37
+ this.#runningPhase = phase;
38
+ if (performance.now() >= this.#phasesExpireAt) return;
105
39
  try {
106
40
  await this.runPhase(phase);
107
41
  } catch (error) {
108
42
  failures.push(error);
109
43
  }
110
44
  }
111
- this.runningPhase = void 0;
45
+ this.#runningPhase = void 0;
112
46
  }
113
47
  canAddTask() {
114
48
  return true;
@@ -123,31 +57,32 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
123
57
  const promises = tasks.map((task) => this.runTaskWithTimeout(phase, task));
124
58
  return Promise.allSettled(promises);
125
59
  }
60
+ /** @internal */
126
61
  registerSignalHandlers() {
127
- this.onSigTerm = () => {
62
+ this.#onSigTerm = () => {
128
63
  this.logger.info(`Received ${paint.amber.bold("SIGTERM")} signal`);
129
64
  this.run(0);
130
65
  };
131
- this.onSigInt = () => {
66
+ this.#onSigInt = () => {
132
67
  this.logger.info(`Received ${paint.amber.bold("SIGINT")} signal`);
133
68
  this.run(0);
134
69
  };
135
- process.on("SIGTERM", this.onSigTerm);
136
- process.on("SIGINT", this.onSigInt);
70
+ process.on("SIGTERM", this.#onSigTerm);
71
+ process.on("SIGINT", this.#onSigInt);
137
72
  }
138
73
  /** @internal */
139
74
  removeSignalHandlers() {
140
- if (this.onSigTerm) {
141
- process.off("SIGTERM", this.onSigTerm);
142
- this.onSigTerm = null;
75
+ if (this.#onSigTerm) {
76
+ process.off("SIGTERM", this.#onSigTerm);
77
+ this.#onSigTerm = null;
143
78
  }
144
- if (!this.onSigInt) return;
145
- process.off("SIGINT", this.onSigInt);
146
- this.onSigInt = null;
79
+ if (!this.#onSigInt) return;
80
+ process.off("SIGINT", this.#onSigInt);
81
+ this.#onSigInt = null;
147
82
  }
148
83
  /** @internal run() awaits this so boot finishes registering its dispose tasks first */
149
84
  gateOnStartup(settled) {
150
- this.startupGate = settled;
85
+ this.#startupGate = settled;
151
86
  }
152
87
  addTask(phase, taskName, task, timeoutMs = 5e3) {
153
88
  super.addTask(phase, taskName, task, timeoutMs);
@@ -157,120 +92,38 @@ var CoordinatedShutdown = class extends CoordinatedLifecycle {
157
92
  return super.removeTask(phase, taskName);
158
93
  }
159
94
  /** @internal */
160
- async run(exitCode = 0, exitProcess = true) {
95
+ run(exitCode = 0, exitProcess = true) {
161
96
  this.removeSignalHandlers();
162
- if (this.hasShutdown || this.isShuttingDown) {
163
- if (exitCode > this.exitCode) this.exitCode = exitCode;
164
- this.logger.warn("Shutdown sequence already ran or is in progress");
165
- return;
97
+ if (this.#running) {
98
+ if (exitCode > this.#exitCode) this.#exitCode = exitCode;
99
+ return this.#running;
166
100
  }
167
- this.isShuttingDown = true;
168
- this.exitCode = exitCode;
169
- this.logger.info(`${paint.amber.bold("Starting")} coordinated shutdown with exit code ${paint.sky.bold(exitCode)}`);
101
+ this.#exitCode = exitCode;
102
+ this.#running = this.#shutDown(exitProcess);
103
+ return this.#running;
104
+ }
105
+ async #shutDown(exitProcess) {
106
+ this.logger.info(`${paint.amber.bold("Starting")} coordinated shutdown with exit code ${paint.sky.bold(this.#exitCode)}`);
170
107
  try {
171
- this.phasesExpireAt = performance.now() + this.deadlineMs;
172
- if (this.startupGate) await settleWithin(this.startupGate, this.deadlineMs);
108
+ this.#phasesExpireAt = performance.now() + this.#deadlineMs;
109
+ if (this.#startupGate) await settleWithin(this.#startupGate, this.#deadlineMs);
173
110
  const failures = [];
174
- await settleWithin(this.runPhases(failures), Math.max(this.phasesExpireAt - performance.now(), 0));
175
- const caughtPhase = this.runningPhase;
176
- if (caughtPhase !== void 0) this.logger.error(`Shutdown deadline of ${paint.sky.bold(this.deadlineMs)}ms elapsed at phase ${paint.iris.bold(this.phaseEnum[caughtPhase])}`);
111
+ await settleWithin(this.#runPhases(failures), Math.max(this.#phasesExpireAt - performance.now(), 0));
112
+ const caughtPhase = this.#runningPhase;
113
+ if (caughtPhase !== void 0) this.logger.error(`Shutdown deadline of ${paint.sky.bold(this.#deadlineMs)}ms elapsed at phase ${paint.iris.bold(this.phaseEnum[caughtPhase])}`);
177
114
  if (failures.length > 0) this.logger.error(`${paint.coral.bold("Coordinated shutdown failed")}`, ...failures);
178
115
  else if (caughtPhase === void 0) this.logger.info(`${paint.mint.bold("Coordinated shutdown completed")} successfully`);
179
116
  } finally {
180
- this.hasShutdown = true;
181
117
  if (exitProcess) {
182
- this.logger.debug(`${paint.coral.bold("Exiting")} process with code ${paint.sky.bold(this.exitCode)}`);
118
+ this.logger.debug(`${paint.coral.bold("Exiting")} process with code ${paint.sky.bold(this.#exitCode)}`);
183
119
  setTimeout(() => {
184
- process.exit(this.exitCode);
120
+ process.exit(this.#exitCode);
185
121
  }, LOG_FLUSH_DELAY_MS);
186
- } else {
187
- this.logger.debug(`${paint.amber.bold("Skipping")} process exit (dev mode)`);
188
- this.isShuttingDown = false;
189
- }
190
- }
191
- }
192
- };
193
-
194
- //#endregion
195
- //#region src/node/Lifecycle/CoordinatedStartup.ts
196
- const PHASE_ORDER = [
197
- 1,
198
- 2,
199
- 3
200
- ];
201
- var CoordinatedStartup = class extends CoordinatedLifecycle {
202
- isStartingUp = false;
203
- hasStarted = false;
204
- constructor() {
205
- super("Startup", PHASE_ORDER, StartupPhase);
206
- }
207
- addTask(phase, taskName, task, timeoutMs = 1e4) {
208
- super.addTask(phase, taskName, task, timeoutMs);
209
- }
210
- canAddTask() {
211
- if (this.hasStarted) throw new SeedcordError(SeedcordErrorCode.LifecycleAddAfterCompletion);
212
- if (this.isStartingUp) throw new SeedcordError(SeedcordErrorCode.LifecycleAddDuringRun);
213
- return true;
214
- }
215
- canRemoveTask() {
216
- if (this.isStartingUp) throw new SeedcordError(SeedcordErrorCode.LifecycleRemoveDuringRun);
217
- return true;
218
- }
219
- getTaskType() {
220
- return "startup";
221
- }
222
- async executeTasksInPhase(phase, tasks) {
223
- const promises = tasks.map((task) => this.runTaskWithTimeout(phase, task));
224
- return Promise.allSettled(promises);
225
- }
226
- /** @internal */
227
- async run() {
228
- if (this.hasStarted) {
229
- this.logger.warn("Startup sequence has already completed");
230
- return;
231
- }
232
- if (this.isStartingUp) {
233
- this.logger.warn("Startup sequence already in progress");
234
- return;
122
+ } else this.logger.debug(`${paint.amber.bold("Skipping")} process exit (dev mode)`);
235
123
  }
236
- this.isStartingUp = true;
237
- this.logger.info(`${paint.mint.bold("Starting")} coordinated startup sequence`);
238
- try {
239
- for (const phase of PHASE_ORDER) {
240
- if (!this.isStartingUp) {
241
- this.logger.warn("Startup sequence aborted");
242
- return;
243
- }
244
- await this.runPhase(phase);
245
- }
246
- this.hasStarted = true;
247
- this.logger.info(`${paint.mint.bold("Coordinated startup completed")} successfully`);
248
- } catch (error) {
249
- if (!this.isStartingUp) {
250
- this.logger.warn("Startup sequence aborted during error handling");
251
- return;
252
- }
253
- this.logger.error(`${paint.coral.bold("Coordinated startup failed")}`);
254
- throw error;
255
- } finally {
256
- this.isStartingUp = false;
257
- }
258
- }
259
- isAborted() {
260
- return !this.isStartingUp;
261
- }
262
- /** @internal */
263
- abort() {
264
- if (!this.isStartingUp) return;
265
- this.isStartingUp = false;
266
- this.logger.warn("Aborting coordinated startup sequence");
267
- }
268
- /** @internal */
269
- get isReady() {
270
- return this.hasStarted;
271
124
  }
272
125
  };
273
126
 
274
127
  //#endregion
275
- export { CoordinatedShutdown, CoordinatedStartup, Pluggable };
128
+ export { CoordinatedShutdown, CoordinatedStartup, ServerHost };
276
129
  //# sourceMappingURL=node.index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"node.index.mjs","names":["PHASE_ORDER"],"sources":["../src/node/Lifecycle/CoordinatedLifecycle.ts","../src/node/Lifecycle/CoordinatedShutdown.ts","../src/node/Lifecycle/CoordinatedStartup.ts"],"sourcesContent":["/*\n * Inspired by Akka Coordinated Shutdown: https://doc.akka.io/libraries/akka-core/current/coordinated-shutdown.html\n * and Lewis's implementation in a private repo elsewhere (https://github.com/Yomanz)\n */\n\nimport { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordAggregateError, SeedcordTypeError } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\n\nimport { withTimeout } from './withTimeout';\n\nimport type { LifecycleTask } from './LifecycleTypes';\n\n// base for the startup and shutdown coordinators, runs phase-ordered tasks off a per-phase map\nexport abstract class CoordinatedLifecycle<TPhase extends number> {\n protected readonly logger: Logger;\n protected readonly tasksMap = new Map<TPhase, LifecycleTask[]>();\n\n protected constructor(\n loggerName: string,\n protected readonly phaseOrder: TPhase[],\n protected readonly phaseEnum: Record<number, string>\n ) {\n this.logger = new Logger(loggerName, { channel: 'lifecycle' });\n this.phaseOrder.forEach((phase) => this.tasksMap.set(phase, []));\n }\n\n public addTask(phase: TPhase, taskName: string, task: () => Promise<void>, timeoutMs: number): void {\n if (!this.canAddTask()) return;\n\n const tasks = this.tasksMap.get(phase);\n if (!tasks) {\n throw new SeedcordTypeError(SeedcordErrorCode.LifecycleUnknownPhase, [phase]);\n }\n\n tasks.push({ name: taskName, task, timeout: timeoutMs });\n this.logger.debug(\n `${paint.italic('Added')} ${this.getTaskType()} task ${paint.sky.bold(taskName)} to phase ${paint.iris.bold(this.phaseEnum[phase])}`\n );\n }\n\n public removeTask(phase: TPhase, taskName: string): boolean {\n if (!this.canRemoveTask()) return false;\n\n const tasks = this.tasksMap.get(phase);\n if (!tasks) return false;\n\n const initialLength = tasks.length;\n const filteredTasks = tasks.filter((task) => task.name !== taskName);\n this.tasksMap.set(phase, filteredTasks);\n\n const removed = initialLength !== filteredTasks.length;\n if (removed) {\n this.logger.debug(\n `${paint.italic('Removed')} ${this.getTaskType()} task ${paint.sky.bold(taskName)} from phase ${paint.iris.bold(this.phaseEnum[phase])}`\n );\n }\n\n return removed;\n }\n\n protected async runPhase(phase: TPhase): Promise<void> {\n const tasks = this.tasksMap.get(phase) ?? [];\n if (tasks.length === 0) {\n this.logger.trace(`No tasks to run in phase ${paint.iris.bold(this.phaseEnum[phase])}`);\n return;\n }\n\n this.logger.debug(\n `${paint.amber.bold('Running')} ${this.getTaskType()} phase ${paint.iris.bold(this.phaseEnum[phase])} with ${paint.sky.bold(String(tasks.length))} tasks`\n );\n\n const results: PromiseSettledResult<void>[] = await this.executeTasksInPhase(phase, tasks);\n\n const reasons = results.reduce<unknown[]>((rejected, result) => {\n if (result.status === 'rejected') rejected.push(result.reason as unknown);\n return rejected;\n }, []);\n if (reasons.length > 0) {\n // paint here would leak ANSI codes into the serialized error message (the unknown-exception webhook)\n throw new SeedcordAggregateError(SeedcordErrorCode.LifecyclePhaseFailures, reasons, [\n this.phaseEnum[phase],\n reasons.length\n ]);\n }\n\n this.logger.debug(\n `Phase ${paint.iris.bold(this.phaseEnum[phase])} ${paint.mint.bold('completed successfully')}`\n );\n }\n\n protected async runTaskWithTimeout(phase: TPhase, task: LifecycleTask): Promise<void> {\n this.logger.trace(\n `${paint.italic('Starting')} task ${paint.sky.bold(task.name)} in phase ${paint.iris.bold(this.phaseEnum[phase])}`\n );\n\n try {\n await withTimeout(task.name, task.task, task.timeout);\n\n this.logger.trace(\n `${paint.italic('Completed')} task ${paint.sky.bold(task.name)} in phase ${paint.iris.bold(this.phaseEnum[phase])}`\n );\n } catch (error) {\n if (this.isAborted()) return;\n this.logger.error(\n `${paint.italic('Failed')} task ${paint.sky.bold(task.name)} in phase ${paint.iris.bold(this.phaseEnum[phase])}:`,\n error\n );\n throw error;\n }\n }\n\n protected isAborted(): boolean {\n return false;\n }\n\n protected abstract canAddTask(): boolean;\n protected abstract canRemoveTask(): boolean;\n protected abstract getTaskType(): string;\n protected abstract executeTasksInPhase(\n phase: TPhase,\n tasks: LifecycleTask[]\n ): Promise<PromiseSettledResult<void>[]>;\n}\n","import { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordRangeError } from '@seedcord/errors/internal';\n\nimport { ShutdownPhase } from '#src/lifecycle/phases';\n\nimport { CoordinatedLifecycle } from './CoordinatedLifecycle';\nimport { settleWithin } from './withTimeout';\n\nimport type { LifecycleTask } from './LifecycleTypes';\n\nconst PHASE_ORDER: ShutdownPhase[] = [\n ShutdownPhase.Unbind,\n ShutdownPhase.Drain,\n ShutdownPhase.Disconnect,\n ShutdownPhase.Logout\n];\n\n// gives the logger's file sink time to flush before process.exit\nconst LOG_FLUSH_DELAY_MS = 3000;\n\n// 25s plus LOG_FLUSH_DELAY_MS stays under kubernetes' 30s SIGKILL window\nconst DEFAULT_SHUTDOWN_DEADLINE_MS = 25_000;\n\nexport class CoordinatedShutdown extends CoordinatedLifecycle<ShutdownPhase> {\n private isShuttingDown = false;\n private hasShutdown = false;\n private exitCode = 0;\n private onSigTerm: (() => void) | null = null;\n private onSigInt: (() => void) | null = null;\n private startupGate?: Promise<void>;\n private deadlineMs = DEFAULT_SHUTDOWN_DEADLINE_MS;\n private phasesExpireAt = Infinity;\n private runningPhase: ShutdownPhase | undefined;\n\n // the signal handlers below can fire before start() ever runs\n public constructor(deadlineMs?: number) {\n super('Shutdown', PHASE_ORDER, ShutdownPhase);\n\n if (deadlineMs !== undefined) this.setDeadline(deadlineMs);\n this.registerSignalHandlers();\n }\n\n /** @internal */\n public setDeadline(deadlineMs: number): void {\n if (!Number.isFinite(deadlineMs) || deadlineMs <= 0) {\n throw new SeedcordRangeError(SeedcordErrorCode.LifecycleInvalidShutdownDeadline, [deadlineMs]);\n }\n this.deadlineMs = deadlineMs;\n }\n\n private async runPhases(failures: unknown[]): Promise<void> {\n for (const phase of PHASE_ORDER) {\n // set above the check so the reported phase is the one that never started\n this.runningPhase = phase;\n // runPhases keeps going after settleWithin stops waiting on it\n if (performance.now() >= this.phasesExpireAt) return;\n try {\n await this.runPhase(phase);\n } catch (error) {\n failures.push(error);\n }\n }\n this.runningPhase = undefined;\n }\n\n protected canAddTask(): boolean {\n return true;\n }\n\n protected canRemoveTask(): boolean {\n return true;\n }\n\n protected getTaskType(): string {\n return 'shutdown';\n }\n\n protected async executeTasksInPhase(\n phase: ShutdownPhase,\n tasks: LifecycleTask[]\n ): Promise<PromiseSettledResult<void>[]> {\n const promises = tasks.map((task) => this.runTaskWithTimeout(phase, task));\n return Promise.allSettled(promises);\n }\n\n private registerSignalHandlers(): void {\n this.onSigTerm = () => {\n this.logger.info(`Received ${paint.amber.bold('SIGTERM')} signal`);\n void this.run(0);\n };\n\n this.onSigInt = () => {\n this.logger.info(`Received ${paint.amber.bold('SIGINT')} signal`);\n void this.run(0);\n };\n\n process.on('SIGTERM', this.onSigTerm);\n process.on('SIGINT', this.onSigInt);\n }\n\n /** @internal */\n public removeSignalHandlers(): void {\n if (this.onSigTerm) {\n process.off('SIGTERM', this.onSigTerm);\n this.onSigTerm = null;\n }\n if (!this.onSigInt) {\n return;\n }\n\n process.off('SIGINT', this.onSigInt);\n this.onSigInt = null;\n }\n\n /** @internal run() awaits this so boot finishes registering its dispose tasks first */\n public gateOnStartup(settled: Promise<void>): void {\n this.startupGate = settled;\n }\n\n public override addTask(phase: ShutdownPhase, taskName: string, task: () => Promise<void>, timeoutMs = 5000): void {\n super.addTask(phase, taskName, task, timeoutMs);\n }\n\n /** @internal */\n public override removeTask(phase: ShutdownPhase, taskName: string): boolean {\n return super.removeTask(phase, taskName);\n }\n\n /** @internal */\n public async run(exitCode = 0, exitProcess = true): Promise<void> {\n this.removeSignalHandlers();\n\n // a dev-mode run leaves the process alive, so a second call would re-execute every task\n if (this.hasShutdown || this.isShuttingDown) {\n // a crash mid-shutdown must still leave a failing code for whatever supervises the process\n if (exitCode > this.exitCode) this.exitCode = exitCode;\n this.logger.warn('Shutdown sequence already ran or is in progress');\n return;\n }\n\n this.isShuttingDown = true;\n this.exitCode = exitCode;\n this.logger.info(\n `${paint.amber.bold('Starting')} coordinated shutdown with exit code ${paint.sky.bold(exitCode)}`\n );\n\n try {\n this.phasesExpireAt = performance.now() + this.deadlineMs;\n // a startup that outlasts the deadline leaves its own dispose tasks unregistered\n if (this.startupGate) await settleWithin(this.startupGate, this.deadlineMs);\n\n const failures: unknown[] = [];\n await settleWithin(this.runPhases(failures), Math.max(this.phasesExpireAt - performance.now(), 0));\n\n const caughtPhase = this.runningPhase;\n if (caughtPhase !== undefined) {\n this.logger.error(\n `Shutdown deadline of ${paint.sky.bold(this.deadlineMs)}ms elapsed at phase ${paint.iris.bold(this.phaseEnum[caughtPhase])}`\n );\n }\n\n if (failures.length > 0) {\n this.logger.error(`${paint.coral.bold('Coordinated shutdown failed')}`, ...failures);\n } else if (caughtPhase === undefined) {\n this.logger.info(`${paint.mint.bold('Coordinated shutdown completed')} successfully`);\n }\n } finally {\n this.hasShutdown = true;\n if (exitProcess) {\n this.logger.debug(`${paint.coral.bold('Exiting')} process with code ${paint.sky.bold(this.exitCode)}`);\n setTimeout(() => {\n process.exit(this.exitCode);\n }, LOG_FLUSH_DELAY_MS);\n } else {\n this.logger.debug(`${paint.amber.bold('Skipping')} process exit (dev mode)`);\n this.isShuttingDown = false;\n }\n }\n }\n}\n","import { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\nimport { StartupPhase } from '#src/lifecycle/phases';\n\nimport { CoordinatedLifecycle } from './CoordinatedLifecycle';\n\nimport type { LifecycleTask } from './LifecycleTypes';\n\nconst PHASE_ORDER: StartupPhase[] = [StartupPhase.Configuration, StartupPhase.Login, StartupPhase.Ready];\n\nexport class CoordinatedStartup extends CoordinatedLifecycle<StartupPhase> {\n private isStartingUp = false;\n private hasStarted = false;\n\n public constructor() {\n super('Startup', PHASE_ORDER, StartupPhase);\n }\n\n public override addTask(\n phase: StartupPhase,\n taskName: string,\n task: () => Promise<void>,\n timeoutMs = 10_000\n ): void {\n super.addTask(phase, taskName, task, timeoutMs);\n }\n\n protected canAddTask(): boolean {\n if (this.hasStarted) {\n throw new SeedcordError(SeedcordErrorCode.LifecycleAddAfterCompletion);\n }\n\n if (this.isStartingUp) {\n throw new SeedcordError(SeedcordErrorCode.LifecycleAddDuringRun);\n }\n\n return true;\n }\n\n protected canRemoveTask(): boolean {\n if (this.isStartingUp) {\n throw new SeedcordError(SeedcordErrorCode.LifecycleRemoveDuringRun);\n }\n\n return true;\n }\n\n protected getTaskType(): string {\n return 'startup';\n }\n\n protected async executeTasksInPhase(\n phase: StartupPhase,\n tasks: LifecycleTask[]\n ): Promise<PromiseSettledResult<void>[]> {\n const promises = tasks.map((task) => this.runTaskWithTimeout(phase, task));\n return Promise.allSettled(promises);\n }\n\n // each phase completes fully before the next begins\n /** @internal */\n public async run(): Promise<void> {\n if (this.hasStarted) {\n this.logger.warn('Startup sequence has already completed');\n return;\n }\n\n if (this.isStartingUp) {\n this.logger.warn('Startup sequence already in progress');\n return;\n }\n\n this.isStartingUp = true;\n this.logger.info(`${paint.mint.bold('Starting')} coordinated startup sequence`);\n\n try {\n for (const phase of PHASE_ORDER) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- abort() can flip isStartingUp to false mid-loop from the cli\n if (!this.isStartingUp) {\n this.logger.warn('Startup sequence aborted');\n return;\n }\n await this.runPhase(phase);\n }\n\n this.hasStarted = true;\n this.logger.info(`${paint.mint.bold('Coordinated startup completed')} successfully`);\n } catch (error) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- abort() can flip isStartingUp to false before this catch runs\n if (!this.isStartingUp) {\n this.logger.warn('Startup sequence aborted during error handling');\n return;\n }\n this.logger.error(`${paint.coral.bold('Coordinated startup failed')}`);\n throw error;\n } finally {\n this.isStartingUp = false;\n }\n }\n\n protected override isAborted(): boolean {\n return !this.isStartingUp;\n }\n\n /** @internal */\n public abort(): void {\n if (!this.isStartingUp) return;\n\n this.isStartingUp = false;\n this.logger.warn('Aborting coordinated startup sequence');\n }\n\n /** @internal */\n public get isReady(): boolean {\n return this.hasStarted;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"node.index.mjs","names":[],"sources":["../src/node/Lifecycle/CoordinatedShutdown.ts"],"sourcesContent":["import { SeedcordErrorCode, paint } from '@seedcord/errors';\nimport { SeedcordRangeError } from '@seedcord/errors/internal';\n\nimport { CoordinatedLifecycle } from '#src/lifecycle/CoordinatedLifecycle';\nimport { ShutdownPhase } from '#src/lifecycle/phases';\nimport { settleWithin } from '#src/lifecycle/withTimeout';\n\nimport type { LifecycleTask } from '#src/lifecycle/LifecycleTypes';\n\nconst PHASE_ORDER: ShutdownPhase[] = [\n ShutdownPhase.Unbind,\n ShutdownPhase.Drain,\n ShutdownPhase.Disconnect,\n ShutdownPhase.Logout\n];\n\n// gives the logger's file sink time to flush before process.exit\nconst LOG_FLUSH_DELAY_MS = 3000;\n\n// 25s plus LOG_FLUSH_DELAY_MS stays under kubernetes' 30s SIGKILL window\nconst DEFAULT_SHUTDOWN_DEADLINE_MS = 25_000;\n\nexport class CoordinatedShutdown extends CoordinatedLifecycle<ShutdownPhase> {\n #running?: Promise<void>;\n #exitCode = 0;\n #onSigTerm: (() => void) | null = null;\n #onSigInt: (() => void) | null = null;\n #startupGate?: Promise<void>;\n #deadlineMs = DEFAULT_SHUTDOWN_DEADLINE_MS;\n #phasesExpireAt = Infinity;\n #runningPhase: ShutdownPhase | undefined;\n\n public constructor(deadlineMs?: number) {\n super('Shutdown', PHASE_ORDER, ShutdownPhase);\n\n if (deadlineMs !== undefined) this.setDeadline(deadlineMs);\n }\n\n /** @internal */\n public setDeadline(deadlineMs: number): void {\n if (!Number.isFinite(deadlineMs) || deadlineMs <= 0) {\n throw new SeedcordRangeError(SeedcordErrorCode.LifecycleInvalidTimeout, [\n 'lifecycle.shutdownDeadline',\n deadlineMs\n ]);\n }\n this.#deadlineMs = deadlineMs;\n }\n\n async #runPhases(failures: unknown[]): Promise<void> {\n for (const phase of PHASE_ORDER) {\n // set above the check so the reported phase is the one that never started\n this.#runningPhase = phase;\n // runPhases keeps going after settleWithin stops waiting on it\n if (performance.now() >= this.#phasesExpireAt) return;\n try {\n await this.runPhase(phase);\n } catch (error) {\n failures.push(error);\n }\n }\n this.#runningPhase = undefined;\n }\n\n protected canAddTask(): boolean {\n return true;\n }\n\n protected canRemoveTask(): boolean {\n return true;\n }\n\n protected getTaskType(): string {\n return 'shutdown';\n }\n\n protected async executeTasksInPhase(\n phase: ShutdownPhase,\n tasks: LifecycleTask[]\n ): Promise<PromiseSettledResult<void>[]> {\n const promises = tasks.map((task) => this.runTaskWithTimeout(phase, task));\n return Promise.allSettled(promises);\n }\n\n /** @internal */\n public registerSignalHandlers(): void {\n this.#onSigTerm = () => {\n this.logger.info(`Received ${paint.amber.bold('SIGTERM')} signal`);\n void this.run(0);\n };\n\n this.#onSigInt = () => {\n this.logger.info(`Received ${paint.amber.bold('SIGINT')} signal`);\n void this.run(0);\n };\n\n process.on('SIGTERM', this.#onSigTerm);\n process.on('SIGINT', this.#onSigInt);\n }\n\n /** @internal */\n public removeSignalHandlers(): void {\n if (this.#onSigTerm) {\n process.off('SIGTERM', this.#onSigTerm);\n this.#onSigTerm = null;\n }\n if (!this.#onSigInt) {\n return;\n }\n\n process.off('SIGINT', this.#onSigInt);\n this.#onSigInt = null;\n }\n\n /** @internal run() awaits this so boot finishes registering its dispose tasks first */\n public gateOnStartup(settled: Promise<void>): void {\n this.#startupGate = settled;\n }\n\n public override addTask(\n phase: ShutdownPhase,\n taskName: string,\n task: LifecycleTask['task'],\n timeoutMs = 5000\n ): void {\n super.addTask(phase, taskName, task, timeoutMs);\n }\n\n /** @internal */\n public override removeTask(phase: ShutdownPhase, taskName: string): boolean {\n return super.removeTask(phase, taskName);\n }\n\n // a later call waits on the first run and never repeats a task\n /** @internal */\n public run(exitCode = 0, exitProcess = true): Promise<void> {\n this.removeSignalHandlers();\n\n if (this.#running) {\n // a crash mid-shutdown must still leave a failing code for whatever supervises the process\n if (exitCode > this.#exitCode) this.#exitCode = exitCode;\n return this.#running;\n }\n\n this.#exitCode = exitCode;\n this.#running = this.#shutDown(exitProcess);\n return this.#running;\n }\n\n async #shutDown(exitProcess: boolean): Promise<void> {\n this.logger.info(\n `${paint.amber.bold('Starting')} coordinated shutdown with exit code ${paint.sky.bold(this.#exitCode)}`\n );\n\n try {\n this.#phasesExpireAt = performance.now() + this.#deadlineMs;\n // a startup that outlasts the deadline leaves its own dispose tasks unregistered\n if (this.#startupGate) await settleWithin(this.#startupGate, this.#deadlineMs);\n\n const failures: unknown[] = [];\n await settleWithin(this.#runPhases(failures), Math.max(this.#phasesExpireAt - performance.now(), 0));\n\n const caughtPhase = this.#runningPhase;\n if (caughtPhase !== undefined) {\n this.logger.error(\n `Shutdown deadline of ${paint.sky.bold(this.#deadlineMs)}ms elapsed at phase ${paint.iris.bold(this.phaseEnum[caughtPhase])}`\n );\n }\n\n if (failures.length > 0) {\n this.logger.error(`${paint.coral.bold('Coordinated shutdown failed')}`, ...failures);\n } else if (caughtPhase === undefined) {\n this.logger.info(`${paint.mint.bold('Coordinated shutdown completed')} successfully`);\n }\n } finally {\n if (exitProcess) {\n this.logger.debug(`${paint.coral.bold('Exiting')} process with code ${paint.sky.bold(this.#exitCode)}`);\n setTimeout(() => {\n process.exit(this.#exitCode);\n }, LOG_FLUSH_DELAY_MS);\n } else {\n this.logger.debug(`${paint.amber.bold('Skipping')} process exit (dev mode)`);\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -119,7 +119,7 @@ declare class DispatchContext implements DispatchBag {
119
119
  /**
120
120
  * Read a key that must be present, with `undefined` stripped from the result.
121
121
  *
122
- * @throws A **SeedcordError** If nothing set the key on this dispatch.
122
+ * @throws A **SeedcordError** when nothing set the key on this dispatch.
123
123
  */
124
124
  require<Key extends keyof DispatchState>(key: Key): Exclude<DispatchState[Key], undefined>;
125
125
  }
@@ -424,8 +424,8 @@ declare class Bus extends TypedEventEmitter<SubscriptionTuples> {
424
424
  */
425
425
  publish<KeyOfSubscribers extends PublishableKey>(event: KeyOfSubscribers, data: AllSubscriptions[KeyOfSubscribers]): boolean;
426
426
  /**
427
- * @deprecated Always throws. Call {@link Bus.publish} to reach subscribers and listeners together.
428
- * @throws A **SeedcordTypeError** with `CoreBusEmitUnavailable`. `emit` reaches only the `on()` listeners.
427
+ * @deprecated Call {@link Bus.publish} to reach subscribers and listeners together.
428
+ * @throws A **SeedcordTypeError** every time, with `CoreBusEmitUnavailable`. `emit` reaches only the `on()` listeners.
429
429
  */
430
430
  emit<KeyOfSubscribers extends SubscriptionKey>(event: KeyOfSubscribers, ..._args: SubscriptionTuples[KeyOfSubscribers]): never;
431
431
  /** @internal the framework's own publish path, keyed by a symbol no public entry exports */
@@ -489,4 +489,4 @@ declare enum ShutdownPhase {
489
489
  }
490
490
  //#endregion
491
491
  export { RegisteredCount as A, InteractionMetadataKey as B, AckState as C, AckTrace as D, sendTarget as E, CommandMetadataKey as F, WebhookUrlMetadataKey as G, InteractionRouteKeys as H, EventMetadataKey as I, Notice as K, EventMiddlewareMetadataKey as L, VerifyWebhooks as M, PublishDefault as N, RegisterDefaults as O, DispatchContext as P, GatedMetadataKey as R, reportedWrite as S, checkAckLegality as T, MiddlewareKind as U, InteractionMiddlewareMetadataKey as V, SubscribeMetadataKey as W, ResponseSent as _, StoredSubscriberCtor as a, Subscriptions as b, registrationFor as c, DefaultSubscriptions as d, DispatchOutcome as f, ResponseOutcome as g, ResponseFailed as h, Bus as i, UnregisterSubscriber as j, RegisterSubscriber as k, Subscriber as l, ResponseAttempt as m, StartupPhase as n, SubscriberRegistration as o, FaultSource as p, CoreBase as r, busLoggerOf as s, ShutdownPhase as t, AttemptedWrite as u, SubscriptionData as v, ReplyMethod as w, ReplyTelemetry as x, SubscriptionKey as y, InteractionKind as z };
492
- //# sourceMappingURL=phases-D_zQBXga.d.mts.map
492
+ //# sourceMappingURL=phases-FTM69J3a.d.mts.map
@@ -1,3 +1,3 @@
1
- import { a as Plugin, f as PluginLifecycleSpec, l as PluginOptions } from "./Plugin-Dhx_ivqj.mjs";
1
+ import { f as PluginLifecycleSpec, n as PluginHost, s as Plugin, u as PluginOptions } from "./PluginHost-DB4j8unu.mjs";
2
2
  import { HmrAware, HmrUpdateEvent } from "@seedcord/types";
3
- export { type HmrAware, type HmrUpdateEvent, Plugin, type PluginLifecycleSpec, type PluginOptions };
3
+ export { type HmrAware, type HmrUpdateEvent, Plugin, PluginHost, type PluginLifecycleSpec, type PluginOptions };
@@ -1,3 +1,3 @@
1
- import { t as Plugin } from "./Plugin-CmNW5n6a.mjs";
1
+ import { a as Plugin, t as PluginHost } from "./PluginHost-B8kxLOeb.mjs";
2
2
 
3
- export { Plugin };
3
+ export { Plugin, PluginHost };
@@ -0,0 +1,7 @@
1
+ //#region src/subscribers/publishDefault.ts
2
+ /** @internal PublishableKey excludes this key so publish() never emits it externally. */
3
+ const PublishDefault = Symbol("seedcord:bus:publish-default");
4
+
5
+ //#endregion
6
+ export { PublishDefault as t };
7
+ //# sourceMappingURL=publishDefault-D3dAyzZ9.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publishDefault-D3dAyzZ9.mjs","names":[],"sources":["../src/subscribers/publishDefault.ts"],"sourcesContent":["/** @internal PublishableKey excludes this key so publish() never emits it externally. */\nexport const PublishDefault = Symbol('seedcord:bus:publish-default');\n"],"mappings":";;AACA,MAAa,iBAAiB,OAAO,8BAA8B"}