@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.
- package/README.md +28 -10
- 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 +84 -298
- 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 +17 -16
- 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-CXyUZIvT.mjs +0 -310
- package/dist/Pluggable-CXyUZIvT.mjs.map +0 -1
- package/dist/Pluggable-CwKbLui3.d.mts +0 -162
- 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
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { n as StartupPhase } from "./phases-BauKJOTT.mjs";
|
|
2
|
+
import { o as pluginLoggerOf } from "./PluginHost-B8kxLOeb.mjs";
|
|
3
|
+
import { SeedcordErrorCode, isSeedcordError } from "@seedcord/errors";
|
|
4
|
+
import { SeedcordError, throwSingleOrAggregate } from "@seedcord/errors/internal";
|
|
5
|
+
import { Logger } from "@seedcord/logger";
|
|
6
|
+
|
|
7
|
+
//#region src/notices/logUnknownFault.ts
|
|
8
|
+
function causeLine(error) {
|
|
9
|
+
const { cause } = error;
|
|
10
|
+
if (!Error.isError(cause)) return "";
|
|
11
|
+
const [first] = cause.message.split("\n");
|
|
12
|
+
return `\ncaused by ${cause.name}: ${first ?? cause.message}`;
|
|
13
|
+
}
|
|
14
|
+
function logUnknownFault(logger, uuid, error, errors) {
|
|
15
|
+
if (errors?.errorStack ?? true) logger.error(uuid, error);
|
|
16
|
+
else logger.error(`${uuid} | ${error.message}${causeLine(error)}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/lifecycle/withTimeout.ts
|
|
21
|
+
async function raceTimer(start, timeoutMs, onTimeout) {
|
|
22
|
+
let timer;
|
|
23
|
+
try {
|
|
24
|
+
await Promise.race([start(), new Promise((resolve, reject) => {
|
|
25
|
+
const expiresAt = performance.now() + timeoutMs;
|
|
26
|
+
const tick = () => {
|
|
27
|
+
const remaining = expiresAt - performance.now();
|
|
28
|
+
if (remaining > 0) {
|
|
29
|
+
timer = setTimeout(tick, remaining);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
onTimeout(resolve, reject);
|
|
33
|
+
};
|
|
34
|
+
timer = setTimeout(tick, timeoutMs);
|
|
35
|
+
})]);
|
|
36
|
+
} finally {
|
|
37
|
+
if (timer) clearTimeout(timer);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function withTimeout(name, run, timeoutMs) {
|
|
41
|
+
return raceTimer(run, timeoutMs, (_, reject) => {
|
|
42
|
+
reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [name, timeoutMs]));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function settleWithin(work, timeoutMs) {
|
|
46
|
+
return raceTimer(() => work.then(() => void 0, () => void 0), timeoutMs, (resolve) => resolve());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/lifecycle/PluginLifecycle.ts
|
|
51
|
+
var PluginLifecycle = class {
|
|
52
|
+
startup;
|
|
53
|
+
shutdown;
|
|
54
|
+
logger = new Logger("Plugins", { channel: "plugins" });
|
|
55
|
+
completedInits = /* @__PURE__ */ new Set();
|
|
56
|
+
disposePhases = /* @__PURE__ */ new Set();
|
|
57
|
+
timedOutInits = /* @__PURE__ */ new Set();
|
|
58
|
+
attachments = [];
|
|
59
|
+
registered = false;
|
|
60
|
+
constructor(startup, shutdown) {
|
|
61
|
+
this.startup = startup;
|
|
62
|
+
this.shutdown = shutdown;
|
|
63
|
+
}
|
|
64
|
+
register(attachments) {
|
|
65
|
+
if (this.registered) return;
|
|
66
|
+
this.registered = true;
|
|
67
|
+
this.attachments = [...attachments];
|
|
68
|
+
const groups = Map.groupBy(this.attachments, (attachment) => attachment.spec.init.phase);
|
|
69
|
+
for (const [phase, group] of groups) {
|
|
70
|
+
if (phase === 3) continue;
|
|
71
|
+
const budget = group.reduce((sum, a) => sum + a.spec.init.timeout, 0);
|
|
72
|
+
this.startup.addTask(phase, "plugins-init", () => this.runInits(group), budget);
|
|
73
|
+
}
|
|
74
|
+
this.registerReadyTask(groups.get(3) ?? []);
|
|
75
|
+
}
|
|
76
|
+
async rollback() {
|
|
77
|
+
await this.disposeCompleted(void 0, (caught) => this.logger.warn("rollback dispose failed", caught));
|
|
78
|
+
this.completedInits.clear();
|
|
79
|
+
}
|
|
80
|
+
afterTimedOutInits(run) {
|
|
81
|
+
if (this.timedOutInits.size === 0) {
|
|
82
|
+
run();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
Promise.allSettled(this.timedOutInits).then(run);
|
|
86
|
+
}
|
|
87
|
+
async runInits(group) {
|
|
88
|
+
for (const attachment of group) {
|
|
89
|
+
const { key, instance, spec } = attachment;
|
|
90
|
+
pluginLoggerOf(instance).utils.initialization(key, "start");
|
|
91
|
+
const running = instance.init();
|
|
92
|
+
try {
|
|
93
|
+
await withTimeout(`Plugin (${key})`, () => running, spec.init.timeout);
|
|
94
|
+
} catch (caught) {
|
|
95
|
+
if (isSeedcordError(caught, void 0, SeedcordErrorCode.LifecycleTaskTimeout)) this.disposeWhenInitResolves(attachment, running);
|
|
96
|
+
throw caught;
|
|
97
|
+
}
|
|
98
|
+
pluginLoggerOf(instance).utils.initialization(key, "end");
|
|
99
|
+
this.completedInits.add(attachment);
|
|
100
|
+
if (instance.dispose) this.registerDisposeTask(spec.dispose.phase);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
disposeWhenInitResolves({ key, instance, spec }, running) {
|
|
104
|
+
const dispose = instance.dispose?.bind(instance);
|
|
105
|
+
const settled = running.then(async () => {
|
|
106
|
+
if (!dispose) return;
|
|
107
|
+
await withTimeout(`Plugin:${key}:dispose`, dispose, spec.dispose.timeout).catch((caught) => this.logger.warn(`${key} dispose failed after a timed-out init`, caught));
|
|
108
|
+
}, (caught) => this.logger.warn(`${key} init failed after its timeout`, caught));
|
|
109
|
+
this.timedOutInits.add(settled);
|
|
110
|
+
settled.finally(() => this.timedOutInits.delete(settled));
|
|
111
|
+
}
|
|
112
|
+
registerReadyTask(readyInits) {
|
|
113
|
+
const steps = this.attachments.reduce((acc, { key, instance, spec }) => {
|
|
114
|
+
const ready = instance.ready?.bind(instance);
|
|
115
|
+
if (ready) acc.push({
|
|
116
|
+
key,
|
|
117
|
+
run: ready,
|
|
118
|
+
timeout: spec.ready.timeout
|
|
119
|
+
});
|
|
120
|
+
return acc;
|
|
121
|
+
}, []);
|
|
122
|
+
if (readyInits.length === 0 && steps.length === 0) return;
|
|
123
|
+
const budget = readyInits.reduce((sum, a) => sum + a.spec.init.timeout, 0) + steps.reduce((sum, step) => sum + step.timeout, 0);
|
|
124
|
+
this.startup.addTask(3, "plugins-ready", async () => {
|
|
125
|
+
await this.runInits(readyInits);
|
|
126
|
+
for (const step of steps) await withTimeout(`Plugin:${step.key}:ready`, step.run, step.timeout);
|
|
127
|
+
}, budget);
|
|
128
|
+
}
|
|
129
|
+
registerDisposeTask(phase) {
|
|
130
|
+
if (this.disposePhases.has(phase)) return;
|
|
131
|
+
this.disposePhases.add(phase);
|
|
132
|
+
const budget = this.attachments.reduce((sum, a) => a.instance.dispose && a.spec.dispose.phase === phase ? sum + a.spec.dispose.timeout : sum, 0);
|
|
133
|
+
this.shutdown.addTask(phase, "plugins-dispose", () => this.runDisposals(phase), budget);
|
|
134
|
+
}
|
|
135
|
+
async runDisposals(phase) {
|
|
136
|
+
const failures = [];
|
|
137
|
+
await this.disposeCompleted(phase, (caught) => failures.push(caught));
|
|
138
|
+
throwSingleOrAggregate(failures, SeedcordErrorCode.PluginDisposeFailures);
|
|
139
|
+
}
|
|
140
|
+
async disposeCompleted(phase, onError) {
|
|
141
|
+
for (const attachment of [...this.attachments].reverse()) {
|
|
142
|
+
if (!this.completedInits.has(attachment)) continue;
|
|
143
|
+
const { key, instance, spec } = attachment;
|
|
144
|
+
const dispose = instance.dispose?.bind(instance);
|
|
145
|
+
if (!dispose) continue;
|
|
146
|
+
if (phase !== void 0 && spec.dispose.phase !== phase) continue;
|
|
147
|
+
try {
|
|
148
|
+
await withTimeout(`Plugin:${key}:dispose`, dispose, spec.dispose.timeout);
|
|
149
|
+
} catch (caught) {
|
|
150
|
+
onError(caught);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
//#endregion
|
|
157
|
+
export { logUnknownFault as i, settleWithin as n, withTimeout as r, PluginLifecycle as t };
|
|
158
|
+
//# sourceMappingURL=PluginLifecycle-AhQ46NIr.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PluginLifecycle-AhQ46NIr.mjs","names":[],"sources":["../src/notices/logUnknownFault.ts","../src/lifecycle/withTimeout.ts","../src/lifecycle/PluginLifecycle.ts"],"sourcesContent":["import type { Logger } from '@seedcord/logger';\nimport type { ErrorsConfig } from '@seedcord/types';\n\nfunction causeLine(error: Error): string {\n const { cause } = error;\n if (!Error.isError(cause)) return '';\n const [first] = cause.message.split('\\n');\n return `\\ncaused by ${cause.name}: ${first ?? cause.message}`;\n}\n\nexport function logUnknownFault(logger: Logger, uuid: string, error: Error, errors: ErrorsConfig | undefined): void {\n if (errors?.errorStack ?? true) logger.error(uuid, error);\n else logger.error(`${uuid} | ${error.message}${causeLine(error)}`);\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\n// calling start() in here turns its synchronous throw into a rejection\nasync function raceTimer(\n start: () => 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 start(),\n new Promise<void>((resolve, reject) => {\n // node's timer can go off before performance.now() reaches timeoutMs\n const expiresAt = performance.now() + timeoutMs;\n const tick = (): void => {\n const remaining = expiresAt - performance.now();\n if (remaining > 0) {\n timer = setTimeout(tick, remaining);\n return;\n }\n onTimeout(resolve, reject);\n };\n timer = setTimeout(tick, timeoutMs);\n })\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n// rejects with a LifecycleTaskTimeout when run outlasts the bound\nexport function withTimeout(name: string, run: () => Promise<void>, timeoutMs: number): Promise<void> {\n return raceTimer(run, timeoutMs, (_, reject) => {\n reject(new SeedcordError(SeedcordErrorCode.LifecycleTaskTimeout, [name, timeoutMs]));\n });\n}\n\n// the caller does not get work's rejection\nexport function settleWithin(work: Promise<unknown>, timeoutMs: number): Promise<void> {\n return raceTimer(\n () =>\n work.then(\n () => undefined,\n () => undefined\n ),\n timeoutMs,\n (resolve) => resolve()\n );\n}\n","import { SeedcordErrorCode, isSeedcordError } from '@seedcord/errors';\nimport { throwSingleOrAggregate } from '@seedcord/errors/internal';\nimport { Logger } from '@seedcord/logger';\n\nimport { StartupPhase } from '#src/lifecycle/phases';\nimport { pluginLoggerOf } from '#src/plugin/Plugin';\n\nimport { withTimeout } from './withTimeout';\n\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\nimport type { ShutdownPhase } from '#src/lifecycle/phases';\nimport type { Attachment } from '#src/plugin/PluginHost';\nimport type { CoordinatedStartup } from './CoordinatedStartup';\n\ninterface ReadyStep {\n readonly key: string;\n readonly run: () => Promise<void>;\n readonly timeout: number;\n}\n\nexport class PluginLifecycle {\n private readonly logger = new Logger('Plugins', { channel: 'plugins' });\n private readonly completedInits = new Set<Attachment>();\n private readonly disposePhases = new Set<ShutdownPhase>();\n private readonly timedOutInits = new Set<Promise<void>>();\n private attachments: readonly Attachment[] = [];\n private registered = false;\n\n constructor(\n private readonly startup: Pick<CoordinatedStartup, 'addTask'>,\n private readonly shutdown: Pick<CoordinatedShutdown, 'addTask'>\n ) {}\n\n // one combined task per phase keeps plugin inits sequential while the phase's other tasks run concurrently\n public register(attachments: readonly Attachment[]): void {\n if (this.registered) return;\n this.registered = true;\n this.attachments = [...attachments];\n\n const groups = Map.groupBy(this.attachments, (attachment) => attachment.spec.init.phase);\n\n for (const [phase, group] of groups) {\n // Ready inits run in the combined Ready task, before the ready hooks\n if (phase === StartupPhase.Ready) continue;\n const budget = group.reduce((sum, a) => sum + a.spec.init.timeout, 0);\n this.startup.addTask(phase, 'plugins-init', () => this.runInits(group), budget);\n }\n\n this.registerReadyTask(groups.get(StartupPhase.Ready) ?? []);\n }\n\n public async rollback(): Promise<void> {\n // log rollback dispose failures so they do not hide the startup error\n await this.disposeCompleted(undefined, (caught) => this.logger.warn('rollback dispose failed', caught));\n // prevents re-dispose when shutdown runs after rollback\n this.completedInits.clear();\n }\n\n public afterTimedOutInits(run: () => void): void {\n if (this.timedOutInits.size === 0) {\n run();\n return;\n }\n void Promise.allSettled(this.timedOutInits).then(run);\n }\n\n private async runInits(group: readonly Attachment[]): Promise<void> {\n for (const attachment of group) {\n const { key, instance, spec } = attachment;\n\n pluginLoggerOf(instance).utils.initialization(key, 'start');\n const running = instance.init();\n try {\n await withTimeout(`Plugin (${key})`, () => running, spec.init.timeout);\n } catch (caught) {\n if (isSeedcordError(caught, undefined, SeedcordErrorCode.LifecycleTaskTimeout)) {\n this.disposeWhenInitResolves(attachment, running);\n }\n throw caught;\n }\n pluginLoggerOf(instance).utils.initialization(key, 'end');\n\n this.completedInits.add(attachment);\n if (instance.dispose) this.registerDisposeTask(spec.dispose.phase);\n }\n }\n\n // disposeCompleted skips a plugin missing from completedInits\n private disposeWhenInitResolves({ key, instance, spec }: Attachment, running: Promise<void>): void {\n const dispose = instance.dispose?.bind(instance);\n\n const settled = running.then(\n async () => {\n if (!dispose) return;\n await withTimeout(`Plugin:${key}:dispose`, dispose, spec.dispose.timeout).catch((caught: unknown) =>\n this.logger.warn(`${key} dispose failed after a timed-out init`, caught)\n );\n },\n (caught: unknown) => this.logger.warn(`${key} init failed after its timeout`, caught)\n );\n this.timedOutInits.add(settled);\n void settled.finally(() => this.timedOutInits.delete(settled));\n }\n\n private registerReadyTask(readyInits: readonly Attachment[]): void {\n const steps = this.attachments.reduce<ReadyStep[]>((acc, { key, instance, spec }) => {\n const ready = instance.ready?.bind(instance);\n if (ready) acc.push({ key, run: ready, timeout: spec.ready.timeout });\n return acc;\n }, []);\n if (readyInits.length === 0 && steps.length === 0) return;\n\n const budget =\n readyInits.reduce((sum, a) => sum + a.spec.init.timeout, 0) +\n steps.reduce((sum, step) => sum + step.timeout, 0);\n this.startup.addTask(\n StartupPhase.Ready,\n 'plugins-ready',\n async () => {\n await this.runInits(readyInits);\n for (const step of steps) {\n await withTimeout(`Plugin:${step.key}:ready`, step.run, step.timeout);\n }\n },\n budget\n );\n }\n\n private registerDisposeTask(phase: 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(\n (sum, a) => (a.instance.dispose && a.spec.dispose.phase === phase ? sum + a.spec.dispose.timeout : sum),\n 0\n );\n this.shutdown.addTask(phase, 'plugins-dispose', () => this.runDisposals(phase), budget);\n }\n\n private async runDisposals(phase: ShutdownPhase): Promise<void> {\n const failures: unknown[] = [];\n await this.disposeCompleted(phase, (caught) => failures.push(caught));\n throwSingleOrAggregate(failures, SeedcordErrorCode.PluginDisposeFailures);\n }\n\n private async disposeCompleted(\n phase: 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 { key, instance, spec } = attachment;\n const dispose = instance.dispose?.bind(instance);\n if (!dispose) continue;\n if (phase !== undefined && spec.dispose.phase !== phase) continue;\n\n try {\n await withTimeout(`Plugin:${key}:dispose`, dispose, spec.dispose.timeout);\n } catch (caught) {\n onError(caught);\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { t as PublishDefault } from "./publishDefault-D3dAyzZ9.mjs";
|
|
2
|
+
import { n as attachmentsOf, r as sealAttachments, t as PluginHost } from "./PluginHost-B8kxLOeb.mjs";
|
|
3
|
+
import { i as logUnknownFault, t as PluginLifecycle } from "./PluginLifecycle-AhQ46NIr.mjs";
|
|
4
|
+
import { SeedcordErrorCode } from "@seedcord/errors";
|
|
5
|
+
import { SeedcordError } from "@seedcord/errors/internal";
|
|
6
|
+
import { Logger, LoggerChannelRegistry } from "@seedcord/logger";
|
|
7
|
+
import { asError, runsUnderSeedcordDev } from "@seedcord/utils/internal";
|
|
8
|
+
import { HostShutdown, HostStartup } from "@seedcord/types/internal";
|
|
9
|
+
import { installNodeConsole, installNodeDefaults } from "@seedcord/logger/node";
|
|
10
|
+
import crypto from "node:crypto";
|
|
11
|
+
|
|
12
|
+
//#region src/node/assertRuntimeVersion.ts
|
|
13
|
+
const MINIMUM = ">=";
|
|
14
|
+
const SPACES = /\s/g;
|
|
15
|
+
function parseVersion(text) {
|
|
16
|
+
const [withoutBuild = ""] = text.replace(/^v/, "").split("+");
|
|
17
|
+
const [release = "", ...prerelease] = withoutBuild.split("-");
|
|
18
|
+
const parts = release.split(".").map(Number);
|
|
19
|
+
return parts.every(Number.isInteger) ? {
|
|
20
|
+
parts,
|
|
21
|
+
prerelease: prerelease.length > 0
|
|
22
|
+
} : void 0;
|
|
23
|
+
}
|
|
24
|
+
function meetsMinimum(required, running) {
|
|
25
|
+
const length = Math.max(required.parts.length, running.parts.length);
|
|
26
|
+
for (let index = 0; index < length; index++) {
|
|
27
|
+
const wanted = required.parts[index] ?? 0;
|
|
28
|
+
const got = running.parts[index] ?? 0;
|
|
29
|
+
if (got !== wanted) return got > wanted;
|
|
30
|
+
}
|
|
31
|
+
return !running.prerelease;
|
|
32
|
+
}
|
|
33
|
+
function assertDeclaredRuntime() {
|
|
34
|
+
const { bun, node } = process.versions;
|
|
35
|
+
const runtime = bun === void 0 ? {
|
|
36
|
+
name: "Node",
|
|
37
|
+
range: ">=24.11",
|
|
38
|
+
running: node
|
|
39
|
+
} : {
|
|
40
|
+
name: "Bun",
|
|
41
|
+
range: ">=1.4.2",
|
|
42
|
+
running: bun
|
|
43
|
+
};
|
|
44
|
+
const range = runtime.range.replaceAll(SPACES, "");
|
|
45
|
+
if (!range.startsWith(MINIMUM)) return;
|
|
46
|
+
const required = parseVersion(range.slice(2));
|
|
47
|
+
const running = parseVersion(runtime.running);
|
|
48
|
+
if (!required || !running || meetsMinimum(required, running)) return;
|
|
49
|
+
throw new SeedcordError(SeedcordErrorCode.UnsupportedRuntimeVersion, [
|
|
50
|
+
runtime.name,
|
|
51
|
+
runtime.range,
|
|
52
|
+
runtime.running
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/node/processErrors.ts
|
|
58
|
+
const logger = new Logger("Faults", { channel: "errors" });
|
|
59
|
+
/** @internal */
|
|
60
|
+
function registerProcessErrors(core, shutdown) {
|
|
61
|
+
function report(caught, origin) {
|
|
62
|
+
const error = asError(caught);
|
|
63
|
+
const uuid = crypto.randomUUID();
|
|
64
|
+
logUnknownFault(logger, uuid, error, core.config.errors);
|
|
65
|
+
core.bus[PublishDefault]("unknownException", {
|
|
66
|
+
uuid,
|
|
67
|
+
dispatchId: null,
|
|
68
|
+
error,
|
|
69
|
+
origin
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const onRejection = (reason) => {
|
|
73
|
+
report(reason, "process:unhandledRejection");
|
|
74
|
+
};
|
|
75
|
+
const onException = (caught) => {
|
|
76
|
+
report(caught, "process:uncaughtException");
|
|
77
|
+
shutdown.run(1);
|
|
78
|
+
};
|
|
79
|
+
process.on("unhandledRejection", onRejection);
|
|
80
|
+
process.on("uncaughtException", onException);
|
|
81
|
+
return () => {
|
|
82
|
+
process.off("unhandledRejection", onRejection);
|
|
83
|
+
process.off("uncaughtException", onException);
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/node/ServerHost.ts
|
|
89
|
+
/** Base class for a transport `Seedcord` class that runs as a long-lived node or bun process. */
|
|
90
|
+
var ServerHost = class ServerHost extends PluginHost {
|
|
91
|
+
/** @internal */
|
|
92
|
+
[HostShutdown];
|
|
93
|
+
/** @internal */
|
|
94
|
+
[HostStartup];
|
|
95
|
+
/** Add a task that runs while the bot shuts down. */
|
|
96
|
+
shutdown;
|
|
97
|
+
/** Add a task that runs while the bot starts. */
|
|
98
|
+
startup;
|
|
99
|
+
#lifecycle;
|
|
100
|
+
#startFailed = false;
|
|
101
|
+
#initPromise;
|
|
102
|
+
static #isInstantiated = false;
|
|
103
|
+
static #liveHost;
|
|
104
|
+
static #liveShutdown;
|
|
105
|
+
static #liveProcessErrors;
|
|
106
|
+
constructor(transport, shutdown, startup) {
|
|
107
|
+
assertDeclaredRuntime();
|
|
108
|
+
super(transport, "server");
|
|
109
|
+
if (ServerHost.#isInstantiated) throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);
|
|
110
|
+
installNodeConsole();
|
|
111
|
+
ServerHost.#isInstantiated = true;
|
|
112
|
+
ServerHost.#liveHost = this;
|
|
113
|
+
ServerHost.#liveShutdown = shutdown;
|
|
114
|
+
this[HostShutdown] = shutdown;
|
|
115
|
+
this[HostStartup] = startup;
|
|
116
|
+
this.shutdown = { addTask: shutdown.addTask.bind(shutdown) };
|
|
117
|
+
this.startup = { addTask: startup.addTask.bind(startup) };
|
|
118
|
+
this.#lifecycle = new PluginLifecycle(this.startup, this.shutdown);
|
|
119
|
+
}
|
|
120
|
+
/** @internal */
|
|
121
|
+
init() {
|
|
122
|
+
this.#initPromise ??= this.#runInit().catch(async (caught) => {
|
|
123
|
+
this.#initPromise = void 0;
|
|
124
|
+
await this.#release(1);
|
|
125
|
+
throw caught;
|
|
126
|
+
});
|
|
127
|
+
return this.#initPromise;
|
|
128
|
+
}
|
|
129
|
+
async #runInit() {
|
|
130
|
+
if (this.#startFailed) throw new SeedcordError(SeedcordErrorCode.LifecycleRestartAfterFailure);
|
|
131
|
+
installNodeDefaults(this.config.logger);
|
|
132
|
+
sealAttachments(this);
|
|
133
|
+
this.#lifecycle.register(attachmentsOf(this));
|
|
134
|
+
if (!runsUnderSeedcordDev()) this[HostShutdown].registerSignalHandlers();
|
|
135
|
+
if (this.config.errors?.catchProcessErrors ?? true) ServerHost.#liveProcessErrors = registerProcessErrors(this, this[HostShutdown]);
|
|
136
|
+
const startupSettled = Promise.withResolvers();
|
|
137
|
+
this[HostShutdown].gateOnStartup(startupSettled.promise);
|
|
138
|
+
try {
|
|
139
|
+
await this[HostStartup].run(() => this.openHealth());
|
|
140
|
+
} catch (caught) {
|
|
141
|
+
this.#startFailed = true;
|
|
142
|
+
await this.#lifecycle.rollback();
|
|
143
|
+
throw caught;
|
|
144
|
+
} finally {
|
|
145
|
+
startupSettled.resolve();
|
|
146
|
+
}
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
/** @internal */
|
|
150
|
+
openHealth() {}
|
|
151
|
+
/**
|
|
152
|
+
* Runs the coordinated shutdown without calling `process.exit`. A new `Seedcord` can be
|
|
153
|
+
* constructed afterwards. `await using` calls this when its scope ends.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* // the bot shuts down when this block ends
|
|
158
|
+
* await using bot = await new Seedcord(config).start();
|
|
159
|
+
* console.log(await bot.rest.get(Routes.user('@me')));
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
async [Symbol.asyncDispose]() {
|
|
163
|
+
await this.#release(0);
|
|
164
|
+
}
|
|
165
|
+
async #release(exitCode) {
|
|
166
|
+
try {
|
|
167
|
+
await this[HostShutdown].run(exitCode, false);
|
|
168
|
+
} finally {
|
|
169
|
+
ServerHost.reset(this);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** @internal */
|
|
173
|
+
static reset(host) {
|
|
174
|
+
if (host !== void 0 && ServerHost.#liveHost !== host) return false;
|
|
175
|
+
ServerHost.#liveShutdown?.removeSignalHandlers();
|
|
176
|
+
ServerHost.#liveShutdown = void 0;
|
|
177
|
+
ServerHost.#liveProcessErrors?.();
|
|
178
|
+
ServerHost.#liveProcessErrors = void 0;
|
|
179
|
+
ServerHost.#liveHost = void 0;
|
|
180
|
+
ServerHost.#isInstantiated = false;
|
|
181
|
+
LoggerChannelRegistry.instance.configure({});
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
function shutdownOf(host) {
|
|
186
|
+
return host[HostShutdown];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
//#endregion
|
|
190
|
+
export { shutdownOf as n, assertDeclaredRuntime as r, ServerHost as t };
|
|
191
|
+
//# sourceMappingURL=ServerHost-CvGQr7oN.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ServerHost-CvGQr7oN.mjs","names":[],"sources":["../src/node/assertRuntimeVersion.ts","../src/node/processErrors.ts","../src/node/ServerHost.ts"],"sourcesContent":["import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\n\n// every seedcord package writes each engines range as `>=` and a version\nconst MINIMUM = '>=';\nconst SPACES = /\\s/g;\n\ninterface Version {\n parts: number[];\n prerelease: boolean;\n}\n\nfunction parseVersion(text: string): Version | undefined {\n const [withoutBuild = ''] = text.replace(/^v/, '').split('+');\n const [release = '', ...prerelease] = withoutBuild.split('-');\n const parts = release.split('.').map(Number);\n return parts.every(Number.isInteger) ? { parts, prerelease: prerelease.length > 0 } : undefined;\n}\n\n// semver ranks 1.4.2-canary.1 below 1.4.2\nfunction meetsMinimum(required: Version, running: Version): boolean {\n const length = Math.max(required.parts.length, running.parts.length);\n for (let index = 0; index < length; index++) {\n const wanted = required.parts[index] ?? 0;\n const got = running.parts[index] ?? 0;\n if (got !== wanted) return got > wanted;\n }\n return !running.prerelease;\n}\n\nexport function assertDeclaredRuntime(): void {\n const { bun, node } = process.versions;\n // tsdown-config bakes both ranges in from this package's engines\n const runtime =\n bun === undefined\n ? { name: 'Node', range: process.env.PACKAGE_NODE_RANGE ?? '', running: node }\n : { name: 'Bun', range: process.env.PACKAGE_BUN_RANGE ?? '', running: bun };\n\n const range = runtime.range.replaceAll(SPACES, '');\n if (!range.startsWith(MINIMUM)) return;\n\n const required = parseVersion(range.slice(MINIMUM.length));\n const running = parseVersion(runtime.running);\n if (!required || !running || meetsMinimum(required, running)) return;\n\n throw new SeedcordError(SeedcordErrorCode.UnsupportedRuntimeVersion, [\n runtime.name,\n runtime.range,\n runtime.running\n ]);\n}\n","import crypto from 'node:crypto';\n\nimport { Logger } from '@seedcord/logger';\nimport { asError } from '@seedcord/utils/internal';\n\nimport { logUnknownFault } from '#src/notices/logUnknownFault';\nimport { PublishDefault } from '#subscribers/publishDefault';\n\nimport type { CoreBase } from '#interfaces/CoreBase';\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\n\nconst logger = new Logger('Faults', { channel: 'errors' });\n\n/** @internal */\nexport function registerProcessErrors(core: CoreBase, shutdown: CoordinatedShutdown): () => void {\n function report(caught: unknown, origin: string): void {\n const error = asError(caught);\n const uuid = crypto.randomUUID();\n\n logUnknownFault(logger, uuid, error, core.config.errors);\n\n core.bus[PublishDefault]('unknownException', { uuid, dispatchId: null, error, origin });\n }\n\n const onRejection = (reason: unknown): void => {\n report(reason, 'process:unhandledRejection');\n };\n\n const onException = (caught: unknown): void => {\n report(caught, 'process:uncaughtException');\n // node does not exit on its own once a listener is registered\n void shutdown.run(1);\n };\n\n process.on('unhandledRejection', onRejection);\n process.on('uncaughtException', onException);\n\n return () => {\n process.off('unhandledRejection', onRejection);\n process.off('uncaughtException', onException);\n };\n}\n","import { SeedcordErrorCode } from '@seedcord/errors';\nimport { SeedcordError } from '@seedcord/errors/internal';\nimport { LoggerChannelRegistry } from '@seedcord/logger';\nimport { installNodeConsole, installNodeDefaults } from '@seedcord/logger/node';\nimport { HostShutdown, HostStartup } from '@seedcord/types/internal';\nimport { runsUnderSeedcordDev } from '@seedcord/utils/internal';\n\nimport { assertDeclaredRuntime } from '#node/assertRuntimeVersion';\nimport { PluginLifecycle } from '#src/lifecycle/PluginLifecycle';\nimport { attachmentsOf, PluginHost, sealAttachments } from '#src/plugin/PluginHost';\n\nimport { registerProcessErrors } from './processErrors';\n\nimport type { CoordinatedShutdown } from '#node/Lifecycle/CoordinatedShutdown';\nimport type { CoordinatedStartup } from '#src/lifecycle/CoordinatedStartup';\nimport type { Transport } from '#src/plugin/options';\nimport type { Promisable } from 'type-fest';\n\n/** Base class for a transport `Seedcord` class that runs as a long-lived node or bun process. */\nexport abstract class ServerHost<BotT extends Transport> extends PluginHost<BotT, 'server'> {\n /** @internal */\n readonly [HostShutdown]: CoordinatedShutdown;\n /** @internal */\n readonly [HostStartup]: CoordinatedStartup;\n\n /** Add a task that runs while the bot shuts down. */\n public readonly shutdown: Pick<CoordinatedShutdown, 'addTask'>;\n\n /** Add a task that runs while the bot starts. */\n public readonly startup: Pick<CoordinatedStartup, 'addTask'>;\n\n readonly #lifecycle: PluginLifecycle;\n #startFailed = false;\n #initPromise?: Promise<this> | undefined;\n\n static #isInstantiated = false;\n static #liveHost?: object | undefined;\n static #liveShutdown?: CoordinatedShutdown | undefined;\n static #liveProcessErrors?: (() => void) | undefined;\n\n constructor(transport: BotT, shutdown: CoordinatedShutdown, startup: CoordinatedStartup) {\n // a `sideEffects: false` build would drop the same call in the node entry\n assertDeclaredRuntime();\n super(transport, 'server');\n\n if (ServerHost.#isInstantiated) throw new SeedcordError(SeedcordErrorCode.CoreSingletonViolation);\n\n // init() opens the log file\n installNodeConsole();\n\n ServerHost.#isInstantiated = true;\n ServerHost.#liveHost = this;\n ServerHost.#liveShutdown = shutdown;\n this[HostShutdown] = shutdown;\n this[HostStartup] = startup;\n // a getter returning the slot would expose run() and the signal handlers too\n this.shutdown = { addTask: shutdown.addTask.bind(shutdown) };\n this.startup = { addTask: startup.addTask.bind(startup) };\n this.#lifecycle = new PluginLifecycle(this.startup, this.shutdown);\n }\n\n /** @internal */\n protected init(): Promise<this> {\n // a retry after a rejection runs #runInit again and hits its restart guard\n this.#initPromise ??= this.#runInit().catch(async (caught: unknown) => {\n this.#initPromise = undefined;\n await this.#release(1);\n throw caught;\n });\n return this.#initPromise;\n }\n\n async #runInit(): Promise<this> {\n // a rerun after a failed startup would re-init the rolled-back plugins\n if (this.#startFailed) throw new SeedcordError(SeedcordErrorCode.LifecycleRestartAfterFailure);\n\n installNodeDefaults(this.config.logger);\n sealAttachments(this);\n this.#lifecycle.register(attachmentsOf(this));\n\n // codegen and the build construct the bot without starting it.\n // under seedcord dev the CLI handles SIGINT and SIGTERM\n if (!runsUnderSeedcordDev()) this[HostShutdown].registerSignalHandlers();\n if (this.config.errors?.catchProcessErrors ?? true) {\n ServerHost.#liveProcessErrors = registerProcessErrors(this, this[HostShutdown]);\n }\n\n const startupSettled: PromiseWithResolvers<void> = Promise.withResolvers();\n this[HostShutdown].gateOnStartup(startupSettled.promise);\n\n try {\n // nothing a plugin adds through startup.addTask runs after this\n await this[HostStartup].run(() => this.openHealth());\n } catch (caught) {\n this.#startFailed = true;\n await this.#lifecycle.rollback();\n throw caught;\n } finally {\n startupSettled.resolve();\n }\n\n return this;\n }\n\n /** @internal */\n protected openHealth(): Promisable<void> {}\n\n /**\n * Runs the coordinated shutdown without calling `process.exit`. A new `Seedcord` can be\n * constructed afterwards. `await using` calls this when its scope ends.\n *\n * @example\n * ```ts\n * // the bot shuts down when this block ends\n * await using bot = await new Seedcord(config).start();\n * console.log(await bot.rest.get(Routes.user('@me')));\n * ```\n */\n public async [Symbol.asyncDispose](): Promise<void> {\n await this.#release(0);\n }\n\n async #release(exitCode: number): Promise<void> {\n try {\n await this[HostShutdown].run(exitCode, false);\n } finally {\n ServerHost.reset(this);\n }\n }\n\n /** @internal */\n protected static reset(host?: object): boolean {\n if (host !== undefined && ServerHost.#liveHost !== host) return false;\n\n ServerHost.#liveShutdown?.removeSignalHandlers();\n ServerHost.#liveShutdown = undefined;\n ServerHost.#liveProcessErrors?.();\n ServerHost.#liveProcessErrors = undefined;\n ServerHost.#liveHost = undefined;\n ServerHost.#isInstantiated = false;\n // configure() keeps the dev TUI's capture sink\n LoggerChannelRegistry.instance.configure({});\n return true;\n }\n}\n\n// the public `shutdown` field carries addTask alone\nexport function shutdownOf(host: Pick<ServerHost<Transport>, typeof HostShutdown>): CoordinatedShutdown {\n return host[HostShutdown];\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { d as Transport, n as PluginHost } from "./PluginHost-DB4j8unu.mjs";
|
|
2
|
+
import { n as CoordinatedStartup, t as CoordinatedShutdown } from "./CoordinatedShutdown-H5YjZn8A.mjs";
|
|
3
|
+
import { HostShutdown, HostStartup } from "@seedcord/types/internal";
|
|
4
|
+
import { Promisable } from "type-fest";
|
|
5
|
+
//#region src/node/ServerHost.d.ts
|
|
6
|
+
/** Base class for a transport `Seedcord` class that runs as a long-lived node or bun process. */
|
|
7
|
+
declare abstract class ServerHost<BotT extends Transport> extends PluginHost<BotT, 'server'> {
|
|
8
|
+
#private;
|
|
9
|
+
/** @internal */
|
|
10
|
+
readonly [HostShutdown]: CoordinatedShutdown;
|
|
11
|
+
/** @internal */
|
|
12
|
+
readonly [HostStartup]: CoordinatedStartup;
|
|
13
|
+
/** Add a task that runs while the bot shuts down. */
|
|
14
|
+
readonly shutdown: Pick<CoordinatedShutdown, 'addTask'>;
|
|
15
|
+
/** Add a task that runs while the bot starts. */
|
|
16
|
+
readonly startup: Pick<CoordinatedStartup, 'addTask'>;
|
|
17
|
+
constructor(transport: BotT, shutdown: CoordinatedShutdown, startup: CoordinatedStartup);
|
|
18
|
+
/** @internal */
|
|
19
|
+
protected init(): Promise<this>;
|
|
20
|
+
/** @internal */
|
|
21
|
+
protected openHealth(): Promisable<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Runs the coordinated shutdown without calling `process.exit`. A new `Seedcord` can be
|
|
24
|
+
* constructed afterwards. `await using` calls this when its scope ends.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* // the bot shuts down when this block ends
|
|
29
|
+
* await using bot = await new Seedcord(config).start();
|
|
30
|
+
* console.log(await bot.rest.get(Routes.user('@me')));
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
34
|
+
/** @internal */
|
|
35
|
+
protected static reset(host?: object): boolean;
|
|
36
|
+
}
|
|
37
|
+
declare function shutdownOf(host: Pick<ServerHost<Transport>, typeof HostShutdown>): CoordinatedShutdown;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { shutdownOf as n, ServerHost as t };
|
|
40
|
+
//# sourceMappingURL=ServerHost-DYeh0KTk.d.mts.map
|
package/dist/hmr.index.d.mts
CHANGED
|
@@ -34,7 +34,6 @@ export declare class HmrModuleHandler<THandler, TMiddleware = void, TArtifacts =
|
|
|
34
34
|
trackHandler(file: string, handler: THandler): void;
|
|
35
35
|
/** Links the middleware to its source file so a later update can unload it. */
|
|
36
36
|
trackMiddleware(file: string, middleware: TMiddleware): void;
|
|
37
|
-
private isTracked;
|
|
38
37
|
private unload;
|
|
39
38
|
private reloadFile;
|
|
40
39
|
private snapshotUnits;
|
package/dist/hmr.index.mjs
CHANGED