• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

flyingsquirrel0419 / layercache / 25331407836

04 May 2026 04:49PM UTC coverage: 95.656%. First build
25331407836

Pull #66

github

web-flow
Merge b37c6cfec into 353bf9eb5
Pull Request #66: Avoid sorting on pruning hot paths

1714 of 1845 branches covered (92.9%)

Branch coverage included in aggregate %.

18 of 21 new or added lines in 3 files covered. (85.71%)

3108 of 3196 relevant lines covered (97.25%)

334.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.59
/src/internal/CacheStackMaintenance.ts
1
import type { CacheWriteBehindOptions } from '../types'
2

3
type WriteBehindOperation = () => Promise<void>
4
type FlushWriteBehindBatch = (batch: WriteBehindOperation[]) => Promise<void>
5
type GenerationCleanupTask = (generation: number) => Promise<void>
6
type GenerationCleanupErrorHandler = (generation: number, error: unknown) => void
7

8
const MAX_KEY_EPOCHS = 50_000
15✔
9

10
export class CacheStackMaintenance {
11
  private readonly keyEpochs = new Map<string, number>()
318✔
12
  private readonly writeBehindQueue: WriteBehindOperation[] = []
318✔
13
  private writeBehindTimer?: ReturnType<typeof setInterval>
14
  private writeBehindFlushPromise?: Promise<void>
15
  private generationCleanupPromise?: Promise<void>
16
  private clearEpoch = 0
318✔
17

18
  initializeWriteBehindTimer(
19
    writeStrategy: 'write-through' | 'write-behind' | undefined,
20
    options: CacheWriteBehindOptions | undefined,
21
    flush: () => Promise<void>
22
  ): void {
23
    if (writeStrategy !== 'write-behind') {
252✔
24
      return
245✔
25
    }
26

27
    const flushIntervalMs = options?.flushIntervalMs
7✔
28
    if (!flushIntervalMs || flushIntervalMs <= 0) {
252✔
29
      return
5✔
30
    }
31

32
    this.disposeWriteBehindTimer()
2✔
33
    this.writeBehindTimer = setInterval(() => {
2✔
34
      void flush()
1✔
35
    }, flushIntervalMs)
36
    this.writeBehindTimer.unref?.()
2✔
37
  }
38

39
  disposeWriteBehindTimer(): void {
40
    if (!this.writeBehindTimer) {
31✔
41
      return
29✔
42
    }
43

44
    clearInterval(this.writeBehindTimer)
2✔
45
    this.writeBehindTimer = undefined
2✔
46
  }
47

48
  beginClearEpoch(): void {
49
    this.clearEpoch += 1
8✔
50
    this.keyEpochs.clear()
8✔
51
    this.writeBehindQueue.length = 0
8✔
52
  }
53

54
  currentClearEpoch(): number {
55
    return this.clearEpoch
412✔
56
  }
57

58
  currentKeyEpoch(key: string): number {
59
    return this.keyEpochs.get(key) ?? 0
50,920✔
60
  }
61

62
  bumpKeyEpochs(keys: string[]): void {
63
    for (const key of keys) {
54✔
64
      const nextEpoch = this.currentKeyEpoch(key) + 1
50,069✔
65
      this.keyEpochs.delete(key)
50,069✔
66
      this.keyEpochs.set(key, nextEpoch)
50,069✔
67
    }
68
    this.pruneKeyEpochsIfNeeded()
54✔
69
  }
70

71
  isWriteOutdated(key: string, expectedClearEpoch?: number, expectedKeyEpoch?: number): boolean {
72
    if (expectedClearEpoch !== undefined && expectedClearEpoch !== this.clearEpoch) {
470✔
73
      return true
2✔
74
    }
75

76
    if (expectedKeyEpoch !== undefined && expectedKeyEpoch !== this.currentKeyEpoch(key)) {
468✔
77
      return true
3✔
78
    }
79

80
    return false
465✔
81
  }
82

83
  async enqueueWriteBehind(
84
    operation: WriteBehindOperation,
85
    options: CacheWriteBehindOptions | undefined,
86
    flushBatch: FlushWriteBehindBatch
87
  ): Promise<void> {
88
    this.writeBehindQueue.push(operation)
15✔
89
    const batchSize = options?.batchSize ?? 100
15!
90
    const maxQueueSize = options?.maxQueueSize ?? batchSize * 10
15✔
91

92
    if (this.writeBehindQueue.length >= batchSize) {
15✔
93
      await this.flushWriteBehindQueue(options, flushBatch)
5✔
94
      return
5✔
95
    }
96

97
    if (this.writeBehindQueue.length >= maxQueueSize) {
10✔
98
      await this.flushWriteBehindQueue(options, flushBatch)
1✔
99
    }
100
  }
101

102
  async flushWriteBehindQueue(
103
    options: CacheWriteBehindOptions | undefined,
104
    flushBatch: FlushWriteBehindBatch
105
  ): Promise<void> {
106
    if (this.writeBehindFlushPromise || this.writeBehindQueue.length === 0) {
38✔
107
      await this.writeBehindFlushPromise
30✔
108
      return
30✔
109
    }
110

111
    const batchSize = options?.batchSize ?? 100
8!
112
    const batch = this.writeBehindQueue.splice(0, batchSize)
38✔
113
    this.writeBehindFlushPromise = flushBatch(batch)
38✔
114

115
    try {
38✔
116
      await this.writeBehindFlushPromise
38✔
117
    } finally {
118
      this.writeBehindFlushPromise = undefined
8✔
119
    }
120

121
    if (this.writeBehindQueue.length > 0) {
8!
122
      await this.flushWriteBehindQueue(options, flushBatch)
×
123
    }
124
  }
125

126
  scheduleGenerationCleanup(
127
    generation: number,
128
    task: GenerationCleanupTask,
129
    onError: GenerationCleanupErrorHandler
130
  ): void {
131
    const scheduledTask = (this.generationCleanupPromise ?? Promise.resolve())
6✔
132
      .then(() => task(generation))
6✔
133
      .catch((error) => {
134
        onError(generation, error)
2✔
135
      })
136

137
    this.generationCleanupPromise = scheduledTask.finally(() => {
6✔
138
      if (this.generationCleanupPromise === scheduledTask) {
6!
139
        this.generationCleanupPromise = undefined
×
140
      }
141
    })
142
  }
143

144
  async waitForGenerationCleanup(): Promise<void> {
145
    await this.generationCleanupPromise
30✔
146
  }
147

148
  private pruneKeyEpochsIfNeeded(): void {
149
    if (this.keyEpochs.size <= MAX_KEY_EPOCHS) {
54✔
150
      return
51✔
151
    }
152

153
    const toDelete = Math.ceil(this.keyEpochs.size * 0.1)
3✔
154
    for (let i = 0; i < toDelete; i++) {
3✔
155
      const oldestKey = this.keyEpochs.keys().next().value
15,003✔
156
      if (oldestKey === undefined) {
15,003!
NEW
157
        break
×
158
      }
159
      this.keyEpochs.delete(oldestKey)
15,003✔
160
    }
161
  }
162
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc