@seedcord/core 0.10.0 → 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.
- package/README.md +27 -9
- package/dist/CoordinatedShutdown-H5YjZn8A.d.mts +78 -0
- package/dist/CoordinatedStartup-CGnJrqcl.mjs +168 -0
- package/dist/CoordinatedStartup-CGnJrqcl.mjs.map +1 -0
- package/dist/{HmrModuleHandler-DOAtah3j.mjs → HmrModuleHandler-8ZC-UuZh.mjs} +9 -22
- package/dist/HmrModuleHandler-8ZC-UuZh.mjs.map +1 -0
- package/dist/PluginHost-B8kxLOeb.mjs +245 -0
- package/dist/PluginHost-B8kxLOeb.mjs.map +1 -0
- package/dist/{Plugin-Dhx_ivqj.d.mts → PluginHost-DB4j8unu.d.mts} +110 -17
- package/dist/PluginLifecycle-AhQ46NIr.mjs +158 -0
- package/dist/PluginLifecycle-AhQ46NIr.mjs.map +1 -0
- package/dist/ServerHost-CvGQr7oN.mjs +191 -0
- package/dist/ServerHost-CvGQr7oN.mjs.map +1 -0
- package/dist/ServerHost-DYeh0KTk.d.mts +40 -0
- package/dist/hmr.index.d.mts +0 -1
- package/dist/hmr.index.mjs +1 -1
- package/dist/{cursor-BTU5bGoo.d.mts → index-Du71Xx69.d.mts} +253 -6
- package/dist/index.d.mts +19 -84
- package/dist/index.mjs +17 -37
- package/dist/index.mjs.map +1 -1
- package/dist/internal.index.d.mts +185 -22
- package/dist/internal.index.mjs +713 -19
- package/dist/internal.index.mjs.map +1 -1
- package/dist/node-internal.index.d.mts +25 -70
- package/dist/node-internal.index.mjs +83 -297
- package/dist/node-internal.index.mjs.map +1 -1
- package/dist/node.index.d.mts +3 -2
- package/dist/node.index.mjs +53 -200
- package/dist/node.index.mjs.map +1 -1
- package/dist/{phases-D_zQBXga.d.mts → phases-FTM69J3a.d.mts} +4 -4
- package/dist/plugin.index.d.mts +2 -2
- package/dist/plugin.index.mjs +2 -2
- package/dist/publishDefault-D3dAyzZ9.mjs +7 -0
- package/dist/publishDefault-D3dAyzZ9.mjs.map +1 -0
- package/dist/subscribers-Ce3SWy23.mjs +1913 -0
- package/dist/subscribers-Ce3SWy23.mjs.map +1 -0
- package/package.json +15 -15
- package/dist/Bus-BxPa-Uvp.mjs +0 -866
- package/dist/Bus-BxPa-Uvp.mjs.map +0 -1
- package/dist/HmrModuleHandler-DOAtah3j.mjs.map +0 -1
- package/dist/Pluggable-CwKbLui3.d.mts +0 -162
- package/dist/Pluggable-afy2H0Dr.mjs +0 -339
- package/dist/Pluggable-afy2H0Dr.mjs.map +0 -1
- package/dist/Plugin-CmNW5n6a.mjs +0 -122
- package/dist/Plugin-CmNW5n6a.mjs.map +0 -1
- package/dist/contextMenuLeaves-COzDYa6c.d.mts +0 -17
- package/dist/cursor-CgNBnJfu.mjs +0 -802
- package/dist/cursor-CgNBnJfu.mjs.map +0 -1
- package/dist/devChannel-CQU7hq7Q.mjs +0 -14
- package/dist/devChannel-CQU7hq7Q.mjs.map +0 -1
- package/dist/index-B_-EjyvS.d.mts +0 -135
- package/dist/publishDefault-BuSZPbjT.mjs +0 -14
- package/dist/publishDefault-BuSZPbjT.mjs.map +0 -1
- package/dist/slashRouteLeaves-k1hJnXDO.mjs +0 -31
- package/dist/slashRouteLeaves-k1hJnXDO.mjs.map +0 -1
- package/dist/subscribers-xGy7a7xR.mjs +0 -167
- package/dist/subscribers-xGy7a7xR.mjs.map +0 -1
- package/dist/types-BG0BimVr.d.mts +0 -13
package/dist/node.index.mjs
CHANGED
|
@@ -1,76 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
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.
|
|
99
|
-
this
|
|
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
|
|
103
|
-
this
|
|
104
|
-
if (performance.now() >= this
|
|
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
|
|
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
|
|
62
|
+
this.#onSigTerm = () => {
|
|
128
63
|
this.logger.info(`Received ${paint.amber.bold("SIGTERM")} signal`);
|
|
129
64
|
this.run(0);
|
|
130
65
|
};
|
|
131
|
-
this
|
|
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
|
|
136
|
-
process.on("SIGINT", this
|
|
70
|
+
process.on("SIGTERM", this.#onSigTerm);
|
|
71
|
+
process.on("SIGINT", this.#onSigInt);
|
|
137
72
|
}
|
|
138
73
|
/** @internal */
|
|
139
74
|
removeSignalHandlers() {
|
|
140
|
-
if (this
|
|
141
|
-
process.off("SIGTERM", this
|
|
142
|
-
this
|
|
75
|
+
if (this.#onSigTerm) {
|
|
76
|
+
process.off("SIGTERM", this.#onSigTerm);
|
|
77
|
+
this.#onSigTerm = null;
|
|
143
78
|
}
|
|
144
|
-
if (!this
|
|
145
|
-
process.off("SIGINT", this
|
|
146
|
-
this
|
|
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
|
|
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
|
-
|
|
95
|
+
run(exitCode = 0, exitProcess = true) {
|
|
161
96
|
this.removeSignalHandlers();
|
|
162
|
-
if (this
|
|
163
|
-
if (exitCode > this
|
|
164
|
-
this
|
|
165
|
-
return;
|
|
97
|
+
if (this.#running) {
|
|
98
|
+
if (exitCode > this.#exitCode) this.#exitCode = exitCode;
|
|
99
|
+
return this.#running;
|
|
166
100
|
}
|
|
167
|
-
this
|
|
168
|
-
this
|
|
169
|
-
this
|
|
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
|
|
172
|
-
if (this
|
|
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
|
|
175
|
-
const caughtPhase = this
|
|
176
|
-
if (caughtPhase !== void 0) this.logger.error(`Shutdown deadline of ${paint.sky.bold(this
|
|
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
|
|
118
|
+
this.logger.debug(`${paint.coral.bold("Exiting")} process with code ${paint.sky.bold(this.#exitCode)}`);
|
|
183
119
|
setTimeout(() => {
|
|
184
|
-
process.exit(this
|
|
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,
|
|
128
|
+
export { CoordinatedShutdown, CoordinatedStartup, ServerHost };
|
|
276
129
|
//# sourceMappingURL=node.index.mjs.map
|
package/dist/node.index.mjs.map
CHANGED
|
@@ -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**
|
|
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
|
|
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-
|
|
492
|
+
//# sourceMappingURL=phases-FTM69J3a.d.mts.map
|
package/dist/plugin.index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
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 };
|
package/dist/plugin.index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
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"}
|