@libp2p/gossipsub 14.1.1-6059227cb

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 (160) hide show
  1. package/README.md +85 -0
  2. package/dist/index.min.js +19 -0
  3. package/dist/index.min.js.map +7 -0
  4. package/dist/src/config.d.ts +32 -0
  5. package/dist/src/config.d.ts.map +1 -0
  6. package/dist/src/config.js +2 -0
  7. package/dist/src/config.js.map +1 -0
  8. package/dist/src/constants.d.ts +213 -0
  9. package/dist/src/constants.d.ts.map +1 -0
  10. package/dist/src/constants.js +217 -0
  11. package/dist/src/constants.js.map +1 -0
  12. package/dist/src/errors.d.ts +9 -0
  13. package/dist/src/errors.d.ts.map +1 -0
  14. package/dist/src/errors.js +15 -0
  15. package/dist/src/errors.js.map +1 -0
  16. package/dist/src/gossipsub.d.ts +419 -0
  17. package/dist/src/gossipsub.d.ts.map +1 -0
  18. package/dist/src/gossipsub.js +2520 -0
  19. package/dist/src/gossipsub.js.map +1 -0
  20. package/dist/src/index.d.ts +344 -0
  21. package/dist/src/index.d.ts.map +1 -0
  22. package/dist/src/index.js +43 -0
  23. package/dist/src/index.js.map +1 -0
  24. package/dist/src/message/decodeRpc.d.ts +11 -0
  25. package/dist/src/message/decodeRpc.d.ts.map +1 -0
  26. package/dist/src/message/decodeRpc.js +10 -0
  27. package/dist/src/message/decodeRpc.js.map +1 -0
  28. package/dist/src/message/index.d.ts +2 -0
  29. package/dist/src/message/index.d.ts.map +1 -0
  30. package/dist/src/message/index.js +2 -0
  31. package/dist/src/message/index.js.map +1 -0
  32. package/dist/src/message/rpc.d.ts +99 -0
  33. package/dist/src/message/rpc.d.ts.map +1 -0
  34. package/dist/src/message/rpc.js +663 -0
  35. package/dist/src/message/rpc.js.map +1 -0
  36. package/dist/src/message-cache.d.ts +80 -0
  37. package/dist/src/message-cache.d.ts.map +1 -0
  38. package/dist/src/message-cache.js +144 -0
  39. package/dist/src/message-cache.js.map +1 -0
  40. package/dist/src/metrics.d.ts +467 -0
  41. package/dist/src/metrics.d.ts.map +1 -0
  42. package/dist/src/metrics.js +896 -0
  43. package/dist/src/metrics.js.map +1 -0
  44. package/dist/src/score/compute-score.d.ts +4 -0
  45. package/dist/src/score/compute-score.d.ts.map +1 -0
  46. package/dist/src/score/compute-score.js +75 -0
  47. package/dist/src/score/compute-score.js.map +1 -0
  48. package/dist/src/score/index.d.ts +4 -0
  49. package/dist/src/score/index.d.ts.map +1 -0
  50. package/dist/src/score/index.js +4 -0
  51. package/dist/src/score/index.js.map +1 -0
  52. package/dist/src/score/message-deliveries.d.ts +45 -0
  53. package/dist/src/score/message-deliveries.d.ts.map +1 -0
  54. package/dist/src/score/message-deliveries.js +75 -0
  55. package/dist/src/score/message-deliveries.js.map +1 -0
  56. package/dist/src/score/peer-score-params.d.ts +125 -0
  57. package/dist/src/score/peer-score-params.d.ts.map +1 -0
  58. package/dist/src/score/peer-score-params.js +159 -0
  59. package/dist/src/score/peer-score-params.js.map +1 -0
  60. package/dist/src/score/peer-score-thresholds.d.ts +31 -0
  61. package/dist/src/score/peer-score-thresholds.d.ts.map +1 -0
  62. package/dist/src/score/peer-score-thresholds.js +32 -0
  63. package/dist/src/score/peer-score-thresholds.js.map +1 -0
  64. package/dist/src/score/peer-score.d.ts +119 -0
  65. package/dist/src/score/peer-score.d.ts.map +1 -0
  66. package/dist/src/score/peer-score.js +459 -0
  67. package/dist/src/score/peer-score.js.map +1 -0
  68. package/dist/src/score/peer-stats.d.ts +32 -0
  69. package/dist/src/score/peer-stats.d.ts.map +1 -0
  70. package/dist/src/score/peer-stats.js +2 -0
  71. package/dist/src/score/peer-stats.js.map +1 -0
  72. package/dist/src/score/scoreMetrics.d.ts +23 -0
  73. package/dist/src/score/scoreMetrics.d.ts.map +1 -0
  74. package/dist/src/score/scoreMetrics.js +155 -0
  75. package/dist/src/score/scoreMetrics.js.map +1 -0
  76. package/dist/src/stream.d.ts +30 -0
  77. package/dist/src/stream.d.ts.map +1 -0
  78. package/dist/src/stream.js +55 -0
  79. package/dist/src/stream.js.map +1 -0
  80. package/dist/src/tracer.d.ts +53 -0
  81. package/dist/src/tracer.d.ts.map +1 -0
  82. package/dist/src/tracer.js +155 -0
  83. package/dist/src/tracer.js.map +1 -0
  84. package/dist/src/types.d.ts +148 -0
  85. package/dist/src/types.d.ts.map +1 -0
  86. package/dist/src/types.js +90 -0
  87. package/dist/src/types.js.map +1 -0
  88. package/dist/src/utils/buildRawMessage.d.ts +20 -0
  89. package/dist/src/utils/buildRawMessage.d.ts.map +1 -0
  90. package/dist/src/utils/buildRawMessage.js +151 -0
  91. package/dist/src/utils/buildRawMessage.js.map +1 -0
  92. package/dist/src/utils/create-gossip-rpc.d.ts +7 -0
  93. package/dist/src/utils/create-gossip-rpc.d.ts.map +1 -0
  94. package/dist/src/utils/create-gossip-rpc.js +31 -0
  95. package/dist/src/utils/create-gossip-rpc.js.map +1 -0
  96. package/dist/src/utils/index.d.ts +4 -0
  97. package/dist/src/utils/index.d.ts.map +1 -0
  98. package/dist/src/utils/index.js +4 -0
  99. package/dist/src/utils/index.js.map +1 -0
  100. package/dist/src/utils/messageIdToString.d.ts +5 -0
  101. package/dist/src/utils/messageIdToString.d.ts.map +1 -0
  102. package/dist/src/utils/messageIdToString.js +8 -0
  103. package/dist/src/utils/messageIdToString.js.map +1 -0
  104. package/dist/src/utils/msgIdFn.d.ts +10 -0
  105. package/dist/src/utils/msgIdFn.d.ts.map +1 -0
  106. package/dist/src/utils/msgIdFn.js +23 -0
  107. package/dist/src/utils/msgIdFn.js.map +1 -0
  108. package/dist/src/utils/multiaddr.d.ts +3 -0
  109. package/dist/src/utils/multiaddr.d.ts.map +1 -0
  110. package/dist/src/utils/multiaddr.js +15 -0
  111. package/dist/src/utils/multiaddr.js.map +1 -0
  112. package/dist/src/utils/publishConfig.d.ts +8 -0
  113. package/dist/src/utils/publishConfig.d.ts.map +1 -0
  114. package/dist/src/utils/publishConfig.js +25 -0
  115. package/dist/src/utils/publishConfig.js.map +1 -0
  116. package/dist/src/utils/set.d.ts +14 -0
  117. package/dist/src/utils/set.d.ts.map +1 -0
  118. package/dist/src/utils/set.js +41 -0
  119. package/dist/src/utils/set.js.map +1 -0
  120. package/dist/src/utils/shuffle.d.ts +7 -0
  121. package/dist/src/utils/shuffle.d.ts.map +1 -0
  122. package/dist/src/utils/shuffle.js +21 -0
  123. package/dist/src/utils/shuffle.js.map +1 -0
  124. package/dist/src/utils/time-cache.d.ts +22 -0
  125. package/dist/src/utils/time-cache.d.ts.map +1 -0
  126. package/dist/src/utils/time-cache.js +54 -0
  127. package/dist/src/utils/time-cache.js.map +1 -0
  128. package/package.json +142 -0
  129. package/src/config.ts +31 -0
  130. package/src/constants.ts +261 -0
  131. package/src/errors.ts +17 -0
  132. package/src/gossipsub.ts +3061 -0
  133. package/src/index.ts +404 -0
  134. package/src/message/decodeRpc.ts +19 -0
  135. package/src/message/index.ts +1 -0
  136. package/src/message/rpc.proto +58 -0
  137. package/src/message/rpc.ts +848 -0
  138. package/src/message-cache.ts +196 -0
  139. package/src/metrics.ts +1014 -0
  140. package/src/score/compute-score.ts +98 -0
  141. package/src/score/index.ts +3 -0
  142. package/src/score/message-deliveries.ts +95 -0
  143. package/src/score/peer-score-params.ts +316 -0
  144. package/src/score/peer-score-thresholds.ts +70 -0
  145. package/src/score/peer-score.ts +565 -0
  146. package/src/score/peer-stats.ts +33 -0
  147. package/src/score/scoreMetrics.ts +215 -0
  148. package/src/stream.ts +79 -0
  149. package/src/tracer.ts +177 -0
  150. package/src/types.ts +178 -0
  151. package/src/utils/buildRawMessage.ts +174 -0
  152. package/src/utils/create-gossip-rpc.ts +34 -0
  153. package/src/utils/index.ts +3 -0
  154. package/src/utils/messageIdToString.ts +8 -0
  155. package/src/utils/msgIdFn.ts +24 -0
  156. package/src/utils/multiaddr.ts +19 -0
  157. package/src/utils/publishConfig.ts +33 -0
  158. package/src/utils/set.ts +43 -0
  159. package/src/utils/shuffle.ts +21 -0
  160. package/src/utils/time-cache.ts +71 -0
@@ -0,0 +1,174 @@
1
+ import { randomBytes } from '@libp2p/crypto'
2
+ import { publicKeyFromProtobuf } from '@libp2p/crypto/keys'
3
+ import { peerIdFromMultihash } from '@libp2p/peer-id'
4
+ import * as Digest from 'multiformats/hashes/digest'
5
+ import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
6
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
7
+ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
8
+ import { StrictSign, StrictNoSign } from '../index.ts'
9
+ import { RPC } from '../message/rpc.js'
10
+ import { PublishConfigType, ValidateError } from '../types.js'
11
+ import type { Message } from '../index.ts'
12
+ import type { PublishConfig, TopicStr } from '../types.js'
13
+ import type { PublicKey, PeerId } from '@libp2p/interface'
14
+
15
+ export const SignPrefix = uint8ArrayFromString('libp2p-pubsub:')
16
+
17
+ export interface RawMessageAndMessage {
18
+ raw: RPC.Message
19
+ msg: Message
20
+ }
21
+
22
+ export async function buildRawMessage (
23
+ publishConfig: PublishConfig,
24
+ topic: TopicStr,
25
+ originalData: Uint8Array,
26
+ transformedData: Uint8Array
27
+ ): Promise<RawMessageAndMessage> {
28
+ switch (publishConfig.type) {
29
+ case PublishConfigType.Signing: {
30
+ const rpcMsg: RPC.Message = {
31
+ from: publishConfig.author.toMultihash().bytes,
32
+ data: transformedData,
33
+ seqno: randomBytes(8),
34
+ topic,
35
+ signature: undefined, // Exclude signature field for signing
36
+ key: undefined // Exclude key field for signing
37
+ }
38
+
39
+ // Get the message in bytes, and prepend with the pubsub prefix
40
+ // the signature is over the bytes "libp2p-pubsub:<protobuf-message>"
41
+ const bytes = uint8ArrayConcat([SignPrefix, RPC.Message.encode(rpcMsg)])
42
+
43
+ rpcMsg.signature = await publishConfig.privateKey.sign(bytes)
44
+ rpcMsg.key = publishConfig.key
45
+
46
+ const msg: Message = {
47
+ type: 'signed',
48
+ from: publishConfig.author,
49
+ data: originalData,
50
+ sequenceNumber: BigInt(`0x${uint8ArrayToString(rpcMsg.seqno ?? new Uint8Array(0), 'base16')}`),
51
+ topic,
52
+ signature: rpcMsg.signature,
53
+ key: publicKeyFromProtobuf(rpcMsg.key)
54
+ }
55
+ return {
56
+ raw: rpcMsg,
57
+ msg
58
+ }
59
+ }
60
+
61
+ case PublishConfigType.Anonymous: {
62
+ return {
63
+ raw: {
64
+ from: undefined,
65
+ data: transformedData,
66
+ seqno: undefined,
67
+ topic,
68
+ signature: undefined,
69
+ key: undefined
70
+ },
71
+ msg: {
72
+ type: 'unsigned',
73
+ data: originalData,
74
+ topic
75
+ }
76
+ }
77
+ }
78
+
79
+ default:
80
+ throw new Error('Unreachable')
81
+ }
82
+ }
83
+
84
+ export type ValidationResult = { valid: true, message: Message } | { valid: false, error: ValidateError }
85
+
86
+ export async function validateToRawMessage (
87
+ signaturePolicy: typeof StrictNoSign | typeof StrictSign,
88
+ msg: RPC.Message
89
+ ): Promise<ValidationResult> {
90
+ // If strict-sign, verify all
91
+ // If anonymous (no-sign), ensure no preven
92
+
93
+ switch (signaturePolicy) {
94
+ case StrictNoSign:
95
+ if (msg.signature != null) { return { valid: false, error: ValidateError.SignaturePresent } }
96
+ if (msg.seqno != null) { return { valid: false, error: ValidateError.SeqnoPresent } }
97
+ if (msg.key != null) { return { valid: false, error: ValidateError.FromPresent } }
98
+
99
+ return { valid: true, message: { type: 'unsigned', topic: msg.topic, data: msg.data ?? new Uint8Array(0) } }
100
+
101
+ case StrictSign: {
102
+ // Verify seqno
103
+ if (msg.seqno == null) { return { valid: false, error: ValidateError.InvalidSeqno } }
104
+ if (msg.seqno.length !== 8) {
105
+ return { valid: false, error: ValidateError.InvalidSeqno }
106
+ }
107
+
108
+ if (msg.signature == null) { return { valid: false, error: ValidateError.InvalidSignature } }
109
+ if (msg.from == null) { return { valid: false, error: ValidateError.InvalidPeerId } }
110
+
111
+ let fromPeerId: PeerId
112
+ try {
113
+ // TODO: Fix PeerId types
114
+ fromPeerId = peerIdFromMultihash(Digest.decode(msg.from))
115
+ } catch (e) {
116
+ return { valid: false, error: ValidateError.InvalidPeerId }
117
+ }
118
+
119
+ // - check from defined
120
+ // - transform source to PeerId
121
+ // - parse signature
122
+ // - get .key, else from source
123
+ // - check key == source if present
124
+ // - verify sig
125
+
126
+ let publicKey: PublicKey
127
+ if (msg.key != null) {
128
+ publicKey = publicKeyFromProtobuf(msg.key)
129
+ // TODO: Should `fromPeerId.pubKey` be optional?
130
+ if (fromPeerId.publicKey !== undefined && !publicKey.equals(fromPeerId.publicKey)) {
131
+ return { valid: false, error: ValidateError.InvalidPeerId }
132
+ }
133
+ } else {
134
+ if (fromPeerId.publicKey == null) {
135
+ return { valid: false, error: ValidateError.InvalidPeerId }
136
+ }
137
+ publicKey = fromPeerId.publicKey
138
+ }
139
+
140
+ const rpcMsgPreSign: RPC.Message = {
141
+ from: msg.from,
142
+ data: msg.data,
143
+ seqno: msg.seqno,
144
+ topic: msg.topic,
145
+ signature: undefined, // Exclude signature field for signing
146
+ key: undefined // Exclude key field for signing
147
+ }
148
+
149
+ // Get the message in bytes, and prepend with the pubsub prefix
150
+ // the signature is over the bytes "libp2p-pubsub:<protobuf-message>"
151
+ const bytes = uint8ArrayConcat([SignPrefix, RPC.Message.encode(rpcMsgPreSign)])
152
+
153
+ if (!(await publicKey.verify(bytes, msg.signature))) {
154
+ return { valid: false, error: ValidateError.InvalidSignature }
155
+ }
156
+
157
+ return {
158
+ valid: true,
159
+ message: {
160
+ type: 'signed',
161
+ from: fromPeerId,
162
+ data: msg.data ?? new Uint8Array(0),
163
+ sequenceNumber: BigInt(`0x${uint8ArrayToString(msg.seqno, 'base16')}`),
164
+ topic: msg.topic,
165
+ signature: msg.signature,
166
+ key: msg.key != null ? publicKeyFromProtobuf(msg.key) : publicKey
167
+ }
168
+ }
169
+ }
170
+
171
+ default:
172
+ throw new Error('Unreachable')
173
+ }
174
+ }
@@ -0,0 +1,34 @@
1
+ import type { RPC } from '../message/rpc.js'
2
+
3
+ /**
4
+ * Create a gossipsub RPC object
5
+ */
6
+ export function createGossipRpc (messages: RPC.Message[] = [], control?: Partial<RPC.ControlMessage>): RPC {
7
+ return {
8
+ subscriptions: [],
9
+ messages,
10
+ control: control !== undefined
11
+ ? {
12
+ graft: control.graft ?? [],
13
+ prune: control.prune ?? [],
14
+ ihave: control.ihave ?? [],
15
+ iwant: control.iwant ?? [],
16
+ idontwant: control.idontwant ?? []
17
+ }
18
+ : undefined
19
+ }
20
+ }
21
+
22
+ export function ensureControl (rpc: RPC): Required<RPC> {
23
+ if (rpc.control === undefined) {
24
+ rpc.control = {
25
+ graft: [],
26
+ prune: [],
27
+ ihave: [],
28
+ iwant: [],
29
+ idontwant: []
30
+ }
31
+ }
32
+
33
+ return rpc as Required<RPC>
34
+ }
@@ -0,0 +1,3 @@
1
+ export * from './shuffle.js'
2
+ export * from './messageIdToString.js'
3
+ export { getPublishConfigFromPeerId } from './publishConfig.js'
@@ -0,0 +1,8 @@
1
+ import { toString } from 'uint8arrays/to-string'
2
+
3
+ /**
4
+ * Browser friendly function to convert Uint8Array message id to base64 string.
5
+ */
6
+ export function messageIdToString (msgId: Uint8Array): string {
7
+ return toString(msgId, 'base64')
8
+ }
@@ -0,0 +1,24 @@
1
+ import { msgId } from '@libp2p/pubsub/utils'
2
+ import { sha256 } from 'multiformats/hashes/sha2'
3
+ import type { Message } from '../index.js'
4
+
5
+ /**
6
+ * Generate a message id, based on the `key` and `seqno`
7
+ */
8
+ export function msgIdFnStrictSign (msg: Message): Uint8Array {
9
+ if (msg.type !== 'signed') {
10
+ throw new Error('expected signed message type')
11
+ }
12
+ // Should never happen
13
+ if (msg.sequenceNumber == null) { throw Error('missing seqno field') }
14
+
15
+ // TODO: Should use .from here or key?
16
+ return msgId(msg.from.publicKey ?? msg.key, msg.sequenceNumber)
17
+ }
18
+
19
+ /**
20
+ * Generate a message id, based on message `data`
21
+ */
22
+ export async function msgIdFnStrictNoSign (msg: Message): Promise<Uint8Array> {
23
+ return sha256.encode(msg.data)
24
+ }
@@ -0,0 +1,19 @@
1
+ import { getNetConfig, isNetworkAddress } from '@libp2p/utils'
2
+ import type { Multiaddr } from '@multiformats/multiaddr'
3
+
4
+ export function multiaddrToIPStr (multiaddr: Multiaddr): string | null {
5
+ if (isNetworkAddress(multiaddr)) {
6
+ const config = getNetConfig(multiaddr)
7
+
8
+ switch (config.type) {
9
+ case 'ip4':
10
+ case 'ip6':
11
+
12
+ return config.host
13
+ default:
14
+ break
15
+ }
16
+ }
17
+
18
+ return null
19
+ }
@@ -0,0 +1,33 @@
1
+ import { publicKeyToProtobuf } from '@libp2p/crypto/keys'
2
+ import { StrictSign, StrictNoSign } from '../index.ts'
3
+ import { PublishConfigType } from '../types.js'
4
+ import type { PublishConfig } from '../types.js'
5
+ import type { PeerId, PrivateKey } from '@libp2p/interface'
6
+
7
+ /**
8
+ * Prepare a PublishConfig object from a PeerId.
9
+ */
10
+ export function getPublishConfigFromPeerId (
11
+ signaturePolicy: typeof StrictSign | typeof StrictNoSign,
12
+ peerId: PeerId,
13
+ privateKey: PrivateKey
14
+ ): PublishConfig {
15
+ switch (signaturePolicy) {
16
+ case StrictSign: {
17
+ return {
18
+ type: PublishConfigType.Signing,
19
+ author: peerId,
20
+ key: publicKeyToProtobuf(privateKey.publicKey),
21
+ privateKey
22
+ }
23
+ }
24
+
25
+ case StrictNoSign:
26
+ return {
27
+ type: PublishConfigType.Anonymous
28
+ }
29
+
30
+ default:
31
+ throw new Error(`Unknown signature policy "${signaturePolicy}"`)
32
+ }
33
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Exclude up to `ineed` items from a set if item meets condition `cond`
3
+ */
4
+ export function removeItemsFromSet<T> (
5
+ superSet: Set<T>,
6
+ ineed: number,
7
+ cond: (peer: T) => boolean = () => true
8
+ ): Set<T> {
9
+ const subset = new Set<T>()
10
+ if (ineed <= 0) { return subset }
11
+
12
+ for (const id of superSet) {
13
+ if (subset.size >= ineed) { break }
14
+ if (cond(id)) {
15
+ subset.add(id)
16
+ superSet.delete(id)
17
+ }
18
+ }
19
+
20
+ return subset
21
+ }
22
+
23
+ /**
24
+ * Exclude up to `ineed` items from a set
25
+ */
26
+ export function removeFirstNItemsFromSet<T> (superSet: Set<T>, ineed: number): Set<T> {
27
+ return removeItemsFromSet(superSet, ineed, () => true)
28
+ }
29
+
30
+ export class MapDef<K, V> extends Map<K, V> {
31
+ constructor (private readonly getDefault: () => V) {
32
+ super()
33
+ }
34
+
35
+ getOrDefault (key: K): V {
36
+ let value = super.get(key)
37
+ if (value === undefined) {
38
+ value = this.getDefault()
39
+ this.set(key, value)
40
+ }
41
+ return value
42
+ }
43
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pseudo-randomly shuffles an array
3
+ *
4
+ * Mutates the input array
5
+ */
6
+ export function shuffle<T> (arr: T[]): T[] {
7
+ if (arr.length <= 1) {
8
+ return arr
9
+ }
10
+ const randInt = (): number => {
11
+ return Math.floor(Math.random() * Math.floor(arr.length))
12
+ }
13
+
14
+ for (let i = 0; i < arr.length; i++) {
15
+ const j = randInt()
16
+ const tmp = arr[i]
17
+ arr[i] = arr[j]
18
+ arr[j] = tmp
19
+ }
20
+ return arr
21
+ }
@@ -0,0 +1,71 @@
1
+ interface SimpleTimeCacheOpts {
2
+ validityMs: number
3
+ }
4
+
5
+ interface CacheValue<T> {
6
+ value: T
7
+ validUntilMs: number
8
+ }
9
+
10
+ /**
11
+ * This is similar to https://github.com/daviddias/time-cache/blob/master/src/index.js
12
+ * for our own need, we don't use lodash throttle to improve performance.
13
+ * This gives 4x - 5x performance gain compared to npm TimeCache
14
+ */
15
+ export class SimpleTimeCache<T> {
16
+ private readonly entries = new Map<string | number, CacheValue<T>>()
17
+ private readonly validityMs: number
18
+
19
+ constructor (opts: SimpleTimeCacheOpts) {
20
+ this.validityMs = opts.validityMs
21
+
22
+ // allow negative validityMs so that this does not cache anything, spec test compliance.spec.js
23
+ // sends duplicate messages and expect peer to receive all. Application likely uses positive validityMs
24
+ }
25
+
26
+ get size (): number {
27
+ return this.entries.size
28
+ }
29
+
30
+ /** Returns true if there was a key collision and the entry is dropped */
31
+ put (key: string | number, value: T): boolean {
32
+ if (this.entries.has(key)) {
33
+ // Key collisions break insertion order in the entries cache, which break prune logic.
34
+ // prune relies on each iterated entry to have strictly ascending validUntilMs, else it
35
+ // won't prune expired entries and SimpleTimeCache will grow unexpectedly.
36
+ // As of Oct 2022 NodeJS v16, inserting the same key twice with different value does not
37
+ // change the key position in the iterator stream. A unit test asserts this behaviour.
38
+ return true
39
+ }
40
+
41
+ this.entries.set(key, { value, validUntilMs: Date.now() + this.validityMs })
42
+ return false
43
+ }
44
+
45
+ prune (): void {
46
+ const now = Date.now()
47
+
48
+ for (const [k, v] of this.entries.entries()) {
49
+ if (v.validUntilMs < now) {
50
+ this.entries.delete(k)
51
+ } else {
52
+ // Entries are inserted with strictly ascending validUntilMs.
53
+ // Stop early to save iterations
54
+ break
55
+ }
56
+ }
57
+ }
58
+
59
+ has (key: string): boolean {
60
+ return this.entries.has(key)
61
+ }
62
+
63
+ get (key: string | number): T | undefined {
64
+ const value = this.entries.get(key)
65
+ return (value != null) && value.validUntilMs >= Date.now() ? value.value : undefined
66
+ }
67
+
68
+ clear (): void {
69
+ this.entries.clear()
70
+ }
71
+ }