@thru/indexer 0.2.32 → 0.2.34
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/index.cjs +211 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.mjs +212 -64
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
|
2
2
|
import { PgTableWithColumns, PgColumn } from 'drizzle-orm/pg-core';
|
|
3
3
|
import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { Filter, Event, AccountState, ChainClientFactory } from '@thru/replay';
|
|
5
|
+
import { Filter, Event, AccountState, ChainClientFactory, ReplayLogger } from '@thru/replay';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Schema type definitions with improved type safety.
|
|
@@ -817,6 +817,8 @@ interface IndexerConfig {
|
|
|
817
817
|
pageSize?: number;
|
|
818
818
|
/** Log level (default: "info") */
|
|
819
819
|
logLevel?: "debug" | "info" | "warn" | "error";
|
|
820
|
+
/** Optional structured logger for runtime, processor, and replay lifecycle logs. */
|
|
821
|
+
logger?: ReplayLogger;
|
|
820
822
|
/** Human-readable endpoint label included in normalized stream errors */
|
|
821
823
|
endpointLabel?: string;
|
|
822
824
|
/** Initial supervisor restart backoff in milliseconds (default: 1000) */
|
|
@@ -886,11 +888,13 @@ interface IndexerResult {
|
|
|
886
888
|
*/
|
|
887
889
|
declare class Indexer {
|
|
888
890
|
private config;
|
|
891
|
+
private logger;
|
|
889
892
|
private abortController;
|
|
890
893
|
private running;
|
|
891
894
|
private shutdownRequested;
|
|
892
895
|
private startedAtMs;
|
|
893
896
|
private streamStatuses;
|
|
897
|
+
private streamHealthStates;
|
|
894
898
|
constructor(config: IndexerConfig);
|
|
895
899
|
/**
|
|
896
900
|
* Check if the checkpoint table exists in the database.
|
|
@@ -926,6 +930,9 @@ declare class Indexer {
|
|
|
926
930
|
private statusKey;
|
|
927
931
|
private statusFor;
|
|
928
932
|
private resultForStream;
|
|
933
|
+
private setStreamState;
|
|
934
|
+
private streamLogFields;
|
|
935
|
+
private emitHealthTransitions;
|
|
929
936
|
private createObserver;
|
|
930
937
|
private runEventStreamSupervisor;
|
|
931
938
|
private runAccountStreamSupervisor;
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { pgTable, timestamp, text, bigint, boolean, integer, index } from 'drizzle-orm/pg-core';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import {
|
|
3
|
+
import { createEventReplay, createAccountsByOwnerReplay, AccountView } from '@thru/replay';
|
|
4
4
|
import { eq, sql } from 'drizzle-orm';
|
|
5
5
|
import { encodeAddress } from '@thru/sdk/helpers';
|
|
6
6
|
|
|
@@ -299,6 +299,54 @@ function getSchemaExports(config) {
|
|
|
299
299
|
return exports;
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
// src/runtime/logger.ts
|
|
303
|
+
var LEVELS = ["debug", "info", "warn", "error"];
|
|
304
|
+
function shouldLog(level, minimum) {
|
|
305
|
+
return LEVELS.indexOf(level) >= LEVELS.indexOf(minimum);
|
|
306
|
+
}
|
|
307
|
+
function writeConsole(prefix, level, message, meta) {
|
|
308
|
+
const text3 = `[${prefix}] ${message}`;
|
|
309
|
+
if (meta && Object.keys(meta).length > 0) {
|
|
310
|
+
if (level === "error") {
|
|
311
|
+
console.error(text3, meta);
|
|
312
|
+
} else if (level === "warn") {
|
|
313
|
+
console.warn(text3, meta);
|
|
314
|
+
} else {
|
|
315
|
+
console.log(text3, meta);
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (level === "error") {
|
|
320
|
+
console.error(text3);
|
|
321
|
+
} else if (level === "warn") {
|
|
322
|
+
console.warn(text3);
|
|
323
|
+
} else {
|
|
324
|
+
console.log(text3);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function createScopedLogger(options) {
|
|
328
|
+
const minimum = options.level ?? "info";
|
|
329
|
+
const bindings = options.bindings ?? {};
|
|
330
|
+
const log = (level, message, meta) => {
|
|
331
|
+
if (!shouldLog(level, minimum)) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const hasMeta = meta !== void 0 && Object.keys(meta).length > 0;
|
|
335
|
+
const fields = { ...bindings, ...meta ?? {} };
|
|
336
|
+
if (options.logger) {
|
|
337
|
+
options.logger[level](message, fields);
|
|
338
|
+
} else {
|
|
339
|
+
writeConsole(options.prefix, level, message, hasMeta ? fields : void 0);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
return {
|
|
343
|
+
debug: (message, meta) => log("debug", message, meta),
|
|
344
|
+
info: (message, meta) => log("info", message, meta),
|
|
345
|
+
warn: (message, meta) => log("warn", message, meta),
|
|
346
|
+
error: (message, meta) => log("error", message, meta)
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
302
350
|
// src/streams/processor.ts
|
|
303
351
|
var StreamBatcher = class {
|
|
304
352
|
currentSlot = null;
|
|
@@ -358,14 +406,21 @@ async function runEventStreamProcessor(stream, options, abortSignal) {
|
|
|
358
406
|
safetyMargin = 64,
|
|
359
407
|
pageSize = 512,
|
|
360
408
|
logLevel = "info",
|
|
409
|
+
logger: baseLogger,
|
|
361
410
|
validateParse = false,
|
|
362
411
|
observer
|
|
363
412
|
} = options;
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
413
|
+
const logger = createScopedLogger({
|
|
414
|
+
logger: baseLogger,
|
|
415
|
+
level: logLevel,
|
|
416
|
+
prefix: stream.name,
|
|
417
|
+
bindings: {
|
|
418
|
+
component: "indexer-stream",
|
|
419
|
+
stream: stream.name,
|
|
420
|
+
kind: "event"
|
|
367
421
|
}
|
|
368
|
-
};
|
|
422
|
+
});
|
|
423
|
+
const log = (level, msg, meta) => logger[level](msg, meta);
|
|
369
424
|
log("info", `Starting stream processor: ${stream.description}`);
|
|
370
425
|
const checkpoint = await getCheckpoint(db, stream.name);
|
|
371
426
|
const startSlot = checkpoint ? checkpoint.slot : defaultStartSlot;
|
|
@@ -377,13 +432,6 @@ async function runEventStreamProcessor(stream, options, abortSignal) {
|
|
|
377
432
|
"info",
|
|
378
433
|
`Starting from slot ${startSlot}${checkpoint ? " (resuming)" : " (fresh start)"}`
|
|
379
434
|
);
|
|
380
|
-
const logger = logLevel === "debug" ? createConsoleLogger(stream.name) : {
|
|
381
|
-
debug: () => {
|
|
382
|
-
},
|
|
383
|
-
info: (msg) => log("info", msg),
|
|
384
|
-
warn: (msg) => log("warn", msg),
|
|
385
|
-
error: (msg) => log("error", msg)
|
|
386
|
-
};
|
|
387
435
|
const replay = createEventReplay({
|
|
388
436
|
clientFactory,
|
|
389
437
|
startSlot,
|
|
@@ -392,7 +440,8 @@ async function runEventStreamProcessor(stream, options, abortSignal) {
|
|
|
392
440
|
pageSize,
|
|
393
441
|
filter: stream.getFilter(),
|
|
394
442
|
logger,
|
|
395
|
-
resubscribeOnEnd: true
|
|
443
|
+
resubscribeOnEnd: true,
|
|
444
|
+
signal: abortSignal
|
|
396
445
|
});
|
|
397
446
|
const batcher = new StreamBatcher();
|
|
398
447
|
const stats = {
|
|
@@ -610,20 +659,21 @@ function defineAccountStream(definition) {
|
|
|
610
659
|
api: definition.api
|
|
611
660
|
};
|
|
612
661
|
}
|
|
613
|
-
function shouldLog(level, minLevel) {
|
|
614
|
-
const levels = ["debug", "info", "warn", "error"];
|
|
615
|
-
return levels.indexOf(level) >= levels.indexOf(minLevel);
|
|
616
|
-
}
|
|
617
662
|
async function runAccountStreamProcessor(stream, options, abortSignal) {
|
|
618
|
-
const { clientFactory, db, logLevel = "info", validateParse = false, observer } = options;
|
|
663
|
+
const { clientFactory, db, logLevel = "info", logger: baseLogger, validateParse = false, observer } = options;
|
|
619
664
|
const checkpointName = `account:${stream.name}`;
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
665
|
+
const logger = createScopedLogger({
|
|
666
|
+
logger: baseLogger,
|
|
667
|
+
level: logLevel,
|
|
668
|
+
prefix: `account-stream:${stream.name}`,
|
|
669
|
+
bindings: {
|
|
670
|
+
component: "indexer-stream",
|
|
671
|
+
stream: stream.name,
|
|
672
|
+
kind: "account",
|
|
673
|
+
checkpoint_name: checkpointName
|
|
625
674
|
}
|
|
626
|
-
};
|
|
675
|
+
});
|
|
676
|
+
const log = (level, msg, meta) => logger[level](msg, meta);
|
|
627
677
|
const stats = {
|
|
628
678
|
accountsProcessed: 0,
|
|
629
679
|
accountsUpdated: 0,
|
|
@@ -642,18 +692,6 @@ async function runAccountStreamProcessor(stream, options, abortSignal) {
|
|
|
642
692
|
if (stream.expectedSize) {
|
|
643
693
|
log("info", `Expected data size: ${stream.expectedSize} bytes`);
|
|
644
694
|
}
|
|
645
|
-
const replayLogger = logLevel === "debug" ? {
|
|
646
|
-
debug: (msg) => log("debug", msg),
|
|
647
|
-
info: (msg) => log("info", msg),
|
|
648
|
-
warn: (msg) => log("warn", msg),
|
|
649
|
-
error: (msg) => log("error", msg)
|
|
650
|
-
} : {
|
|
651
|
-
debug: () => {
|
|
652
|
-
},
|
|
653
|
-
info: (msg) => log("info", msg),
|
|
654
|
-
warn: (msg) => log("warn", msg),
|
|
655
|
-
error: (msg) => log("error", msg)
|
|
656
|
-
};
|
|
657
695
|
let lastProcessedSlot = minUpdatedSlot ?? 0n;
|
|
658
696
|
try {
|
|
659
697
|
const replay = createAccountsByOwnerReplay({
|
|
@@ -662,7 +700,8 @@ async function runAccountStreamProcessor(stream, options, abortSignal) {
|
|
|
662
700
|
view: AccountView.FULL,
|
|
663
701
|
dataSizes: stream.dataSizes ?? (stream.expectedSize ? [stream.expectedSize] : void 0),
|
|
664
702
|
minUpdatedSlot,
|
|
665
|
-
logger
|
|
703
|
+
logger,
|
|
704
|
+
signal: abortSignal,
|
|
666
705
|
onBackfillComplete: (highestSlot) => {
|
|
667
706
|
log(
|
|
668
707
|
"info",
|
|
@@ -868,11 +907,13 @@ function isRetryablePhase(phase) {
|
|
|
868
907
|
// src/runtime/indexer.ts
|
|
869
908
|
var Indexer = class {
|
|
870
909
|
config;
|
|
910
|
+
logger;
|
|
871
911
|
abortController = null;
|
|
872
912
|
running = false;
|
|
873
913
|
shutdownRequested = false;
|
|
874
914
|
startedAtMs = null;
|
|
875
915
|
streamStatuses = /* @__PURE__ */ new Map();
|
|
916
|
+
streamHealthStates = /* @__PURE__ */ new Map();
|
|
876
917
|
constructor(config) {
|
|
877
918
|
this.config = {
|
|
878
919
|
defaultStartSlot: 0n,
|
|
@@ -884,6 +925,12 @@ var Indexer = class {
|
|
|
884
925
|
streamStaleMs: 3e5,
|
|
885
926
|
...config
|
|
886
927
|
};
|
|
928
|
+
this.logger = createScopedLogger({
|
|
929
|
+
logger: this.config.logger,
|
|
930
|
+
level: this.config.logLevel,
|
|
931
|
+
prefix: "indexer",
|
|
932
|
+
bindings: { component: "indexer-runtime" }
|
|
933
|
+
});
|
|
887
934
|
this.initializeStreamStatuses();
|
|
888
935
|
}
|
|
889
936
|
/**
|
|
@@ -898,14 +945,14 @@ var Indexer = class {
|
|
|
898
945
|
} catch (err) {
|
|
899
946
|
const message = err instanceof Error ? err.cause instanceof Error ? err.cause.message : err.message : String(err);
|
|
900
947
|
if (message.includes("does not exist") || message.includes("relation")) {
|
|
901
|
-
|
|
902
|
-
`
|
|
903
|
-
|
|
948
|
+
this.logger.warn(
|
|
949
|
+
`Checkpoint table "indexer_checkpoints" not found.
|
|
950
|
+
Make sure to export checkpointTable from your Drizzle schema:
|
|
904
951
|
|
|
905
952
|
// db/schema.ts
|
|
906
953
|
export { checkpointTable } from "@thru/indexer";
|
|
907
954
|
|
|
908
|
-
|
|
955
|
+
Then run: pnpm drizzle-kit push (or generate + migrate)
|
|
909
956
|
`
|
|
910
957
|
);
|
|
911
958
|
}
|
|
@@ -942,15 +989,14 @@ var Indexer = class {
|
|
|
942
989
|
validateParse,
|
|
943
990
|
endpointLabel,
|
|
944
991
|
supervisorInitialBackoffMs = 1e3,
|
|
945
|
-
supervisorMaxBackoffMs = 3e4
|
|
992
|
+
supervisorMaxBackoffMs = 3e4,
|
|
993
|
+
logger
|
|
946
994
|
} = this.config;
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
`[indexer] Running ${accountStreams.length} account stream(s): ${accountStreams.map((s) => s.name).join(", ") || "none"}`
|
|
953
|
-
);
|
|
995
|
+
this.logger.info("Starting indexer", {
|
|
996
|
+
event: "indexer.started",
|
|
997
|
+
event_streams: eventStreams.map((stream) => stream.name),
|
|
998
|
+
account_streams: accountStreams.map((stream) => stream.name)
|
|
999
|
+
});
|
|
954
1000
|
try {
|
|
955
1001
|
const supervisorOptions = {
|
|
956
1002
|
endpointLabel,
|
|
@@ -965,6 +1011,7 @@ var Indexer = class {
|
|
|
965
1011
|
safetyMargin,
|
|
966
1012
|
pageSize,
|
|
967
1013
|
logLevel,
|
|
1014
|
+
logger,
|
|
968
1015
|
validateParse
|
|
969
1016
|
}, supervisorOptions)
|
|
970
1017
|
);
|
|
@@ -973,6 +1020,7 @@ var Indexer = class {
|
|
|
973
1020
|
clientFactory,
|
|
974
1021
|
db,
|
|
975
1022
|
logLevel,
|
|
1023
|
+
logger,
|
|
976
1024
|
validateParse
|
|
977
1025
|
}, supervisorOptions)
|
|
978
1026
|
);
|
|
@@ -981,7 +1029,9 @@ var Indexer = class {
|
|
|
981
1029
|
eventStreams: eventStreams.map((stream) => this.resultForStream(stream.name)),
|
|
982
1030
|
accountStreams: accountStreams.map((stream) => this.resultForStream(stream.name))
|
|
983
1031
|
};
|
|
984
|
-
|
|
1032
|
+
this.logger.info("All indexer streams stopped", {
|
|
1033
|
+
event: "indexer.stopped"
|
|
1034
|
+
});
|
|
985
1035
|
return result;
|
|
986
1036
|
} finally {
|
|
987
1037
|
this.running = false;
|
|
@@ -997,14 +1047,20 @@ var Indexer = class {
|
|
|
997
1047
|
*/
|
|
998
1048
|
stop() {
|
|
999
1049
|
if (!this.running || !this.abortController) {
|
|
1000
|
-
|
|
1050
|
+
this.logger.info("Indexer is not running", {
|
|
1051
|
+
event: "indexer.stop.noop"
|
|
1052
|
+
});
|
|
1001
1053
|
return;
|
|
1002
1054
|
}
|
|
1003
1055
|
if (this.shutdownRequested) {
|
|
1004
|
-
|
|
1056
|
+
this.logger.warn("Force shutting down indexer", {
|
|
1057
|
+
event: "indexer.force_shutdown"
|
|
1058
|
+
});
|
|
1005
1059
|
process.exit(1);
|
|
1006
1060
|
}
|
|
1007
|
-
|
|
1061
|
+
this.logger.info("Indexer shutdown requested", {
|
|
1062
|
+
event: "indexer.shutdown_requested"
|
|
1063
|
+
});
|
|
1008
1064
|
this.shutdownRequested = true;
|
|
1009
1065
|
this.abortController.abort();
|
|
1010
1066
|
}
|
|
@@ -1025,6 +1081,7 @@ var Indexer = class {
|
|
|
1025
1081
|
return stream;
|
|
1026
1082
|
});
|
|
1027
1083
|
const healthy = this.running && !this.shutdownRequested && streams.length > 0 && streams.every((stream) => stream.state === "running" && !stream.stale);
|
|
1084
|
+
this.emitHealthTransitions(streams, now);
|
|
1028
1085
|
return {
|
|
1029
1086
|
running: this.running,
|
|
1030
1087
|
shutdownRequested: this.shutdownRequested,
|
|
@@ -1036,6 +1093,7 @@ var Indexer = class {
|
|
|
1036
1093
|
}
|
|
1037
1094
|
initializeStreamStatuses() {
|
|
1038
1095
|
this.streamStatuses = /* @__PURE__ */ new Map();
|
|
1096
|
+
this.streamHealthStates = /* @__PURE__ */ new Map();
|
|
1039
1097
|
for (const stream of this.config.eventStreams ?? []) {
|
|
1040
1098
|
this.streamStatuses.set(this.statusKey("event", stream.name), this.createInitialStreamStatus("event", stream.name));
|
|
1041
1099
|
}
|
|
@@ -1077,6 +1135,84 @@ var Indexer = class {
|
|
|
1077
1135
|
status: "fulfilled"
|
|
1078
1136
|
};
|
|
1079
1137
|
}
|
|
1138
|
+
setStreamState(status, nextState, meta = {}) {
|
|
1139
|
+
const previousState = status.state;
|
|
1140
|
+
status.state = nextState;
|
|
1141
|
+
if (previousState === nextState) {
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
this.logger.info("Indexer stream state changed", {
|
|
1145
|
+
event: "indexer.stream.state_changed",
|
|
1146
|
+
stream: status.name,
|
|
1147
|
+
kind: status.kind,
|
|
1148
|
+
previous_state: previousState,
|
|
1149
|
+
next_state: nextState,
|
|
1150
|
+
restart_count: status.restartCount,
|
|
1151
|
+
...meta
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
streamLogFields(stream) {
|
|
1155
|
+
return {
|
|
1156
|
+
stream: stream.name,
|
|
1157
|
+
kind: stream.kind,
|
|
1158
|
+
state: stream.state,
|
|
1159
|
+
stale: stream.stale,
|
|
1160
|
+
checkpoint_slot: stream.checkpointSlot,
|
|
1161
|
+
last_processed_slot: stream.lastProcessedSlot,
|
|
1162
|
+
last_event_at: stream.lastEventAt,
|
|
1163
|
+
restart_count: stream.restartCount,
|
|
1164
|
+
last_error_at: stream.lastErrorAt,
|
|
1165
|
+
last_error: stream.lastError
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
emitHealthTransitions(streams, nowMs) {
|
|
1169
|
+
if (!this.running || this.shutdownRequested) {
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
for (const stream of streams) {
|
|
1173
|
+
const key = this.statusKey(stream.kind, stream.name);
|
|
1174
|
+
const unhealthy = stream.state !== "running" || stream.stale;
|
|
1175
|
+
const previous = this.streamHealthStates.get(key);
|
|
1176
|
+
const initialStarting = !previous && stream.state === "starting" && stream.restartCount === 0 && !stream.lastError;
|
|
1177
|
+
if (initialStarting) {
|
|
1178
|
+
this.streamHealthStates.set(key, {
|
|
1179
|
+
unhealthy: false,
|
|
1180
|
+
unhealthySinceMs: null
|
|
1181
|
+
});
|
|
1182
|
+
continue;
|
|
1183
|
+
}
|
|
1184
|
+
if (unhealthy && previous?.unhealthy !== true) {
|
|
1185
|
+
this.streamHealthStates.set(key, {
|
|
1186
|
+
unhealthy: true,
|
|
1187
|
+
unhealthySinceMs: nowMs
|
|
1188
|
+
});
|
|
1189
|
+
this.logger.warn("Indexer stream unhealthy", {
|
|
1190
|
+
event: "indexer.stream.unhealthy",
|
|
1191
|
+
reason: stream.stale ? "stale" : "state",
|
|
1192
|
+
...this.streamLogFields(stream)
|
|
1193
|
+
});
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
if (!unhealthy && previous?.unhealthy === true) {
|
|
1197
|
+
this.streamHealthStates.set(key, {
|
|
1198
|
+
unhealthy: false,
|
|
1199
|
+
unhealthySinceMs: null
|
|
1200
|
+
});
|
|
1201
|
+
this.logger.info("Indexer stream recovered", {
|
|
1202
|
+
event: "indexer.stream.recovered",
|
|
1203
|
+
unhealthy_duration_ms: previous.unhealthySinceMs === null ? null : nowMs - previous.unhealthySinceMs,
|
|
1204
|
+
...this.streamLogFields(stream)
|
|
1205
|
+
});
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1208
|
+
if (!previous) {
|
|
1209
|
+
this.streamHealthStates.set(key, {
|
|
1210
|
+
unhealthy,
|
|
1211
|
+
unhealthySinceMs: unhealthy ? nowMs : null
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1080
1216
|
createObserver(kind, name, endpointLabel) {
|
|
1081
1217
|
const status = this.statusFor(kind, name);
|
|
1082
1218
|
let startSlot = null;
|
|
@@ -1085,7 +1221,10 @@ var Indexer = class {
|
|
|
1085
1221
|
onStart: (info) => {
|
|
1086
1222
|
startSlot = info.startSlot ?? null;
|
|
1087
1223
|
checkpointSlot = info.checkpointSlot ?? null;
|
|
1088
|
-
status
|
|
1224
|
+
this.setStreamState(status, "running", {
|
|
1225
|
+
start_slot: startSlot?.toString(),
|
|
1226
|
+
checkpoint_slot: checkpointSlot?.toString() ?? null
|
|
1227
|
+
});
|
|
1089
1228
|
status.checkpointSlot = checkpointSlot === null ? null : checkpointSlot.toString();
|
|
1090
1229
|
status.lastStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1091
1230
|
},
|
|
@@ -1157,23 +1296,26 @@ var Indexer = class {
|
|
|
1157
1296
|
const status = this.statusFor(kind, name);
|
|
1158
1297
|
while (!this.abortController?.signal.aborted) {
|
|
1159
1298
|
const observer = this.createObserver(kind, name, options.endpointLabel);
|
|
1160
|
-
status
|
|
1299
|
+
this.setStreamState(status, attempt === 0 ? "starting" : "retrying", {
|
|
1300
|
+
attempt
|
|
1301
|
+
});
|
|
1161
1302
|
status.lastStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1162
1303
|
try {
|
|
1163
1304
|
const summary = await runOnce(observer);
|
|
1164
1305
|
if (this.abortController?.signal.aborted) {
|
|
1165
|
-
status
|
|
1166
|
-
console.log(`[indexer] ${kind} stream "${name}" stopped: ${summary}`);
|
|
1306
|
+
this.setStreamState(status, "stopped", { summary });
|
|
1167
1307
|
return;
|
|
1168
1308
|
}
|
|
1169
1309
|
throw new Error(`${kind} stream "${name}" completed unexpectedly: ${summary}`);
|
|
1170
1310
|
} catch (error) {
|
|
1171
1311
|
if (this.abortController?.signal.aborted) {
|
|
1172
|
-
status
|
|
1312
|
+
this.setStreamState(status, "stopped");
|
|
1173
1313
|
return;
|
|
1174
1314
|
}
|
|
1175
1315
|
status.restartCount++;
|
|
1176
|
-
status
|
|
1316
|
+
this.setStreamState(status, "retrying", {
|
|
1317
|
+
restart_count: status.restartCount
|
|
1318
|
+
});
|
|
1177
1319
|
status.lastErrorAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1178
1320
|
if (!status.lastError || status.lastError.phase === "supervisor") {
|
|
1179
1321
|
status.lastError = normalizeIndexerError({
|
|
@@ -1185,15 +1327,21 @@ var Indexer = class {
|
|
|
1185
1327
|
});
|
|
1186
1328
|
}
|
|
1187
1329
|
const backoffMs = this.supervisorBackoffMs(attempt, options.initialBackoffMs, options.maxBackoffMs);
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1330
|
+
this.logger.warn("Indexer stream supervisor restarting", {
|
|
1331
|
+
event: "indexer.stream.supervisor_restart",
|
|
1332
|
+
stream: name,
|
|
1333
|
+
kind,
|
|
1334
|
+
backoff_ms: backoffMs,
|
|
1335
|
+
attempt: attempt + 1,
|
|
1336
|
+
restart_count: status.restartCount,
|
|
1337
|
+
error,
|
|
1338
|
+
last_error: status.lastError
|
|
1339
|
+
});
|
|
1192
1340
|
attempt++;
|
|
1193
1341
|
await this.delay(backoffMs, this.abortController.signal);
|
|
1194
1342
|
}
|
|
1195
1343
|
}
|
|
1196
|
-
status
|
|
1344
|
+
this.setStreamState(status, "stopped");
|
|
1197
1345
|
}
|
|
1198
1346
|
supervisorBackoffMs(attempt, initialMs, maxMs) {
|
|
1199
1347
|
const base = Math.min(maxMs, initialMs * Math.pow(2, attempt));
|