@powerhousedao/switchboard 6.2.3-dev.42 → 6.2.3-dev.44

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/dist/server.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as resolveUnsupportedStoredDocuments, i as StoredDocumentsRefusedError, n as isPortAvailable, o as applySwitchboardReactorDefaults, r as startSwitchboard, t as deriveAttachmentServiceConfig } from "./server-CMAxYG6D.mjs";
2
+ import { a as resolveUnsupportedStoredDocuments, i as StoredDocumentsRefusedError, n as isPortAvailable, o as applySwitchboardReactorDefaults, r as startSwitchboard, t as deriveAttachmentServiceConfig } from "./server-Bk_IZzCn.mjs";
3
3
  export { StoredDocumentsRefusedError, applySwitchboardReactorDefaults, deriveAttachmentServiceConfig, isPortAvailable, resolveUnsupportedStoredDocuments, startSwitchboard };
4
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a4c8ff4a-a754-5a57-860f-5cadf1712728")}catch(e){}}();
5
- //# debugId=a4c8ff4a-a754-5a57-860f-5cadf1712728
4
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="72238471-a760-5883-8f43-7ff4b6802a34")}catch(e){}}();
5
+ //# debugId=72238471-a760-5883-8f43-7ff4b6802a34
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@powerhousedao/switchboard",
3
3
  "type": "module",
4
- "version": "6.2.3-dev.42",
4
+ "version": "6.2.3-dev.44",
5
5
  "main": "dist/index.mjs",
6
6
  "exports": {
7
7
  ".": {
@@ -41,7 +41,7 @@
41
41
  "@openfeature/core": "1.9.1",
42
42
  "@openfeature/env-var-provider": "0.3.1",
43
43
  "@openfeature/server-sdk": "1.19.0",
44
- "@powerhousedao/config": "6.2.3-dev.42",
44
+ "@powerhousedao/config": "6.2.3-dev.44",
45
45
  "@opentelemetry/api": "^1.9.0",
46
46
  "@opentelemetry/exporter-metrics-otlp-http": "^0.218.0",
47
47
  "@opentelemetry/exporter-trace-otlp-http": "^0.218.0",
@@ -55,23 +55,23 @@
55
55
  "@opentelemetry/sdk-node": "^0.218.0",
56
56
  "@opentelemetry/sdk-trace-base": "^2.7.1",
57
57
  "@opentelemetry/semantic-conventions": "^1.41.1",
58
- "@powerhousedao/opentelemetry-instrumentation-reactor": "6.2.3-dev.42",
59
- "@powerhousedao/reactor": "6.2.3-dev.42",
60
- "@powerhousedao/shared": "6.2.3-dev.42",
61
- "@powerhousedao/vetra": "6.2.3-dev.42",
62
- "@powerhousedao/reactor-api": "6.2.3-dev.42",
63
- "@powerhousedao/reactor-attachments": "6.2.3-dev.42",
64
- "@powerhousedao/reactor-drive": "6.2.3-dev.42",
65
- "@powerhousedao/reactor-group": "6.2.3-dev.42",
66
- "@powerhousedao/reactor-privacy": "6.2.3-dev.42",
67
- "@powerhousedao/reactor-workflow": "6.2.3-dev.42",
68
- "@powerhousedao/workflow": "6.2.3-dev.42",
69
- "@powerhousedao/pglite-fs": "6.2.3-dev.42",
58
+ "@powerhousedao/opentelemetry-instrumentation-reactor": "6.2.3-dev.44",
59
+ "@powerhousedao/reactor": "6.2.3-dev.44",
60
+ "@powerhousedao/shared": "6.2.3-dev.44",
61
+ "@powerhousedao/vetra": "6.2.3-dev.44",
62
+ "@powerhousedao/reactor-api": "6.2.3-dev.44",
63
+ "@powerhousedao/reactor-attachments": "6.2.3-dev.44",
64
+ "@powerhousedao/reactor-drive": "6.2.3-dev.44",
65
+ "@powerhousedao/reactor-group": "6.2.3-dev.44",
66
+ "@powerhousedao/reactor-privacy": "6.2.3-dev.44",
67
+ "@powerhousedao/reactor-workflow": "6.2.3-dev.44",
68
+ "@powerhousedao/workflow": "6.2.3-dev.44",
69
+ "@powerhousedao/pglite-fs": "6.2.3-dev.44",
70
70
  "@pyroscope/nodejs": "^0.4.5",
71
- "@renown/sdk": "6.2.3-dev.42",
71
+ "@renown/sdk": "6.2.3-dev.44",
72
72
  "@sentry/node": "^10.53.1",
73
73
  "@sentry/opentelemetry": "^10.53.1",
74
- "document-model": "6.2.3-dev.42",
74
+ "document-model": "6.2.3-dev.44",
75
75
  "dotenv": "^16.4.7",
76
76
  "express": "^4.22.1",
77
77
  "graphql": "^16",
@@ -94,6 +94,39 @@ describe("installFatalErrorShutdown", () => {
94
94
  expect(proc.exit).not.toHaveBeenCalled();
95
95
  });
96
96
 
97
+ it("still reaches exit when the logger itself throws EPIPE inside onFatal", () => {
98
+ vi.useFakeTimers();
99
+ const proc = makeProc();
100
+ const onSigterm = vi.fn();
101
+ proc.on("SIGTERM", onSigterm);
102
+ const error = vi.fn(() => {
103
+ throw Object.assign(new Error("write EPIPE"), { code: "EPIPE" });
104
+ });
105
+ const throwingLogger = { error } as unknown as Parameters<
106
+ typeof installFatalErrorShutdown
107
+ >[0];
108
+ installFatalErrorShutdown(throwingLogger, proc as never);
109
+
110
+ // Without the guard the EPIPE propagates out of the uncaughtException
111
+ // listener, which in a real process re-enters fatal handling instead of
112
+ // running the shutdown.
113
+ expect(() =>
114
+ proc.emit("uncaughtException", new Error("boom")),
115
+ ).not.toThrow();
116
+
117
+ expect(proc.kill).toHaveBeenCalledOnce();
118
+ expect(onSigterm).toHaveBeenCalledOnce();
119
+ expect(proc.exitCode).toBe(1);
120
+
121
+ vi.advanceTimersByTime(15_000);
122
+
123
+ expect(proc.exit).toHaveBeenCalledTimes(1);
124
+ expect(proc.exit).toHaveBeenCalledWith(1);
125
+ // Exactly the two guarded log attempts: the fatal report and the
126
+ // forced-exit notice. A re-entered handler would log again.
127
+ expect(error).toHaveBeenCalledTimes(2);
128
+ });
129
+
97
130
  it("routes a triggered fatal error through SIGTERM with exit code 1", () => {
98
131
  const proc = makeProc();
99
132
  const onSigterm = vi.fn();
@@ -0,0 +1,131 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import { installFatalErrorShutdown } from "../src/fatal-shutdown.mjs";
4
+ import { installObservabilitySignalHandlers } from "../src/observability-signals.mjs";
5
+
6
+ function makeProc() {
7
+ const emitter = new EventEmitter();
8
+ const proc = Object.assign(emitter, {
9
+ pid: 1234,
10
+ exitCode: undefined as number | undefined,
11
+ kill: vi.fn((_pid: number, signal: string) => {
12
+ emitter.emit(signal);
13
+ return true;
14
+ }),
15
+ exit: vi.fn(),
16
+ });
17
+ return proc;
18
+ }
19
+
20
+ const logger = {
21
+ error: vi.fn(),
22
+ } as unknown as Parameters<typeof installFatalErrorShutdown>[0];
23
+
24
+ /** Lets the finally callback behind the flush promise run. */
25
+ async function settleMicrotasks(): Promise<void> {
26
+ await Promise.resolve();
27
+ await Promise.resolve();
28
+ await Promise.resolve();
29
+ }
30
+
31
+ describe("installObservabilitySignalHandlers", () => {
32
+ afterEach(() => {
33
+ vi.useRealTimers();
34
+ });
35
+
36
+ it.each(["SIGINT", "SIGTERM"])(
37
+ "exits 0 after the flush on an ordinary %s",
38
+ async (signal) => {
39
+ const proc = makeProc();
40
+ const flush = vi.fn(() => Promise.resolve());
41
+ installObservabilitySignalHandlers(flush, proc as never);
42
+
43
+ proc.emit(signal);
44
+ await settleMicrotasks();
45
+
46
+ expect(flush).toHaveBeenCalledOnce();
47
+ expect(proc.exit).toHaveBeenCalledExactlyOnceWith(0);
48
+ },
49
+ );
50
+
51
+ it("does not exit before the flush settles", async () => {
52
+ const proc = makeProc();
53
+ let finish: () => void = () => undefined;
54
+ const flush = vi.fn(
55
+ () => new Promise<void>((resolve) => (finish = resolve)),
56
+ );
57
+ installObservabilitySignalHandlers(flush, proc as never);
58
+
59
+ proc.emit("SIGTERM");
60
+ await settleMicrotasks();
61
+ expect(proc.exit).not.toHaveBeenCalled();
62
+
63
+ finish();
64
+ await settleMicrotasks();
65
+ expect(proc.exit).toHaveBeenCalledExactlyOnceWith(0);
66
+ });
67
+
68
+ it("keeps exit code 1 when a fatal error raised the SIGTERM", async () => {
69
+ vi.useFakeTimers();
70
+ const proc = makeProc();
71
+ const flush = vi.fn(() => Promise.resolve());
72
+ // Production order: observability's handlers install at module load,
73
+ // before the fatal handlers.
74
+ installObservabilitySignalHandlers(flush, proc as never);
75
+ installFatalErrorShutdown(logger, proc as never);
76
+
77
+ proc.emit("uncaughtException", new Error("boom"));
78
+ expect(proc.exitCode).toBe(1);
79
+ await settleMicrotasks();
80
+
81
+ // The flush ran, but observability never lowered the code to 0; the
82
+ // process is still alive inside the fatal path's drain budget.
83
+ expect(flush).toHaveBeenCalledOnce();
84
+ expect(proc.exit).not.toHaveBeenCalled();
85
+
86
+ // Fatal-shutdown's backstop owns the exit and preserves code 1.
87
+ vi.advanceTimersByTime(15_000);
88
+ expect(proc.exit).toHaveBeenCalledExactlyOnceWith(1);
89
+ expect(proc.exit).not.toHaveBeenCalledWith(0);
90
+ expect(proc.exitCode).toBe(1);
91
+ });
92
+
93
+ it("leaves the fatal drain budget intact even when the flush is slow", async () => {
94
+ vi.useFakeTimers();
95
+ const proc = makeProc();
96
+ let finish: () => void = () => undefined;
97
+ const flush = vi.fn(
98
+ () => new Promise<void>((resolve) => (finish = resolve)),
99
+ );
100
+ installObservabilitySignalHandlers(flush, proc as never);
101
+ installFatalErrorShutdown(logger, proc as never);
102
+
103
+ proc.emit("uncaughtException", new Error("boom"));
104
+
105
+ // The flush gives up at its own ~5s budget (simulated by resolving it
106
+ // here); that settling must not force an exit ahead of the drain.
107
+ vi.advanceTimersByTime(5_000);
108
+ finish();
109
+ await settleMicrotasks();
110
+ expect(proc.exit).not.toHaveBeenCalled();
111
+
112
+ // Only the 15s fatal backstop ends the process, with code 1.
113
+ vi.advanceTimersByTime(9_999);
114
+ expect(proc.exit).not.toHaveBeenCalled();
115
+ vi.advanceTimersByTime(1);
116
+ expect(proc.exit).toHaveBeenCalledExactlyOnceWith(1);
117
+ });
118
+
119
+ it("respects a non-zero exit code set by any other module", async () => {
120
+ const proc = makeProc();
121
+ proc.exitCode = 1;
122
+ const flush = vi.fn(() => Promise.resolve());
123
+ installObservabilitySignalHandlers(flush, proc as never);
124
+
125
+ proc.emit("SIGTERM");
126
+ await settleMicrotasks();
127
+
128
+ expect(flush).toHaveBeenCalledOnce();
129
+ expect(proc.exit).not.toHaveBeenCalled();
130
+ });
131
+ });