import type { RuntimeSysAbstraction, SystemService, VoidFunc, BaseSysAbstraction, ExitHandler, ExitService, WithCementWrapperSysAbstractionParams, } from "@adviser/cement"; import { NodeFileService } from "./node-file-service.js"; import process from "node:process"; export class NodeExitServiceImpl implements ExitService { constructor() { // eslint-disable-next-line @typescript-eslint/no-unused-vars process.on("unhandledRejection", (reason: string, p: Promise) => { this.exit(19); }); // eslint-disable-next-line @typescript-eslint/no-unused-vars process.on("uncaughtException", (error: Error) => { this.exit(18); }); process.on("close", () => { this.exit(0); }); process.on("exit", () => { this.exit(0); }); process.on("SIGQUIT", () => { this.exit(3); }); process.on("SIGINT", () => { this.exit(2); }); process.on("SIGTERM", () => { this.exit(9); }); } _exitHandlers: ExitHandler[] = []; injectExitHandlers(hdls: ExitHandler[]): void { // console.log("ExitService: injecting exit handlers", hdls) this._exitHandlers = hdls; } invoked = false; readonly _handleExit = async (): Promise => { if (this.invoked) { // console.error("ExitService: already invoked"); return; } this.invoked = true; for (const h of this._exitHandlers) { try { // console.log(`ExitService: calling handler ${h.id}`) const ret = h.hdl(); // console.log(`ExitService: called handler ${h.id}`, ret) if (typeof (ret as Promise).then === "function") { await ret; } } finally { // ignore } } }; exit(code: number): void { // console.log("ExitService: exit called", code) this._handleExit() .then(() => { process.exit(code); }) .catch((err) => { // eslint-disable-next-line no-console console.error("ExitService: failed to handle exit", err); process.exit(code); }); } } export class NodeSystemService implements SystemService { static readonly _exitHandlers: ExitHandler[] = []; readonly _exitService: ExitService = new NodeExitServiceImpl(); constructor() { this._exitService.injectExitHandlers(NodeSystemService._exitHandlers); } OnExit(hdl: VoidFunc): VoidFunc { const id = crypto.randomUUID(); NodeSystemService._exitHandlers.push({ hdl, id }); return () => { const idx = NodeSystemService._exitHandlers.findIndex((h) => h.id === id); if (idx >= 0) { NodeSystemService._exitHandlers.splice(idx, 1); } }; } Exit(code: number): void { this._exitService.exit(code); } } let baseSysAbstraction: BaseSysAbstraction | undefined = undefined; export function NodeSysAbstraction(param: WithCementWrapperSysAbstractionParams): RuntimeSysAbstraction { const ende = param?.TxtEnDecoder || param.cement.TxtEnDecoderSingleton(); baseSysAbstraction = baseSysAbstraction ?? new param.cement.BaseSysAbstraction({ TxtEnDecoder: ende, FileSystem: new NodeFileService(ende), SystemService: new NodeSystemService(), }); return new param.cement.WrapperRuntimeSysAbstraction(baseSysAbstraction, { basicRuntimeService: param.cement.NodeBasicSysAbstraction(param), ...param, }); }