target-supabase-sdk 0.2.10 → 0.2.12
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/browser.d.ts +4 -6
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +3 -3
- package/dist/chunks/{enable-log-persist-BB-unD4p.js → enable-log-persist-Dl55_mv2.js} +160 -30
- package/dist/chunks/enable-log-persist-Dl55_mv2.js.map +1 -0
- package/dist/chunks/{list.api-6Q4_-7oN.js → list.api-BzbTJBsM.js} +81 -13
- package/dist/chunks/list.api-BzbTJBsM.js.map +1 -0
- package/dist/chunks/{task-queue-D5uWjqVN.js → task-queue-H4mpSW8R.js} +55 -16
- package/dist/chunks/task-queue-H4mpSW8R.js.map +1 -0
- package/dist/core.api.d.ts +6 -15
- package/dist/core.api.d.ts.map +1 -1
- package/dist/core.interface.d.ts +5 -0
- package/dist/core.interface.d.ts.map +1 -1
- package/dist/core.schema.d.ts +77 -0
- package/dist/core.schema.d.ts.map +1 -0
- package/dist/link/index.d.ts +10 -0
- package/dist/link/index.d.ts.map +1 -0
- package/dist/link/link.build.d.ts +45 -0
- package/dist/link/link.build.d.ts.map +1 -0
- package/dist/list/index.d.ts +10 -0
- package/dist/list/index.d.ts.map +1 -0
- package/dist/list/list.build.d.ts +21 -0
- package/dist/list/list.build.d.ts.map +1 -0
- package/dist/node/preload/run-service-preload.d.ts +1 -0
- package/dist/node/preload/run-service-preload.d.ts.map +1 -1
- package/dist/node/preload/service-preload.interface.d.ts +5 -0
- package/dist/node/preload/service-preload.interface.d.ts.map +1 -1
- package/dist/node.js +6 -6
- package/dist/parcel/parcel.api.d.ts +3 -3
- package/dist/parcel/parcel.api.d.ts.map +1 -1
- package/dist/preload.js +14 -3
- package/dist/preload.js.map +1 -1
- package/dist/resource/resource.interface.d.ts +20 -0
- package/dist/resource/resource.interface.d.ts.map +1 -0
- package/dist/service/config.api.d.ts.map +1 -1
- package/dist/service/service.api.d.ts +6 -6
- package/dist/service/service.api.d.ts.map +1 -1
- package/dist/shared/log/index.d.ts +2 -0
- package/dist/shared/log/index.d.ts.map +1 -1
- package/dist/shared/log/log-min-level.d.ts +15 -0
- package/dist/shared/log/log-min-level.d.ts.map +1 -0
- package/dist/shared/log/log-persist-errors.d.ts +4 -0
- package/dist/shared/log/log-persist-errors.d.ts.map +1 -0
- package/dist/shared/log/log-persist-logger.d.ts +1 -0
- package/dist/shared/log/log-persist-logger.d.ts.map +1 -1
- package/dist/shared/log/log-persist.config.d.ts.map +1 -1
- package/dist/shared/log/log-persist.d.ts +8 -1
- package/dist/shared/log/log-persist.d.ts.map +1 -1
- package/dist/shared/log/log-persist.interface.d.ts +8 -0
- package/dist/shared/log/log-persist.interface.d.ts.map +1 -1
- package/dist/task/task-post-validated.api.d.ts +2 -2
- package/dist/task/task-post.api.d.ts +6 -6
- package/dist/trigger/trigger.api.d.ts +6 -6
- package/package.json +1 -1
- package/src/browser.ts +6 -5
- package/src/core.api.ts +19 -28
- package/src/core.interface.ts +6 -0
- package/src/core.schema.ts +51 -0
- package/src/link/index.ts +10 -0
- package/src/link/link.build.ts +48 -0
- package/src/list/index.ts +13 -0
- package/src/list/list.build.ts +51 -0
- package/src/node/preload/run-service-preload.ts +14 -0
- package/src/node/preload/service-preload.interface.ts +6 -0
- package/src/resource/resource.interface.ts +37 -0
- package/src/service/config.api.ts +1 -0
- package/src/shared/log/index.ts +7 -0
- package/src/shared/log/log-min-level.ts +46 -0
- package/src/shared/log/log-persist-errors.ts +16 -0
- package/src/shared/log/log-persist-logger.ts +28 -0
- package/src/shared/log/log-persist.config.ts +20 -0
- package/src/shared/log/log-persist.interface.ts +8 -0
- package/src/shared/log/log-persist.ts +161 -43
- package/dist/chunks/enable-log-persist-BB-unD4p.js.map +0 -1
- package/dist/chunks/list.api-6Q4_-7oN.js.map +0 -1
- package/dist/chunks/task-queue-D5uWjqVN.js.map +0 -1
- package/dist/idea/idea.interface.d.ts +0 -25
- package/dist/idea/idea.interface.d.ts.map +0 -1
- package/src/idea/idea.interface.ts +0 -28
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { isDevEnvironment } from "../../core.utils";
|
|
2
|
+
import { LogLevel } from "./log-manager";
|
|
3
|
+
|
|
4
|
+
/** Env key for global {@link LogManager} min level (console + log-persist when enabled). */
|
|
5
|
+
export const LOG_MIN_LEVEL_ENV_KEY = "LOG_MIN_LEVEL";
|
|
6
|
+
|
|
7
|
+
const LOG_LEVEL_BY_NAME: Record<string, LogLevel> = {
|
|
8
|
+
DEBUG: LogLevel.DEBUG,
|
|
9
|
+
INFO: LogLevel.INFO,
|
|
10
|
+
SUCCESS: LogLevel.SUCCESS,
|
|
11
|
+
WARN: LogLevel.WARN,
|
|
12
|
+
ERROR: LogLevel.ERROR,
|
|
13
|
+
CRITICAL: LogLevel.CRITICAL,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface ResolveLogMinLevelOptions {
|
|
17
|
+
env?: NodeJS.ProcessEnv;
|
|
18
|
+
/** When {@link LOG_MIN_LEVEL_ENV_KEY} unset; defaults to LogManager convention (DEBUG in dev, INFO in prod). */
|
|
19
|
+
defaultLevel?: LogLevel;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function sdkDefaultLogMinLevel(): LogLevel {
|
|
23
|
+
return isDevEnvironment() ? LogLevel.DEBUG : LogLevel.INFO;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parse `LOG_MIN_LEVEL` after `.env` load. Invalid values throw (same style as `envInt`).
|
|
28
|
+
*/
|
|
29
|
+
export function resolveLogMinLevel(options?: ResolveLogMinLevelOptions): LogLevel {
|
|
30
|
+
const env = options?.env ?? process.env;
|
|
31
|
+
const raw = env[LOG_MIN_LEVEL_ENV_KEY]?.trim().toUpperCase();
|
|
32
|
+
if (raw == null || raw === "") {
|
|
33
|
+
return options?.defaultLevel ?? sdkDefaultLogMinLevel();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const level = LOG_LEVEL_BY_NAME[raw];
|
|
37
|
+
if (level == null) {
|
|
38
|
+
throw new Error(`Invalid ${LOG_MIN_LEVEL_ENV_KEY}: ${raw}`);
|
|
39
|
+
}
|
|
40
|
+
return level;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Alias for {@link resolveLogMinLevel}. */
|
|
44
|
+
export function logMinLevelFromEnv(options?: ResolveLogMinLevelOptions): LogLevel {
|
|
45
|
+
return resolveLogMinLevel(options);
|
|
46
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const PERMANENT_LOG_PERSIST_ERROR_PATTERNS = [
|
|
2
|
+
/not initialized/i,
|
|
3
|
+
/call initialize\(\) first/i,
|
|
4
|
+
/missing required supabase environment/i,
|
|
5
|
+
/preload env incomplete/i,
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
export function formatLogPersistError(error: unknown): string {
|
|
9
|
+
return error instanceof Error ? error.message : String(error);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Errors that will not succeed on retry without fixing process configuration. */
|
|
13
|
+
export function isPermanentLogPersistError(error: unknown): boolean {
|
|
14
|
+
const message = formatLogPersistError(error);
|
|
15
|
+
return PERMANENT_LOG_PERSIST_ERROR_PATTERNS.some((pattern) => pattern.test(message));
|
|
16
|
+
}
|
|
@@ -8,13 +8,41 @@ export const LOG_PERSIST_INTERNAL_TOPIC = "log-persist-internal";
|
|
|
8
8
|
|
|
9
9
|
const logger = createLogger({ module: "log-persist" });
|
|
10
10
|
|
|
11
|
+
let errorLogRateLimitMs = 5_000;
|
|
12
|
+
const lastLoggedAt = new Map<string, number>();
|
|
13
|
+
|
|
14
|
+
export function configurePersistLoggerRateLimit(ms: number): void {
|
|
15
|
+
errorLogRateLimitMs = Math.max(0, ms);
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
export function isLogPersistInternalTopic(topic: string | undefined): boolean {
|
|
12
19
|
return topic === LOG_PERSIST_INTERNAL_TOPIC;
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
type PersistLogData = Record<string, unknown>;
|
|
16
23
|
|
|
24
|
+
function shouldEmit(level: "debug" | "info" | "warn" | "error", message: string): boolean {
|
|
25
|
+
if (level !== "error" && level !== "warn") {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (errorLogRateLimitMs <= 0) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const key = `${level}:${message}`;
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
const last = lastLoggedAt.get(key);
|
|
35
|
+
if (last != null && now - last < errorLogRateLimitMs) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
lastLoggedAt.set(key, now);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
17
42
|
function write(level: "debug" | "info" | "warn" | "error", message: string, data?: PersistLogData): void {
|
|
43
|
+
if (!shouldEmit(level, message)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
18
46
|
logger[level](message, {
|
|
19
47
|
topic: LOG_PERSIST_INTERNAL_TOPIC,
|
|
20
48
|
...(data != null ? { data } : {}),
|
|
@@ -19,6 +19,9 @@ export const DEFAULT_LOG_PERSIST_CONFIG: LogPersistConfig = {
|
|
|
19
19
|
},
|
|
20
20
|
postTimeoutMs: 30_000,
|
|
21
21
|
shutdownDrainTimeoutMs: 15_000,
|
|
22
|
+
laneRetryIntervalMs: 5_000,
|
|
23
|
+
errorLogRateLimitMs: 5_000,
|
|
24
|
+
circuitBreakerPermanentFailureThreshold: 3,
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
export function resolveLogPersistConfig(partial?: Partial<LogPersistConfig>): LogPersistConfig {
|
|
@@ -29,6 +32,10 @@ export function resolveLogPersistConfig(partial?: Partial<LogPersistConfig>): Lo
|
|
|
29
32
|
slow: { ...defaults.slow, ...partial?.slow },
|
|
30
33
|
postTimeoutMs: partial?.postTimeoutMs ?? defaults.postTimeoutMs,
|
|
31
34
|
shutdownDrainTimeoutMs: partial?.shutdownDrainTimeoutMs ?? defaults.shutdownDrainTimeoutMs,
|
|
35
|
+
laneRetryIntervalMs: partial?.laneRetryIntervalMs ?? defaults.laneRetryIntervalMs,
|
|
36
|
+
errorLogRateLimitMs: partial?.errorLogRateLimitMs ?? defaults.errorLogRateLimitMs,
|
|
37
|
+
circuitBreakerPermanentFailureThreshold:
|
|
38
|
+
partial?.circuitBreakerPermanentFailureThreshold ?? defaults.circuitBreakerPermanentFailureThreshold,
|
|
32
39
|
};
|
|
33
40
|
}
|
|
34
41
|
|
|
@@ -91,5 +98,18 @@ export function logPersistConfigFromEnv(env: NodeJS.ProcessEnv = process.env): L
|
|
|
91
98
|
DEFAULT_LOG_PERSIST_CONFIG.shutdownDrainTimeoutMs,
|
|
92
99
|
{ env },
|
|
93
100
|
),
|
|
101
|
+
laneRetryIntervalMs: envMs("LOG_PERSIST_LANE_RETRY_MS", DEFAULT_LOG_PERSIST_CONFIG.laneRetryIntervalMs, {
|
|
102
|
+
env,
|
|
103
|
+
}),
|
|
104
|
+
errorLogRateLimitMs: envMs(
|
|
105
|
+
"LOG_PERSIST_ERROR_LOG_RATE_LIMIT_MS",
|
|
106
|
+
DEFAULT_LOG_PERSIST_CONFIG.errorLogRateLimitMs,
|
|
107
|
+
{ env },
|
|
108
|
+
),
|
|
109
|
+
circuitBreakerPermanentFailureThreshold: envInt(
|
|
110
|
+
"LOG_PERSIST_CIRCUIT_BREAKER_THRESHOLD",
|
|
111
|
+
DEFAULT_LOG_PERSIST_CONFIG.circuitBreakerPermanentFailureThreshold,
|
|
112
|
+
{ env, min: 1 },
|
|
113
|
+
),
|
|
94
114
|
});
|
|
95
115
|
}
|
|
@@ -41,6 +41,12 @@ export interface LogPersistConfig {
|
|
|
41
41
|
postTimeoutMs: number;
|
|
42
42
|
/** Graceful shutdown drain ceiling */
|
|
43
43
|
shutdownDrainTimeoutMs: number;
|
|
44
|
+
/** Delay before medium/slow lane flush retries after failure (fast lane uses the same interval). */
|
|
45
|
+
laneRetryIntervalMs: number;
|
|
46
|
+
/** Suppress duplicate persistLogger warn/error lines with the same message within this window. */
|
|
47
|
+
errorLogRateLimitMs: number;
|
|
48
|
+
/** Disable log-persist after this many consecutive permanent flush errors. */
|
|
49
|
+
circuitBreakerPermanentFailureThreshold: number;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
export interface EnableLogPersistOptions {
|
|
@@ -89,6 +95,8 @@ export interface LogPersistQueueBytesStats {
|
|
|
89
95
|
export interface LogPersistStats {
|
|
90
96
|
enabled: boolean;
|
|
91
97
|
draining: boolean;
|
|
98
|
+
circuitBreakerTripped: boolean;
|
|
99
|
+
permanentFailureCount: number;
|
|
92
100
|
service: string | null;
|
|
93
101
|
process: string | null;
|
|
94
102
|
queues: LogPersistQueueStats;
|
|
@@ -7,12 +7,17 @@ import type {
|
|
|
7
7
|
LogPersistStats,
|
|
8
8
|
} from "./log-persist.interface";
|
|
9
9
|
import { computeLogBatchIdempotencyKey } from "./log-persist-batch-id";
|
|
10
|
+
import { formatLogPersistError, isPermanentLogPersistError } from "./log-persist-errors";
|
|
10
11
|
import { buildLogListDraft, estimateEntriesBytes, postLogBatch, takeEntriesWithinBytes } from "./log-persist-flush";
|
|
11
12
|
import { registerLogPersistOffer } from "./log-persist-hook";
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
configurePersistLoggerRateLimit,
|
|
15
|
+
isLogPersistInternalTopic,
|
|
16
|
+
patchPersistLoggerScope,
|
|
17
|
+
persistLogger,
|
|
18
|
+
} from "./log-persist-logger";
|
|
13
19
|
import { heartbeatLogPersistProcess } from "./log-persist-registry";
|
|
14
20
|
|
|
15
|
-
const FAST_RETRY_INTERVAL_MS = 5_000;
|
|
16
21
|
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
17
22
|
|
|
18
23
|
function routeLane(level: LogLevel): LogPersistLane {
|
|
@@ -43,8 +48,12 @@ class LogPersist {
|
|
|
43
48
|
private mediumDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
44
49
|
private slowQueueStartedAt: number | null = null;
|
|
45
50
|
private fastRetryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
51
|
+
private mediumRetryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
52
|
+
private slowRetryTimer: ReturnType<typeof setTimeout> | null = null;
|
|
46
53
|
private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
47
54
|
private lastHeartbeatAt = 0;
|
|
55
|
+
private permanentFailureCount = 0;
|
|
56
|
+
private circuitBreakerTripped = false;
|
|
48
57
|
|
|
49
58
|
private laneFlushing: Record<LogPersistLane, boolean> = {
|
|
50
59
|
fast: false,
|
|
@@ -87,9 +96,12 @@ class LogPersist {
|
|
|
87
96
|
this.processName = options.process;
|
|
88
97
|
this.registryFilePath = options.registryFilePath ?? null;
|
|
89
98
|
this.config = resolveLogPersistConfig(options.config);
|
|
99
|
+
configurePersistLoggerRateLimit(this.config.errorLogRateLimitMs);
|
|
90
100
|
this.draining = false;
|
|
91
101
|
this.shutdownPromise = null;
|
|
92
102
|
this.consecutiveFailures = { fast: 0, medium: 0, slow: 0 };
|
|
103
|
+
this.permanentFailureCount = 0;
|
|
104
|
+
this.circuitBreakerTripped = false;
|
|
93
105
|
this.lastError = null;
|
|
94
106
|
this.lastErrorAt = null;
|
|
95
107
|
this.enabled = true;
|
|
@@ -132,6 +144,7 @@ class LogPersist {
|
|
|
132
144
|
|
|
133
145
|
persistLogger.info("log persist core disabling", this.queueSnapshot());
|
|
134
146
|
this.cancelMediumDebounce();
|
|
147
|
+
this.cancelLaneRetryTimers();
|
|
135
148
|
this.stopHeartbeat();
|
|
136
149
|
await this.shutdown();
|
|
137
150
|
persistLogger.info("log persist core disabled");
|
|
@@ -176,6 +189,8 @@ class LogPersist {
|
|
|
176
189
|
return {
|
|
177
190
|
enabled: this.enabled,
|
|
178
191
|
draining: this.draining,
|
|
192
|
+
circuitBreakerTripped: this.circuitBreakerTripped,
|
|
193
|
+
permanentFailureCount: this.permanentFailureCount,
|
|
179
194
|
service: this.service,
|
|
180
195
|
process: this.processName,
|
|
181
196
|
queues: {
|
|
@@ -274,6 +289,108 @@ class LogPersist {
|
|
|
274
289
|
private recordLaneSuccess(lane: LogPersistLane): void {
|
|
275
290
|
this.lastError = null;
|
|
276
291
|
this.consecutiveFailures[lane] = 0;
|
|
292
|
+
this.permanentFailureCount = 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private handleLaneFlushFailure(
|
|
296
|
+
lane: LogPersistLane,
|
|
297
|
+
error: unknown,
|
|
298
|
+
scheduleRetry: () => void,
|
|
299
|
+
logMessage: string,
|
|
300
|
+
context: Record<string, unknown>,
|
|
301
|
+
): void {
|
|
302
|
+
this.recordLaneError(lane, error);
|
|
303
|
+
const message = this.lastError ?? formatLogPersistError(error);
|
|
304
|
+
|
|
305
|
+
if (isPermanentLogPersistError(error)) {
|
|
306
|
+
this.permanentFailureCount += 1;
|
|
307
|
+
if (this.permanentFailureCount >= this.config.circuitBreakerPermanentFailureThreshold) {
|
|
308
|
+
this.tripCircuitBreaker(message);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
persistLogger.error(logMessage, {
|
|
314
|
+
...context,
|
|
315
|
+
error: message,
|
|
316
|
+
retryInMs: this.config.laneRetryIntervalMs,
|
|
317
|
+
permanentFailureCount: this.permanentFailureCount,
|
|
318
|
+
});
|
|
319
|
+
scheduleRetry();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private tripCircuitBreaker(reason: string): void {
|
|
323
|
+
if (this.circuitBreakerTripped) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
this.circuitBreakerTripped = true;
|
|
327
|
+
|
|
328
|
+
this.cancelMediumDebounce();
|
|
329
|
+
this.cancelLaneRetryTimers();
|
|
330
|
+
this.stopHeartbeat();
|
|
331
|
+
|
|
332
|
+
const dropped = this.queueSnapshot();
|
|
333
|
+
this.fastQueue = [];
|
|
334
|
+
this.fastRetryQueue = [];
|
|
335
|
+
this.mediumQueue = [];
|
|
336
|
+
this.slowQueue = [];
|
|
337
|
+
this.slowQueueStartedAt = null;
|
|
338
|
+
|
|
339
|
+
this.enabled = false;
|
|
340
|
+
registerLogPersistOffer(null);
|
|
341
|
+
|
|
342
|
+
persistLogger.error("circuit breaker tripped — log persist disabled", {
|
|
343
|
+
reason,
|
|
344
|
+
permanentFailureCount: this.permanentFailureCount,
|
|
345
|
+
threshold: this.config.circuitBreakerPermanentFailureThreshold,
|
|
346
|
+
dropped,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private cancelLaneRetryTimers(): void {
|
|
351
|
+
if (this.fastRetryTimer != null) {
|
|
352
|
+
clearTimeout(this.fastRetryTimer);
|
|
353
|
+
this.fastRetryTimer = null;
|
|
354
|
+
}
|
|
355
|
+
if (this.mediumRetryTimer != null) {
|
|
356
|
+
clearTimeout(this.mediumRetryTimer);
|
|
357
|
+
this.mediumRetryTimer = null;
|
|
358
|
+
}
|
|
359
|
+
if (this.slowRetryTimer != null) {
|
|
360
|
+
clearTimeout(this.slowRetryTimer);
|
|
361
|
+
this.slowRetryTimer = null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private scheduleLaneRetry(lane: "fast" | "medium" | "slow", flush: () => void): void {
|
|
366
|
+
const retryMs = this.config.laneRetryIntervalMs;
|
|
367
|
+
const existing =
|
|
368
|
+
lane === "fast" ? this.fastRetryTimer : lane === "medium" ? this.mediumRetryTimer : this.slowRetryTimer;
|
|
369
|
+
if (existing != null) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
persistLogger.warn(`${lane} flush retry scheduled`, { retryInMs: retryMs });
|
|
374
|
+
const timer = setTimeout(() => {
|
|
375
|
+
if (lane === "fast") {
|
|
376
|
+
this.fastRetryTimer = null;
|
|
377
|
+
} else if (lane === "medium") {
|
|
378
|
+
this.mediumRetryTimer = null;
|
|
379
|
+
} else {
|
|
380
|
+
this.slowRetryTimer = null;
|
|
381
|
+
}
|
|
382
|
+
persistLogger.debug(`${lane} flush retry timer fired`);
|
|
383
|
+
flush();
|
|
384
|
+
}, retryMs);
|
|
385
|
+
timer.unref?.();
|
|
386
|
+
|
|
387
|
+
if (lane === "fast") {
|
|
388
|
+
this.fastRetryTimer = timer;
|
|
389
|
+
} else if (lane === "medium") {
|
|
390
|
+
this.mediumRetryTimer = timer;
|
|
391
|
+
} else {
|
|
392
|
+
this.slowRetryTimer = timer;
|
|
393
|
+
}
|
|
277
394
|
}
|
|
278
395
|
|
|
279
396
|
private queueSnapshot(): Record<string, unknown> {
|
|
@@ -341,6 +458,9 @@ class LogPersist {
|
|
|
341
458
|
}
|
|
342
459
|
|
|
343
460
|
private scheduleMediumFlush(): void {
|
|
461
|
+
if (this.mediumRetryTimer != null) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
344
464
|
const { debounceMs, maxEntries, maxBytes } = this.config.medium;
|
|
345
465
|
const queueBytes = estimateEntriesBytes(this.mediumQueue);
|
|
346
466
|
if (this.mediumQueue.length >= maxEntries || queueBytes >= maxBytes) {
|
|
@@ -380,6 +500,9 @@ class LogPersist {
|
|
|
380
500
|
}
|
|
381
501
|
|
|
382
502
|
private scheduleSlowFlush(): void {
|
|
503
|
+
if (this.slowRetryTimer != null) {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
383
506
|
const { maxEntries, maxBytes, maxAgeMs } = this.config.slow;
|
|
384
507
|
const queueBytes = estimateEntriesBytes(this.slowQueue);
|
|
385
508
|
const ageMs = this.slowQueueStartedAt != null ? Date.now() - this.slowQueueStartedAt : 0;
|
|
@@ -518,15 +641,17 @@ class LogPersist {
|
|
|
518
641
|
fastRetryRemaining: this.fastRetryQueue.length,
|
|
519
642
|
});
|
|
520
643
|
} catch (error) {
|
|
521
|
-
this.recordLaneError("fast", error);
|
|
522
644
|
this.fastRetryQueue.push(entry);
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
error
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
645
|
+
this.handleLaneFlushFailure(
|
|
646
|
+
"fast",
|
|
647
|
+
error,
|
|
648
|
+
() => this.scheduleLaneRetry("fast", () => void this.flushFast()),
|
|
649
|
+
"fast flush failed — queued for retry",
|
|
650
|
+
{
|
|
651
|
+
idempotencyKey,
|
|
652
|
+
fastRetryQueue: this.fastRetryQueue.length,
|
|
653
|
+
},
|
|
654
|
+
);
|
|
530
655
|
break;
|
|
531
656
|
}
|
|
532
657
|
}
|
|
@@ -535,19 +660,6 @@ class LogPersist {
|
|
|
535
660
|
}
|
|
536
661
|
}
|
|
537
662
|
|
|
538
|
-
private scheduleFastRetry(): void {
|
|
539
|
-
if (this.fastRetryTimer != null) {
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
persistLogger.warn("fast flush retry scheduled", { retryInMs: FAST_RETRY_INTERVAL_MS });
|
|
543
|
-
this.fastRetryTimer = setTimeout(() => {
|
|
544
|
-
this.fastRetryTimer = null;
|
|
545
|
-
persistLogger.debug("fast flush retry timer fired");
|
|
546
|
-
void this.flushFast();
|
|
547
|
-
}, FAST_RETRY_INTERVAL_MS);
|
|
548
|
-
this.fastRetryTimer.unref?.();
|
|
549
|
-
}
|
|
550
|
-
|
|
551
663
|
private async flushMedium(): Promise<void> {
|
|
552
664
|
if (this.laneFlushing.medium) {
|
|
553
665
|
persistLogger.debug("medium flush skipped — already flushing");
|
|
@@ -601,15 +713,18 @@ class LogPersist {
|
|
|
601
713
|
queueRemaining: this.mediumQueue.length,
|
|
602
714
|
});
|
|
603
715
|
} catch (error) {
|
|
604
|
-
this.
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
716
|
+
this.handleLaneFlushFailure(
|
|
717
|
+
"medium",
|
|
718
|
+
error,
|
|
719
|
+
() => this.scheduleLaneRetry("medium", () => void this.flushMedium()),
|
|
720
|
+
"medium flush failed — entries retained",
|
|
721
|
+
{
|
|
722
|
+
idempotencyKey,
|
|
723
|
+
batchSize: batch.length,
|
|
724
|
+
batchBytes,
|
|
725
|
+
queueRemaining: this.mediumQueue.length,
|
|
726
|
+
},
|
|
727
|
+
);
|
|
613
728
|
break;
|
|
614
729
|
}
|
|
615
730
|
}
|
|
@@ -617,7 +732,7 @@ class LogPersist {
|
|
|
617
732
|
this.laneFlushing.medium = false;
|
|
618
733
|
}
|
|
619
734
|
|
|
620
|
-
if (this.mediumQueue.length > 0 && !this.laneFlushing.medium) {
|
|
735
|
+
if (this.mediumQueue.length > 0 && !this.laneFlushing.medium && this.mediumRetryTimer == null) {
|
|
621
736
|
this.scheduleMediumFlush();
|
|
622
737
|
}
|
|
623
738
|
}
|
|
@@ -675,15 +790,18 @@ class LogPersist {
|
|
|
675
790
|
queueRemaining: this.slowQueue.length,
|
|
676
791
|
});
|
|
677
792
|
} catch (error) {
|
|
678
|
-
this.
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
793
|
+
this.handleLaneFlushFailure(
|
|
794
|
+
"slow",
|
|
795
|
+
error,
|
|
796
|
+
() => this.scheduleLaneRetry("slow", () => void this.flushSlow()),
|
|
797
|
+
"slow flush failed — entries retained",
|
|
798
|
+
{
|
|
799
|
+
idempotencyKey,
|
|
800
|
+
batchSize: batch.length,
|
|
801
|
+
batchBytes,
|
|
802
|
+
queueRemaining: this.slowQueue.length,
|
|
803
|
+
},
|
|
804
|
+
);
|
|
687
805
|
break;
|
|
688
806
|
}
|
|
689
807
|
}
|
|
@@ -695,7 +813,7 @@ class LogPersist {
|
|
|
695
813
|
this.laneFlushing.slow = false;
|
|
696
814
|
}
|
|
697
815
|
|
|
698
|
-
if (this.slowQueue.length > 0 && !this.laneFlushing.slow) {
|
|
816
|
+
if (this.slowQueue.length > 0 && !this.laneFlushing.slow && this.slowRetryTimer == null) {
|
|
699
817
|
this.scheduleSlowFlush();
|
|
700
818
|
}
|
|
701
819
|
}
|