mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 07:05:24 +00:00
feat: collapse plugin runtime to one tier behind a plugin SDK
This commit is contained in:
1 parent
6e058f6e9e
commit
4758016f41
96 files changed
+34631
-8551
No files matched your search
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Everything a plugin registered, so deactivate can undo all of it.
|
||||
*
|
||||
* The rule this enforces: a plugin is only as disableable as its cleanup. A
|
||||
* listening port, a live SSH session or an interval that outlives deactivate
|
||||
* turns "disabled" into a lie, and re-enabling then fails on a port that is
|
||||
* still held.
|
||||
*
|
||||
* Disposal runs in reverse registration order, so a thing torn down last was
|
||||
* set up first. Every disposer is isolated: one that throws is logged and the
|
||||
* rest still run, because a half-disposed plugin is worse than a noisy log.
|
||||
*/
|
||||
|
||||
import { pluginLogger } from "../utils/logger.js";
|
||||
|
||||
export type Disposer = () => void | Promise<void>;
|
||||
|
||||
interface Entry {
|
||||
dispose: Disposer;
|
||||
/** What this is, for the log line when it throws. */
|
||||
label: string;
|
||||
}
|
||||
|
||||
export class DisposableBag {
|
||||
private entries: Entry[] = [];
|
||||
private disposed = false;
|
||||
|
||||
constructor(private readonly pluginId: string) {}
|
||||
|
||||
add(dispose: Disposer, label = "disposable"): void {
|
||||
if (this.disposed) {
|
||||
// Registering after teardown means the resource would never be cleaned
|
||||
// up. Dispose it immediately rather than silently leaking it.
|
||||
void this.run({ dispose, label });
|
||||
return;
|
||||
}
|
||||
this.entries.push({ dispose, label });
|
||||
}
|
||||
|
||||
get size(): number {
|
||||
return this.entries.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs every disposer, newest first. Safe to call more than once, and never
|
||||
* throws: the caller is already tearing the plugin down.
|
||||
*/
|
||||
async disposeAll(): Promise<void> {
|
||||
this.disposed = true;
|
||||
const entries = this.entries.reverse();
|
||||
this.entries = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
await this.run(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private async run(entry: Entry): Promise<void> {
|
||||
try {
|
||||
await entry.dispose();
|
||||
} catch (error) {
|
||||
pluginLogger.error(
|
||||
`Plugin ${this.pluginId} failed to dispose ${entry.label}`,
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
{ operation: "plugin_dispose" },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user