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.
Files changed (79) hide show
  1. package/dist/browser.d.ts +4 -6
  2. package/dist/browser.d.ts.map +1 -1
  3. package/dist/browser.js +3 -3
  4. package/dist/chunks/{enable-log-persist-BB-unD4p.js → enable-log-persist-Dl55_mv2.js} +160 -30
  5. package/dist/chunks/enable-log-persist-Dl55_mv2.js.map +1 -0
  6. package/dist/chunks/{list.api-6Q4_-7oN.js → list.api-BzbTJBsM.js} +81 -13
  7. package/dist/chunks/list.api-BzbTJBsM.js.map +1 -0
  8. package/dist/chunks/{task-queue-D5uWjqVN.js → task-queue-H4mpSW8R.js} +55 -16
  9. package/dist/chunks/task-queue-H4mpSW8R.js.map +1 -0
  10. package/dist/core.api.d.ts +6 -15
  11. package/dist/core.api.d.ts.map +1 -1
  12. package/dist/core.interface.d.ts +5 -0
  13. package/dist/core.interface.d.ts.map +1 -1
  14. package/dist/core.schema.d.ts +77 -0
  15. package/dist/core.schema.d.ts.map +1 -0
  16. package/dist/link/index.d.ts +10 -0
  17. package/dist/link/index.d.ts.map +1 -0
  18. package/dist/link/link.build.d.ts +45 -0
  19. package/dist/link/link.build.d.ts.map +1 -0
  20. package/dist/list/index.d.ts +10 -0
  21. package/dist/list/index.d.ts.map +1 -0
  22. package/dist/list/list.build.d.ts +21 -0
  23. package/dist/list/list.build.d.ts.map +1 -0
  24. package/dist/node/preload/run-service-preload.d.ts +1 -0
  25. package/dist/node/preload/run-service-preload.d.ts.map +1 -1
  26. package/dist/node/preload/service-preload.interface.d.ts +5 -0
  27. package/dist/node/preload/service-preload.interface.d.ts.map +1 -1
  28. package/dist/node.js +6 -6
  29. package/dist/parcel/parcel.api.d.ts +3 -3
  30. package/dist/parcel/parcel.api.d.ts.map +1 -1
  31. package/dist/preload.js +14 -3
  32. package/dist/preload.js.map +1 -1
  33. package/dist/resource/resource.interface.d.ts +20 -0
  34. package/dist/resource/resource.interface.d.ts.map +1 -0
  35. package/dist/service/config.api.d.ts.map +1 -1
  36. package/dist/service/service.api.d.ts +6 -6
  37. package/dist/service/service.api.d.ts.map +1 -1
  38. package/dist/shared/log/index.d.ts +2 -0
  39. package/dist/shared/log/index.d.ts.map +1 -1
  40. package/dist/shared/log/log-min-level.d.ts +15 -0
  41. package/dist/shared/log/log-min-level.d.ts.map +1 -0
  42. package/dist/shared/log/log-persist-errors.d.ts +4 -0
  43. package/dist/shared/log/log-persist-errors.d.ts.map +1 -0
  44. package/dist/shared/log/log-persist-logger.d.ts +1 -0
  45. package/dist/shared/log/log-persist-logger.d.ts.map +1 -1
  46. package/dist/shared/log/log-persist.config.d.ts.map +1 -1
  47. package/dist/shared/log/log-persist.d.ts +8 -1
  48. package/dist/shared/log/log-persist.d.ts.map +1 -1
  49. package/dist/shared/log/log-persist.interface.d.ts +8 -0
  50. package/dist/shared/log/log-persist.interface.d.ts.map +1 -1
  51. package/dist/task/task-post-validated.api.d.ts +2 -2
  52. package/dist/task/task-post.api.d.ts +6 -6
  53. package/dist/trigger/trigger.api.d.ts +6 -6
  54. package/package.json +1 -1
  55. package/src/browser.ts +6 -5
  56. package/src/core.api.ts +19 -28
  57. package/src/core.interface.ts +6 -0
  58. package/src/core.schema.ts +51 -0
  59. package/src/link/index.ts +10 -0
  60. package/src/link/link.build.ts +48 -0
  61. package/src/list/index.ts +13 -0
  62. package/src/list/list.build.ts +51 -0
  63. package/src/node/preload/run-service-preload.ts +14 -0
  64. package/src/node/preload/service-preload.interface.ts +6 -0
  65. package/src/resource/resource.interface.ts +37 -0
  66. package/src/service/config.api.ts +1 -0
  67. package/src/shared/log/index.ts +7 -0
  68. package/src/shared/log/log-min-level.ts +46 -0
  69. package/src/shared/log/log-persist-errors.ts +16 -0
  70. package/src/shared/log/log-persist-logger.ts +28 -0
  71. package/src/shared/log/log-persist.config.ts +20 -0
  72. package/src/shared/log/log-persist.interface.ts +8 -0
  73. package/src/shared/log/log-persist.ts +161 -43
  74. package/dist/chunks/enable-log-persist-BB-unD4p.js.map +0 -1
  75. package/dist/chunks/list.api-6Q4_-7oN.js.map +0 -1
  76. package/dist/chunks/task-queue-D5uWjqVN.js.map +0 -1
  77. package/dist/idea/idea.interface.d.ts +0 -25
  78. package/dist/idea/idea.interface.d.ts.map +0 -1
  79. 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 { isLogPersistInternalTopic, patchPersistLoggerScope, persistLogger } from "./log-persist-logger";
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
- persistLogger.error("fast flush failed — queued for retry", {
524
- idempotencyKey,
525
- error: this.lastError,
526
- retryInMs: FAST_RETRY_INTERVAL_MS,
527
- fastRetryQueue: this.fastRetryQueue.length,
528
- });
529
- this.scheduleFastRetry();
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.recordLaneError("medium", error);
605
- persistLogger.error("medium flush failed — entries retained", {
606
- idempotencyKey,
607
- batchSize: batch.length,
608
- batchBytes,
609
- error: this.lastError,
610
- queueRemaining: this.mediumQueue.length,
611
- });
612
- this.scheduleMediumFlush();
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.recordLaneError("slow", error);
679
- persistLogger.error("slow flush failed — entries retained", {
680
- idempotencyKey,
681
- batchSize: batch.length,
682
- batchBytes,
683
- error: this.lastError,
684
- queueRemaining: this.slowQueue.length,
685
- });
686
- this.scheduleSlowFlush();
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
  }