@newhomestar/sdk 0.7.9 → 0.7.10
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/events.js +28 -8
- package/package.json +1 -1
package/dist/events.js
CHANGED
|
@@ -656,6 +656,14 @@ export function startInboundConsumer(db, options) {
|
|
|
656
656
|
}
|
|
657
657
|
const abort = new AbortController();
|
|
658
658
|
let reconnectAttempt = 0;
|
|
659
|
+
/** Log nested error causes for rich diagnostics (undici stores ECONNREFUSED etc. in err.cause) */
|
|
660
|
+
function _logErrorCause(err, depth = 0) {
|
|
661
|
+
if (!err?.cause || depth > 3)
|
|
662
|
+
return;
|
|
663
|
+
const indent = ' ' + ' └─'.repeat(depth + 1) + ' ';
|
|
664
|
+
console.error(`[nova/events] ${indent}cause: ${String(err.cause).slice(0, 500)}`);
|
|
665
|
+
_logErrorCause(err.cause, depth + 1);
|
|
666
|
+
}
|
|
659
667
|
/** Derive the event topic from a raw PGMQ message payload */
|
|
660
668
|
function _deriveEventType(payload) {
|
|
661
669
|
if (typeof payload.topic === 'string')
|
|
@@ -779,20 +787,25 @@ export function startInboundConsumer(db, options) {
|
|
|
779
787
|
// Node.js's built-in fetch (undici) has a default bodyTimeout of 300s (5 minutes)
|
|
780
788
|
// which kills long-lived SSE streams as if the response body "stalled".
|
|
781
789
|
// Setting bodyTimeout: 0 disables this limit for the stream connection.
|
|
782
|
-
// headersTimeout:
|
|
790
|
+
// headersTimeout: 120_000 guards against slow initial connections (was 30s — too short
|
|
791
|
+
// for containers that may take time to establish TCP to the Events Service).
|
|
783
792
|
const sseAgent = new UndiciAgent({
|
|
784
793
|
bodyTimeout: 0,
|
|
785
|
-
headersTimeout:
|
|
794
|
+
headersTimeout: 120_000,
|
|
786
795
|
keepAliveTimeout: 120_000,
|
|
787
796
|
keepAliveMaxTimeout: 600_000,
|
|
788
797
|
});
|
|
789
798
|
/** Main loop: connect, consume, reconnect */
|
|
790
799
|
async function _connect() {
|
|
800
|
+
// Log config once on startup so container logs show what we're connecting to
|
|
801
|
+
const tokenSuffix = serviceToken ? `***${serviceToken.slice(-4)}` : 'MISSING';
|
|
802
|
+
console.log(`[nova/events] SSE consumer starting — url=${eventsUrl} ` +
|
|
803
|
+
`token=${tokenSuffix} queue=${queueName} vt=${vt}s headersTimeout=120s`);
|
|
791
804
|
while (!abort.signal.aborted) {
|
|
792
805
|
const streamUrl = `${eventsUrl}/events/queue/stream?queue=${encodeURIComponent(queueName)}&vt=${vt}`;
|
|
806
|
+
const connectStart = Date.now();
|
|
793
807
|
try {
|
|
794
|
-
console.log(`[nova/events] Connecting to SSE
|
|
795
|
-
`(attempt ${reconnectAttempt + 1})…`);
|
|
808
|
+
console.log(`[nova/events] Connecting to SSE stream (attempt ${reconnectAttempt + 1}): ${streamUrl}`);
|
|
796
809
|
const response = await fetch(streamUrl, {
|
|
797
810
|
headers: { 'Authorization': `Bearer ${serviceToken}`, 'Accept': 'text/event-stream' },
|
|
798
811
|
signal: abort.signal,
|
|
@@ -806,8 +819,9 @@ export function startInboundConsumer(db, options) {
|
|
|
806
819
|
throw new Error(`SSE connect failed: HTTP ${response.status} — ${text.slice(0, 200)}`);
|
|
807
820
|
}
|
|
808
821
|
// Successfully connected — reset reconnect counter
|
|
822
|
+
const ttfb = Date.now() - connectStart;
|
|
809
823
|
reconnectAttempt = 0;
|
|
810
|
-
console.log(`[nova/events] SSE consumer connected to queue "${queueName}"`);
|
|
824
|
+
console.log(`[nova/events] SSE consumer connected to queue "${queueName}" (TTFB: ${ttfb}ms)`);
|
|
811
825
|
// Consume the stream; catch stream-level errors separately from
|
|
812
826
|
// connection-level errors so they don't trigger exponential backoff.
|
|
813
827
|
// A stream dying mid-flight (e.g. proxy timeout) is a normal lifecycle
|
|
@@ -821,7 +835,9 @@ export function startInboundConsumer(db, options) {
|
|
|
821
835
|
const isAbortError = streamErr?.name === 'AbortError' || streamErr?.code === 20;
|
|
822
836
|
if (isAbortError)
|
|
823
837
|
return;
|
|
824
|
-
|
|
838
|
+
const elapsed = Date.now() - connectStart;
|
|
839
|
+
console.error(`[nova/events] SSE stream error for queue "${queueName}" (after ${elapsed}ms):`, String(streamErr).slice(0, 500));
|
|
840
|
+
_logErrorCause(streamErr);
|
|
825
841
|
}
|
|
826
842
|
if (abort.signal.aborted) {
|
|
827
843
|
console.log(`[nova/events] SSE consumer for queue "${queueName}" shut down gracefully`);
|
|
@@ -829,7 +845,9 @@ export function startInboundConsumer(db, options) {
|
|
|
829
845
|
}
|
|
830
846
|
// Stream ended (normal close OR proxy timeout). The connection WAS
|
|
831
847
|
// established, so no exponential backoff — reconnect in a fixed 1s.
|
|
832
|
-
|
|
848
|
+
const totalDuration = Date.now() - connectStart;
|
|
849
|
+
console.log(`[nova/events] SSE stream for queue "${queueName}" closed after ${totalDuration}ms — ` +
|
|
850
|
+
`reconnecting in 1000ms…`);
|
|
833
851
|
await new Promise((resolve) => {
|
|
834
852
|
const t = setTimeout(resolve, 1_000);
|
|
835
853
|
abort.signal.addEventListener('abort', () => { clearTimeout(t); resolve(); }, { once: true });
|
|
@@ -844,7 +862,9 @@ export function startInboundConsumer(db, options) {
|
|
|
844
862
|
const isAbortError = err?.name === 'AbortError' || err?.code === 20;
|
|
845
863
|
if (isAbortError)
|
|
846
864
|
return;
|
|
847
|
-
|
|
865
|
+
const elapsed = Date.now() - connectStart;
|
|
866
|
+
console.error(`[nova/events] SSE connection error for queue "${queueName}" (failed after ${elapsed}ms):`, String(err).slice(0, 500));
|
|
867
|
+
_logErrorCause(err);
|
|
848
868
|
}
|
|
849
869
|
// Exponential backoff only for connection-level failures (fetch threw or
|
|
850
870
|
// server returned non-200). Stream-level drops use the fixed 1s above.
|
package/package.json
CHANGED