@fluidframework/container-runtime 2.118.0 → 3.0.0

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 (326) hide show
  1. package/CHANGELOG.md +68 -4
  2. package/README.md +53 -50
  3. package/{api-extractor.json → api-extractor/api-extractor-model.json} +1 -1
  4. package/api-report/container-runtime.legacy.alpha.api.md +3 -1
  5. package/api-report/container-runtime.legacy.beta.api.md +3 -1
  6. package/container-runtime.test-files.tar +0 -0
  7. package/dist/batchTracker.d.ts.map +1 -1
  8. package/dist/batchTracker.js +5 -1
  9. package/dist/batchTracker.js.map +1 -1
  10. package/dist/blobManager/blobManager.d.ts +1 -1
  11. package/dist/blobManager/blobManager.d.ts.map +1 -1
  12. package/dist/blobManager/blobManager.js +264 -231
  13. package/dist/blobManager/blobManager.js.map +1 -1
  14. package/dist/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  15. package/dist/channelCollection.d.ts.map +1 -1
  16. package/dist/channelCollection.js +70 -58
  17. package/dist/channelCollection.js.map +1 -1
  18. package/dist/connectionTelemetry.js +38 -20
  19. package/dist/connectionTelemetry.js.map +1 -1
  20. package/dist/containerCompatibility.d.ts +3 -3
  21. package/dist/containerCompatibility.d.ts.map +1 -1
  22. package/dist/containerCompatibility.js +36 -45
  23. package/dist/containerCompatibility.js.map +1 -1
  24. package/dist/containerHandleContext.d.ts.map +1 -1
  25. package/dist/containerHandleContext.js +4 -0
  26. package/dist/containerHandleContext.js.map +1 -1
  27. package/dist/containerRuntime.d.ts +4 -4
  28. package/dist/containerRuntime.d.ts.map +1 -1
  29. package/dist/containerRuntime.js +346 -223
  30. package/dist/containerRuntime.js.map +1 -1
  31. package/dist/dataStore.d.ts.map +1 -1
  32. package/dist/dataStore.js +9 -1
  33. package/dist/dataStore.js.map +1 -1
  34. package/dist/dataStoreContext.d.ts.map +1 -1
  35. package/dist/dataStoreContext.js +184 -127
  36. package/dist/dataStoreContext.js.map +1 -1
  37. package/dist/dataStoreContexts.js +43 -42
  38. package/dist/dataStoreContexts.js.map +1 -1
  39. package/dist/dataStoreRegistry.js +1 -0
  40. package/dist/dataStoreRegistry.js.map +1 -1
  41. package/dist/deltaManagerProxies.js +5 -0
  42. package/dist/deltaManagerProxies.js.map +1 -1
  43. package/dist/deltaScheduler.js +104 -99
  44. package/dist/deltaScheduler.js.map +1 -1
  45. package/dist/error.js +2 -1
  46. package/dist/error.js.map +1 -1
  47. package/dist/gc/garbageCollection.js +85 -55
  48. package/dist/gc/garbageCollection.js.map +1 -1
  49. package/dist/gc/gcConfigs.js +2 -3
  50. package/dist/gc/gcConfigs.js.map +1 -1
  51. package/dist/gc/gcHelpers.js +12 -13
  52. package/dist/gc/gcHelpers.js.map +1 -1
  53. package/dist/gc/gcReferenceGraphAlgorithm.js +1 -2
  54. package/dist/gc/gcReferenceGraphAlgorithm.js.map +1 -1
  55. package/dist/gc/gcSummaryStateTracker.js +8 -3
  56. package/dist/gc/gcSummaryStateTracker.js.map +1 -1
  57. package/dist/gc/gcTelemetry.js +12 -5
  58. package/dist/gc/gcTelemetry.js.map +1 -1
  59. package/dist/gc/gcUnreferencedStateTracker.js +18 -1
  60. package/dist/gc/gcUnreferencedStateTracker.js.map +1 -1
  61. package/dist/inboundBatchAggregator.js +122 -117
  62. package/dist/inboundBatchAggregator.js.map +1 -1
  63. package/dist/metadata.d.ts.map +1 -1
  64. package/dist/metadata.js +3 -3
  65. package/dist/metadata.js.map +1 -1
  66. package/dist/opLifecycle/batchManager.d.ts.map +1 -1
  67. package/dist/opLifecycle/batchManager.js +11 -5
  68. package/dist/opLifecycle/batchManager.js.map +1 -1
  69. package/dist/opLifecycle/duplicateBatchDetector.js +18 -18
  70. package/dist/opLifecycle/duplicateBatchDetector.js.map +1 -1
  71. package/dist/opLifecycle/index.d.ts +2 -2
  72. package/dist/opLifecycle/index.d.ts.map +1 -1
  73. package/dist/opLifecycle/index.js +2 -1
  74. package/dist/opLifecycle/index.js.map +1 -1
  75. package/dist/opLifecycle/opCompressor.js +2 -1
  76. package/dist/opLifecycle/opCompressor.js.map +1 -1
  77. package/dist/opLifecycle/opDecompressor.js +10 -6
  78. package/dist/opLifecycle/opDecompressor.js.map +1 -1
  79. package/dist/opLifecycle/opGroupingManager.js +5 -3
  80. package/dist/opLifecycle/opGroupingManager.js.map +1 -1
  81. package/dist/opLifecycle/opSerialization.d.ts +11 -1
  82. package/dist/opLifecycle/opSerialization.d.ts.map +1 -1
  83. package/dist/opLifecycle/opSerialization.js +22 -6
  84. package/dist/opLifecycle/opSerialization.js.map +1 -1
  85. package/dist/opLifecycle/opSplitter.d.ts +17 -1
  86. package/dist/opLifecycle/opSplitter.d.ts.map +1 -1
  87. package/dist/opLifecycle/opSplitter.js +46 -9
  88. package/dist/opLifecycle/opSplitter.js.map +1 -1
  89. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  90. package/dist/opLifecycle/outbox.js +17 -13
  91. package/dist/opLifecycle/outbox.js.map +1 -1
  92. package/dist/opLifecycle/remoteMessageProcessor.js +6 -3
  93. package/dist/opLifecycle/remoteMessageProcessor.js.map +1 -1
  94. package/dist/opProperties.d.ts.map +1 -1
  95. package/dist/packageVersion.d.ts +1 -1
  96. package/dist/packageVersion.d.ts.map +1 -1
  97. package/dist/packageVersion.js +1 -1
  98. package/dist/packageVersion.js.map +1 -1
  99. package/dist/pendingStateManager.js +54 -47
  100. package/dist/pendingStateManager.js.map +1 -1
  101. package/dist/runCounter.js +10 -30
  102. package/dist/runCounter.js.map +1 -1
  103. package/dist/runtimeLayerCompatState.d.ts +1 -1
  104. package/dist/runtimeLayerCompatState.d.ts.map +1 -1
  105. package/dist/runtimeLayerCompatState.js +31 -11
  106. package/dist/runtimeLayerCompatState.js.map +1 -1
  107. package/dist/signalTelemetryProcessing.js +15 -17
  108. package/dist/signalTelemetryProcessing.js.map +1 -1
  109. package/dist/storageServiceWithAttachBlobs.js +2 -0
  110. package/dist/storageServiceWithAttachBlobs.js.map +1 -1
  111. package/dist/summary/documentSchema.d.ts.map +1 -1
  112. package/dist/summary/documentSchema.js +51 -13
  113. package/dist/summary/documentSchema.js.map +1 -1
  114. package/dist/summary/orderedClientElection.js +25 -17
  115. package/dist/summary/orderedClientElection.js.map +1 -1
  116. package/dist/summary/summarizerClientElection.d.ts.map +1 -1
  117. package/dist/summary/summarizerClientElection.js +18 -7
  118. package/dist/summary/summarizerClientElection.js.map +1 -1
  119. package/dist/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  120. package/dist/summary/summarizerNode/summarizerNode.js +22 -13
  121. package/dist/summary/summarizerNode/summarizerNode.js.map +1 -1
  122. package/dist/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  123. package/dist/summary/summarizerNode/summarizerNodeWithGc.js +20 -6
  124. package/dist/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  125. package/dist/summary/summarizerUtils.d.ts.map +1 -1
  126. package/dist/summary/summarizerUtils.js +4 -3
  127. package/dist/summary/summarizerUtils.js.map +1 -1
  128. package/dist/summary/summaryCollection.js +23 -11
  129. package/dist/summary/summaryCollection.js.map +1 -1
  130. package/dist/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js +4 -2
  131. package/dist/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js.map +1 -1
  132. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
  133. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.js +65 -46
  134. package/dist/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
  135. package/dist/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
  136. package/dist/summary/summaryDelayLoadedModule/summarizer.js +15 -6
  137. package/dist/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
  138. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.js +30 -25
  139. package/dist/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -1
  140. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.js +9 -0
  141. package/dist/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
  142. package/dist/summary/summaryDelayLoadedModule/summaryResultBuilder.js +3 -5
  143. package/dist/summary/summaryDelayLoadedModule/summaryResultBuilder.js.map +1 -1
  144. package/dist/summary/summaryFormat.d.ts.map +1 -1
  145. package/dist/summary/summaryFormat.js +7 -7
  146. package/dist/summary/summaryFormat.js.map +1 -1
  147. package/dist/summary/summaryHelpers.js +5 -5
  148. package/dist/summary/summaryHelpers.js.map +1 -1
  149. package/dist/summary/summaryManager.js +66 -54
  150. package/dist/summary/summaryManager.js.map +1 -1
  151. package/dist/throttler.d.ts.map +1 -1
  152. package/dist/throttler.js +4 -1
  153. package/dist/throttler.js.map +1 -1
  154. package/dist/versionMarks/inboundBatch.d.ts +8 -6
  155. package/dist/versionMarks/inboundBatch.d.ts.map +1 -1
  156. package/dist/versionMarks/inboundBatch.js +11 -7
  157. package/dist/versionMarks/inboundBatch.js.map +1 -1
  158. package/dist/versionMarks/versionMarkResolver.d.ts +30 -23
  159. package/dist/versionMarks/versionMarkResolver.d.ts.map +1 -1
  160. package/dist/versionMarks/versionMarkResolver.js +112 -63
  161. package/dist/versionMarks/versionMarkResolver.js.map +1 -1
  162. package/lib/batchTracker.d.ts.map +1 -1
  163. package/lib/batchTracker.js +5 -1
  164. package/lib/batchTracker.js.map +1 -1
  165. package/lib/blobManager/blobManager.d.ts +1 -1
  166. package/lib/blobManager/blobManager.d.ts.map +1 -1
  167. package/lib/blobManager/blobManager.js +264 -231
  168. package/lib/blobManager/blobManager.js.map +1 -1
  169. package/lib/blobManager/blobManagerSnapSum.d.ts.map +1 -1
  170. package/lib/channelCollection.d.ts.map +1 -1
  171. package/lib/channelCollection.js +65 -53
  172. package/lib/channelCollection.js.map +1 -1
  173. package/lib/connectionTelemetry.js +36 -18
  174. package/lib/connectionTelemetry.js.map +1 -1
  175. package/lib/containerCompatibility.d.ts +3 -3
  176. package/lib/containerCompatibility.d.ts.map +1 -1
  177. package/lib/containerCompatibility.js +33 -42
  178. package/lib/containerCompatibility.js.map +1 -1
  179. package/lib/containerHandleContext.d.ts.map +1 -1
  180. package/lib/containerHandleContext.js +4 -0
  181. package/lib/containerHandleContext.js.map +1 -1
  182. package/lib/containerRuntime.d.ts +4 -4
  183. package/lib/containerRuntime.d.ts.map +1 -1
  184. package/lib/containerRuntime.js +341 -218
  185. package/lib/containerRuntime.js.map +1 -1
  186. package/lib/dataStore.d.ts.map +1 -1
  187. package/lib/dataStore.js +9 -1
  188. package/lib/dataStore.js.map +1 -1
  189. package/lib/dataStoreContext.d.ts.map +1 -1
  190. package/lib/dataStoreContext.js +182 -125
  191. package/lib/dataStoreContext.js.map +1 -1
  192. package/lib/dataStoreContexts.js +43 -42
  193. package/lib/dataStoreContexts.js.map +1 -1
  194. package/lib/dataStoreRegistry.js +1 -0
  195. package/lib/dataStoreRegistry.js.map +1 -1
  196. package/lib/deltaManagerProxies.js +5 -0
  197. package/lib/deltaManagerProxies.js.map +1 -1
  198. package/lib/deltaScheduler.js +104 -99
  199. package/lib/deltaScheduler.js.map +1 -1
  200. package/lib/error.js +2 -1
  201. package/lib/error.js.map +1 -1
  202. package/lib/gc/garbageCollection.js +85 -55
  203. package/lib/gc/garbageCollection.js.map +1 -1
  204. package/lib/gc/gcSummaryStateTracker.js +8 -3
  205. package/lib/gc/gcSummaryStateTracker.js.map +1 -1
  206. package/lib/gc/gcTelemetry.js +12 -5
  207. package/lib/gc/gcTelemetry.js.map +1 -1
  208. package/lib/gc/gcUnreferencedStateTracker.js +18 -1
  209. package/lib/gc/gcUnreferencedStateTracker.js.map +1 -1
  210. package/lib/inboundBatchAggregator.js +122 -117
  211. package/lib/inboundBatchAggregator.js.map +1 -1
  212. package/lib/legacy.d.ts +1 -1
  213. package/lib/legacyAlpha.d.ts +1 -1
  214. package/lib/metadata.d.ts.map +1 -1
  215. package/lib/opLifecycle/batchManager.d.ts.map +1 -1
  216. package/lib/opLifecycle/batchManager.js +8 -2
  217. package/lib/opLifecycle/batchManager.js.map +1 -1
  218. package/lib/opLifecycle/duplicateBatchDetector.js +18 -18
  219. package/lib/opLifecycle/duplicateBatchDetector.js.map +1 -1
  220. package/lib/opLifecycle/index.d.ts +2 -2
  221. package/lib/opLifecycle/index.d.ts.map +1 -1
  222. package/lib/opLifecycle/index.js +2 -2
  223. package/lib/opLifecycle/index.js.map +1 -1
  224. package/lib/opLifecycle/opCompressor.js +3 -2
  225. package/lib/opLifecycle/opCompressor.js.map +1 -1
  226. package/lib/opLifecycle/opDecompressor.js +10 -6
  227. package/lib/opLifecycle/opDecompressor.js.map +1 -1
  228. package/lib/opLifecycle/opGroupingManager.js +3 -1
  229. package/lib/opLifecycle/opGroupingManager.js.map +1 -1
  230. package/lib/opLifecycle/opSerialization.d.ts +11 -1
  231. package/lib/opLifecycle/opSerialization.d.ts.map +1 -1
  232. package/lib/opLifecycle/opSerialization.js +16 -0
  233. package/lib/opLifecycle/opSerialization.js.map +1 -1
  234. package/lib/opLifecycle/opSplitter.d.ts +17 -1
  235. package/lib/opLifecycle/opSplitter.d.ts.map +1 -1
  236. package/lib/opLifecycle/opSplitter.js +44 -7
  237. package/lib/opLifecycle/opSplitter.js.map +1 -1
  238. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  239. package/lib/opLifecycle/outbox.js +14 -10
  240. package/lib/opLifecycle/outbox.js.map +1 -1
  241. package/lib/opLifecycle/remoteMessageProcessor.js +4 -1
  242. package/lib/opLifecycle/remoteMessageProcessor.js.map +1 -1
  243. package/lib/opProperties.d.ts.map +1 -1
  244. package/lib/packageVersion.d.ts +1 -1
  245. package/lib/packageVersion.d.ts.map +1 -1
  246. package/lib/packageVersion.js +1 -1
  247. package/lib/packageVersion.js.map +1 -1
  248. package/lib/pendingStateManager.js +52 -45
  249. package/lib/pendingStateManager.js.map +1 -1
  250. package/lib/public.d.ts +1 -1
  251. package/lib/runCounter.js +10 -30
  252. package/lib/runCounter.js.map +1 -1
  253. package/lib/runtimeLayerCompatState.d.ts +1 -1
  254. package/lib/runtimeLayerCompatState.d.ts.map +1 -1
  255. package/lib/runtimeLayerCompatState.js +28 -8
  256. package/lib/runtimeLayerCompatState.js.map +1 -1
  257. package/lib/signalTelemetryProcessing.js +15 -17
  258. package/lib/signalTelemetryProcessing.js.map +1 -1
  259. package/lib/storageServiceWithAttachBlobs.js +2 -0
  260. package/lib/storageServiceWithAttachBlobs.js.map +1 -1
  261. package/lib/summary/documentSchema.d.ts.map +1 -1
  262. package/lib/summary/documentSchema.js +51 -13
  263. package/lib/summary/documentSchema.js.map +1 -1
  264. package/lib/summary/orderedClientElection.js +25 -17
  265. package/lib/summary/orderedClientElection.js.map +1 -1
  266. package/lib/summary/summarizerClientElection.d.ts.map +1 -1
  267. package/lib/summary/summarizerClientElection.js +18 -7
  268. package/lib/summary/summarizerClientElection.js.map +1 -1
  269. package/lib/summary/summarizerNode/summarizerNode.d.ts.map +1 -1
  270. package/lib/summary/summarizerNode/summarizerNode.js +22 -13
  271. package/lib/summary/summarizerNode/summarizerNode.js.map +1 -1
  272. package/lib/summary/summarizerNode/summarizerNodeWithGc.d.ts.map +1 -1
  273. package/lib/summary/summarizerNode/summarizerNodeWithGc.js +20 -6
  274. package/lib/summary/summarizerNode/summarizerNodeWithGc.js.map +1 -1
  275. package/lib/summary/summarizerUtils.d.ts.map +1 -1
  276. package/lib/summary/summarizerUtils.js +1 -0
  277. package/lib/summary/summarizerUtils.js.map +1 -1
  278. package/lib/summary/summaryCollection.js +23 -11
  279. package/lib/summary/summaryCollection.js.map +1 -1
  280. package/lib/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js +4 -2
  281. package/lib/summary/summaryDelayLoadedModule/runWhileConnectedCoordinator.js.map +1 -1
  282. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.d.ts.map +1 -1
  283. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.js +65 -46
  284. package/lib/summary/summaryDelayLoadedModule/runningSummarizer.js.map +1 -1
  285. package/lib/summary/summaryDelayLoadedModule/summarizer.d.ts.map +1 -1
  286. package/lib/summary/summaryDelayLoadedModule/summarizer.js +15 -6
  287. package/lib/summary/summaryDelayLoadedModule/summarizer.js.map +1 -1
  288. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.js +30 -25
  289. package/lib/summary/summaryDelayLoadedModule/summarizerHeuristics.js.map +1 -1
  290. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.js +9 -0
  291. package/lib/summary/summaryDelayLoadedModule/summaryGenerator.js.map +1 -1
  292. package/lib/summary/summaryDelayLoadedModule/summaryResultBuilder.js +3 -5
  293. package/lib/summary/summaryDelayLoadedModule/summaryResultBuilder.js.map +1 -1
  294. package/lib/summary/summaryFormat.d.ts.map +1 -1
  295. package/lib/summary/summaryManager.js +66 -54
  296. package/lib/summary/summaryManager.js.map +1 -1
  297. package/lib/throttler.d.ts.map +1 -1
  298. package/lib/throttler.js +4 -1
  299. package/lib/throttler.js.map +1 -1
  300. package/lib/versionMarks/inboundBatch.d.ts +8 -6
  301. package/lib/versionMarks/inboundBatch.d.ts.map +1 -1
  302. package/lib/versionMarks/inboundBatch.js +10 -5
  303. package/lib/versionMarks/inboundBatch.js.map +1 -1
  304. package/lib/versionMarks/versionMarkResolver.d.ts +30 -23
  305. package/lib/versionMarks/versionMarkResolver.d.ts.map +1 -1
  306. package/lib/versionMarks/versionMarkResolver.js +112 -63
  307. package/lib/versionMarks/versionMarkResolver.js.map +1 -1
  308. package/package.json +60 -102
  309. package/src/containerCompatibility.ts +33 -42
  310. package/src/containerRuntime.ts +15 -12
  311. package/src/opLifecycle/index.ts +7 -1
  312. package/src/opLifecycle/opCompressor.ts +2 -2
  313. package/src/opLifecycle/opSerialization.ts +22 -1
  314. package/src/opLifecycle/opSplitter.ts +63 -6
  315. package/src/packageVersion.ts +1 -1
  316. package/src/runtimeLayerCompatState.ts +28 -21
  317. package/src/summary/documentSchema.ts +31 -6
  318. package/src/versionMarks/DEV.md +595 -157
  319. package/src/versionMarks/inboundBatch.ts +18 -8
  320. package/src/versionMarks/versionMarkResolver.ts +144 -75
  321. package/tsconfig.json +1 -1
  322. package/internal.d.ts +0 -11
  323. package/legacy/alpha.d.ts +0 -11
  324. package/legacy.d.ts +0 -11
  325. /package/api-extractor/{api-extractor.current.json → api-extractor-report.current.json} +0 -0
  326. /package/api-extractor/{api-extractor.legacy.json → api-extractor-report.legacy.json} +0 -0
package/lib/throttler.js CHANGED
@@ -7,6 +7,10 @@
7
7
  * Delay is based on previous attempts within specified time window, subtracting delay time.
8
8
  */
9
9
  export class Throttler {
10
+ delayWindowMs;
11
+ maxDelayMs;
12
+ delayFn;
13
+ startTimes = [];
10
14
  get numAttempts() {
11
15
  return this.startTimes.length;
12
16
  }
@@ -44,7 +48,6 @@ export class Throttler {
44
48
  this.delayWindowMs = delayWindowMs;
45
49
  this.maxDelayMs = maxDelayMs;
46
50
  this.delayFn = delayFn;
47
- this.startTimes = [];
48
51
  }
49
52
  getDelay() {
50
53
  const now = Date.now();
@@ -1 +1 @@
1
- {"version":3,"file":"throttler.js","sourceRoot":"","sources":["../src/throttler.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport interface IThrottler {\n\t/**\n\t * Computes what the throttle delay should be, and records an attempt\n\t * which will be used for calculating future attempt delays.\n\t */\n\tgetDelay(): number;\n\n\t/**\n\t * Number of attempts that occurred within the sliding window as of\n\t * the most recent delay computation.\n\t */\n\treadonly numAttempts: number;\n\n\t/**\n\t * Width of sliding delay window in milliseconds.\n\t */\n\treadonly delayWindowMs: number;\n\t/**\n\t * Maximum delay allowed in milliseconds.\n\t */\n\treadonly maxDelayMs: number;\n\t/**\n\t * Delay function used to calculate what the delay should be.\n\t * The input is the number of attempts that occurred within the sliding window.\n\t * The result is the calculated delay in milliseconds.\n\t */\n\treadonly delayFn: (numAttempts: number) => number;\n}\n\n/**\n * Used to give increasing delay times for throttling a single functionality.\n * Delay is based on previous attempts within specified time window, subtracting delay time.\n */\nexport class Throttler implements IThrottler {\n\tprivate startTimes: number[] = [];\n\n\tpublic get numAttempts(): number {\n\t\treturn this.startTimes.length;\n\t}\n\n\t/**\n\t * Gets all attempt start times after compensating for the delay times\n\t * by adding the delay times to the actual times.\n\t */\n\tpublic getAttempts(): readonly number[] {\n\t\treturn [...this.startTimes];\n\t}\n\n\t/**\n\t * Latest attempt time after compensating for the delay time itself\n\t * by adding the delay time to the actual time.\n\t */\n\tpublic get latestAttemptTime(): number | undefined {\n\t\treturn this.startTimes.length > 0\n\t\t\t? this.startTimes[this.startTimes.length - 1]\n\t\t\t: undefined;\n\t}\n\n\tconstructor(\n\t\t/**\n\t\t * Width of sliding delay window in milliseconds.\n\t\t */\n\t\tpublic readonly delayWindowMs: number,\n\t\t/**\n\t\t * Maximum delay allowed in milliseconds.\n\t\t */\n\t\tpublic readonly maxDelayMs: number,\n\t\t/**\n\t\t * Delay function used to calculate what the delay should be.\n\t\t * The input is the number of attempts that occurred within the sliding window.\n\t\t * The result is the calculated delay in milliseconds.\n\t\t */\n\t\tpublic readonly delayFn: (numAttempts: number) => number,\n\t) {}\n\n\tpublic getDelay(): number {\n\t\tconst now = Date.now();\n\n\t\tconst latestAttemptTime = this.latestAttemptTime;\n\t\tif (latestAttemptTime !== undefined) {\n\t\t\t// If getDelay was called sooner than the most recent delay,\n\t\t\t// subtract the remaining time, since we previously added it.\n\t\t\tconst earlyMs = latestAttemptTime - now;\n\t\t\tif (earlyMs > 0) {\n\t\t\t\tthis.startTimes = this.startTimes.map((t) => t - earlyMs);\n\t\t\t}\n\t\t}\n\n\t\t// Remove all attempts that have already fallen out of the window.\n\t\tthis.startTimes = this.startTimes.filter((t) => now - t < this.delayWindowMs);\n\n\t\t// Compute delay, but do not exceed the specified max delay.\n\t\tconst delayMs = Math.min(this.delayFn(this.startTimes.length), this.maxDelayMs);\n\n\t\t// Record this attempt start time.\n\t\tthis.startTimes.push(now);\n\n\t\t// Account for the delay time, by effectively removing it from the delay window.\n\t\tthis.startTimes = this.startTimes.map((t) => t + delayMs);\n\n\t\tif (delayMs === this.maxDelayMs) {\n\t\t\t// We hit max delay, so adding more won't affect anything.\n\t\t\t// Shift off oldest time to stop this array from growing forever.\n\t\t\tthis.startTimes.shift();\n\t\t}\n\n\t\treturn delayMs;\n\t}\n}\n\n/**\n * Helper function to generate simple exponential throttle functions.\n * f(n) = [coefficient] x ([multiplier]^n) + [flatOffset]\n * where n = number of attempts, and f(n) = delay time in milliseconds.\n * If not provided, coefficient will default to 1, multiplier to 2,\n * minimum delay to 0, and the offset to 0, yielding:\n * 0 ms, 2 ms, 4 ms, 8 ms, ..., 2^n ms\n * where M = multiplier; an exponential back-off.\n * Use initialDelay to decide what should happen when numAttempts is 0,\n * leave it undefined to not special case.\n */\nexport const formExponentialFn =\n\t({\n\t\tmultiplier = 2,\n\t\tcoefficient = 1,\n\t\toffset = 0,\n\t\tinitialDelay = undefined as number | undefined,\n\t} = {}): IThrottler[\"delayFn\"] =>\n\t(numAttempts) =>\n\t\tMath.max(\n\t\t\t0,\n\t\t\tnumAttempts <= 0 && initialDelay !== undefined\n\t\t\t\t? initialDelay\n\t\t\t\t: coefficient * Math.pow(multiplier, numAttempts) + offset,\n\t\t);\n\n/**\n * f(n) = C x (B^(n+A)) + F = (C x B^A) x B^n + F\n */\nexport const formExponentialFnWithAttemptOffset = (\n\tattemptOffset: number,\n\t{\n\t\tmultiplier = 2,\n\t\tcoefficient = 1,\n\t\toffset = 0,\n\t\tinitialDelay = undefined as number | undefined,\n\t} = {},\n): IThrottler[\"delayFn\"] =>\n\tformExponentialFn({\n\t\tmultiplier,\n\t\tcoefficient: coefficient * Math.pow(multiplier, attemptOffset),\n\t\toffset,\n\t\tinitialDelay,\n\t});\n\n/**\n * Helper function to generate simple linear throttle functions.\n * f(n) = [coefficient] x n + [flatOffset]\n * where n = number of attempts, and f(n) = delay time in milliseconds.\n * If not provided, coefficient will default to 1, and offset to 0, yielding:\n * 0 ms, 1 ms, 2 ms, 3 ms, ..., n ms delays; a linear back-off.\n */\nexport const formLinearFn =\n\t({ coefficient = 1, offset = 0 } = {}): IThrottler[\"delayFn\"] =>\n\t(numAttempts) =>\n\t\tMath.max(0, coefficient * numAttempts + offset);\n\n/**\n * f(n) = C x (n+A) + F = C x n + (C x A + F)\n */\nexport const formLinearFnWithAttemptOffset = (\n\tattemptOffset: number,\n\t{ coefficient = 1, offset = 0 } = {},\n): IThrottler[\"delayFn\"] =>\n\tformLinearFn({\n\t\tcoefficient,\n\t\toffset: coefficient * attemptOffset + offset,\n\t});\n"]}
1
+ {"version":3,"file":"throttler.js","sourceRoot":"","sources":["../src/throttler.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nexport interface IThrottler {\n\t/**\n\t * Computes what the throttle delay should be, and records an attempt\n\t * which will be used for calculating future attempt delays.\n\t */\n\tgetDelay(): number;\n\n\t/**\n\t * Number of attempts that occurred within the sliding window as of\n\t * the most recent delay computation.\n\t */\n\treadonly numAttempts: number;\n\n\t/**\n\t * Width of sliding delay window in milliseconds.\n\t */\n\treadonly delayWindowMs: number;\n\t/**\n\t * Maximum delay allowed in milliseconds.\n\t */\n\treadonly maxDelayMs: number;\n\t/**\n\t * Delay function used to calculate what the delay should be.\n\t * The input is the number of attempts that occurred within the sliding window.\n\t * The result is the calculated delay in milliseconds.\n\t */\n\treadonly delayFn: (numAttempts: number) => number;\n}\n\n/**\n * Used to give increasing delay times for throttling a single functionality.\n * Delay is based on previous attempts within specified time window, subtracting delay time.\n */\nexport class Throttler implements IThrottler {\n\tprivate startTimes: number[] = [];\n\n\tpublic get numAttempts(): number {\n\t\treturn this.startTimes.length;\n\t}\n\n\t/**\n\t * Gets all attempt start times after compensating for the delay times\n\t * by adding the delay times to the actual times.\n\t */\n\tpublic getAttempts(): readonly number[] {\n\t\treturn [...this.startTimes];\n\t}\n\n\t/**\n\t * Latest attempt time after compensating for the delay time itself\n\t * by adding the delay time to the actual time.\n\t */\n\tpublic get latestAttemptTime(): number | undefined {\n\t\treturn this.startTimes.length > 0\n\t\t\t? this.startTimes[this.startTimes.length - 1]\n\t\t\t: undefined;\n\t}\n\n\tconstructor(\n\t\t/**\n\t\t * Width of sliding delay window in milliseconds.\n\t\t */\n\t\tpublic readonly delayWindowMs: number,\n\t\t/**\n\t\t * Maximum delay allowed in milliseconds.\n\t\t */\n\t\tpublic readonly maxDelayMs: number,\n\t\t/**\n\t\t * Delay function used to calculate what the delay should be.\n\t\t * The input is the number of attempts that occurred within the sliding window.\n\t\t * The result is the calculated delay in milliseconds.\n\t\t */\n\t\tpublic readonly delayFn: (numAttempts: number) => number,\n\t) {}\n\n\tpublic getDelay(): number {\n\t\tconst now = Date.now();\n\n\t\tconst latestAttemptTime = this.latestAttemptTime;\n\t\tif (latestAttemptTime !== undefined) {\n\t\t\t// If getDelay was called sooner than the most recent delay,\n\t\t\t// subtract the remaining time, since we previously added it.\n\t\t\tconst earlyMs = latestAttemptTime - now;\n\t\t\tif (earlyMs > 0) {\n\t\t\t\tthis.startTimes = this.startTimes.map((t) => t - earlyMs);\n\t\t\t}\n\t\t}\n\n\t\t// Remove all attempts that have already fallen out of the window.\n\t\tthis.startTimes = this.startTimes.filter((t) => now - t < this.delayWindowMs);\n\n\t\t// Compute delay, but do not exceed the specified max delay.\n\t\tconst delayMs = Math.min(this.delayFn(this.startTimes.length), this.maxDelayMs);\n\n\t\t// Record this attempt start time.\n\t\tthis.startTimes.push(now);\n\n\t\t// Account for the delay time, by effectively removing it from the delay window.\n\t\tthis.startTimes = this.startTimes.map((t) => t + delayMs);\n\n\t\tif (delayMs === this.maxDelayMs) {\n\t\t\t// We hit max delay, so adding more won't affect anything.\n\t\t\t// Shift off oldest time to stop this array from growing forever.\n\t\t\tthis.startTimes.shift();\n\t\t}\n\n\t\treturn delayMs;\n\t}\n}\n\n/**\n * Helper function to generate simple exponential throttle functions.\n * f(n) = [coefficient] x ([multiplier]^n) + [flatOffset]\n * where n = number of attempts, and f(n) = delay time in milliseconds.\n * If not provided, coefficient will default to 1, multiplier to 2,\n * minimum delay to 0, and the offset to 0, yielding:\n * 0 ms, 2 ms, 4 ms, 8 ms, ..., 2^n ms\n * where M = multiplier; an exponential back-off.\n * Use initialDelay to decide what should happen when numAttempts is 0,\n * leave it undefined to not special case.\n */\nexport const formExponentialFn =\n\t({\n\t\tmultiplier = 2,\n\t\tcoefficient = 1,\n\t\toffset = 0,\n\t\tinitialDelay = undefined as number | undefined,\n\t} = {}): IThrottler[\"delayFn\"] =>\n\t(numAttempts) =>\n\t\tMath.max(\n\t\t\t0,\n\t\t\tnumAttempts <= 0 && initialDelay !== undefined\n\t\t\t\t? initialDelay\n\t\t\t\t: coefficient * Math.pow(multiplier, numAttempts) + offset,\n\t\t);\n\n/**\n * f(n) = C x (B^(n+A)) + F = (C x B^A) x B^n + F\n */\nexport const formExponentialFnWithAttemptOffset = (\n\tattemptOffset: number,\n\t{\n\t\tmultiplier = 2,\n\t\tcoefficient = 1,\n\t\toffset = 0,\n\t\tinitialDelay = undefined as number | undefined,\n\t} = {},\n): IThrottler[\"delayFn\"] =>\n\tformExponentialFn({\n\t\tmultiplier,\n\t\tcoefficient: coefficient * Math.pow(multiplier, attemptOffset),\n\t\toffset,\n\t\tinitialDelay,\n\t});\n\n/**\n * Helper function to generate simple linear throttle functions.\n * f(n) = [coefficient] x n + [flatOffset]\n * where n = number of attempts, and f(n) = delay time in milliseconds.\n * If not provided, coefficient will default to 1, and offset to 0, yielding:\n * 0 ms, 1 ms, 2 ms, 3 ms, ..., n ms delays; a linear back-off.\n */\nexport const formLinearFn =\n\t({ coefficient = 1, offset = 0 } = {}): IThrottler[\"delayFn\"] =>\n\t(numAttempts) =>\n\t\tMath.max(0, coefficient * numAttempts + offset);\n\n/**\n * f(n) = C x (n+A) + F = C x n + (C x A + F)\n */\nexport const formLinearFnWithAttemptOffset = (\n\tattemptOffset: number,\n\t{ coefficient = 1, offset = 0 } = {},\n): IThrottler[\"delayFn\"] =>\n\tformLinearFn({\n\t\tcoefficient,\n\t\toffset: coefficient * attemptOffset + offset,\n\t});\n"]}
@@ -10,12 +10,14 @@ import { type InboundMessageResult } from "../opLifecycle/index.js";
10
10
  */
11
11
  export interface InboundVersionMarkUpdate {
12
12
  /**
13
- * A completed batch to record via `processInboundBatch`: its id and its last op's sequence number.
14
- * Undefined when no batch completed on this message (a piecemeal batch still in progress).
13
+ * A completed batch to record via `processInboundBatch`: its id and its last op's sequence
14
+ * number and timestamp. Undefined when no batch completed on this message (a piecemeal batch
15
+ * still in progress).
15
16
  */
16
17
  readonly sequenced?: {
17
18
  readonly batchId: string;
18
19
  readonly sequenceNumber: number;
20
+ readonly timestamp: number;
19
21
  };
20
22
  /**
21
23
  * The batch id to carry for a piecemeal batch still in progress, or undefined when none is pending.
@@ -25,10 +27,10 @@ export interface InboundVersionMarkUpdate {
25
27
  }
26
28
  /**
27
29
  * Maps an inbound batch result to the version-mark resolver update. A batch's resolved point is its last
28
- * op's sequence number, so this reports `(batchId, sequenceNumber)` only once a batch completes. It handles
29
- * both a batch delivered whole (`fullBatch`, incl. an empty grouped batch via the batch-start key message)
30
- * and one delivered piecemeal (`batchStartingMessage` then `nextBatchMessage` with `batchEnd`), carrying the
31
- * batch id across the piecemeal messages via `carriedBatchId`.
30
+ * op's sequence number and timestamp, so this reports `(batchId, sequenceNumber, timestamp)` only once a
31
+ * batch completes. It handles both a batch delivered whole (`fullBatch`, incl. an empty grouped batch via
32
+ * the batch-start key message) and one delivered piecemeal (`batchStartingMessage` then `nextBatchMessage`
33
+ * with `batchEnd`), carrying the batch id across the piecemeal messages via `carriedBatchId`.
32
34
  *
33
35
  * @internal
34
36
  */
@@ -1 +1 @@
1
- {"version":3,"file":"inboundBatch.d.ts","sourceRoot":"","sources":["../../src/versionMarks/inboundBatch.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAEzF;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACxC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC;IACnF;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5C;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACvC,aAAa,EAAE,oBAAoB,EACnC,cAAc,EAAE,MAAM,GAAG,SAAS,GAChC,wBAAwB,CAgC1B"}
1
+ {"version":3,"file":"inboundBatch.d.ts","sourceRoot":"","sources":["../../src/versionMarks/inboundBatch.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAuB,KAAK,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAEzF;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACxC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE;QACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;QAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC3B,CAAC;IACF;;;OAGG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5C;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACvC,aAAa,EAAE,oBAAoB,EACnC,cAAc,EAAE,MAAM,GAAG,SAAS,GAChC,wBAAwB,CAqC1B"}
@@ -5,10 +5,10 @@
5
5
  import { getEffectiveBatchId } from "../opLifecycle/index.js";
6
6
  /**
7
7
  * Maps an inbound batch result to the version-mark resolver update. A batch's resolved point is its last
8
- * op's sequence number, so this reports `(batchId, sequenceNumber)` only once a batch completes. It handles
9
- * both a batch delivered whole (`fullBatch`, incl. an empty grouped batch via the batch-start key message)
10
- * and one delivered piecemeal (`batchStartingMessage` then `nextBatchMessage` with `batchEnd`), carrying the
11
- * batch id across the piecemeal messages via `carriedBatchId`.
8
+ * op's sequence number and timestamp, so this reports `(batchId, sequenceNumber, timestamp)` only once a
9
+ * batch completes. It handles both a batch delivered whole (`fullBatch`, incl. an empty grouped batch via
10
+ * the batch-start key message) and one delivered piecemeal (`batchStartingMessage` then `nextBatchMessage`
11
+ * with `batchEnd`), carrying the batch id across the piecemeal messages via `carriedBatchId`.
12
12
  *
13
13
  * @internal
14
14
  */
@@ -22,7 +22,11 @@ export function inboundVersionMarkUpdate(inboundResult, carriedBatchId) {
22
22
  inboundResult.messages[inboundResult.messages.length - 1] ??
23
23
  inboundResult.batchStart.keyMessage;
24
24
  return {
25
- sequenced: { batchId, sequenceNumber: lastMessage.sequenceNumber },
25
+ sequenced: {
26
+ batchId,
27
+ sequenceNumber: lastMessage.sequenceNumber,
28
+ timestamp: lastMessage.timestamp,
29
+ },
26
30
  carriedBatchId: undefined,
27
31
  };
28
32
  }
@@ -36,6 +40,7 @@ export function inboundVersionMarkUpdate(inboundResult, carriedBatchId) {
36
40
  sequenced: {
37
41
  batchId: carriedBatchId,
38
42
  sequenceNumber: inboundResult.nextMessage.sequenceNumber,
43
+ timestamp: inboundResult.nextMessage.timestamp,
39
44
  },
40
45
  carriedBatchId: undefined,
41
46
  };
@@ -1 +1 @@
1
- {"version":3,"file":"inboundBatch.js","sourceRoot":"","sources":["../../src/versionMarks/inboundBatch.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAA6B,MAAM,yBAAyB,CAAC;AAoBzF;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CACvC,aAAmC,EACnC,cAAkC;IAElC,IAAI,YAAY,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,mBAAmB,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QAC9D,IAAI,aAAa,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACxC,uGAAuG;YACvG,MAAM,WAAW;YAChB,uGAAuG;YACvG,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;gBACzD,aAAa,CAAC,UAAU,CAAC,UAAU,CAAC;YACrC,OAAO;gBACN,SAAS,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,WAAW,CAAC,cAAc,EAAE;gBAClE,cAAc,EAAE,SAAS;aACzB,CAAC;QACH,CAAC;QACD,4EAA4E;QAC5E,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;IACpC,CAAC;IACD,IACC,aAAa,CAAC,IAAI,KAAK,kBAAkB;QACzC,aAAa,CAAC,QAAQ,KAAK,IAAI;QAC/B,cAAc,KAAK,SAAS,EAC3B,CAAC;QACF,OAAO;YACN,SAAS,EAAE;gBACV,OAAO,EAAE,cAAc;gBACvB,cAAc,EAAE,aAAa,CAAC,WAAW,CAAC,cAAc;aACxD;YACD,cAAc,EAAE,SAAS;SACzB,CAAC;IACH,CAAC;IACD,qEAAqE;IACrE,OAAO,EAAE,cAAc,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { getEffectiveBatchId, type InboundMessageResult } from \"../opLifecycle/index.js\";\n\n/**\n * The version-mark resolver update derived from an inbound batch result.\n *\n * @internal\n */\nexport interface InboundVersionMarkUpdate {\n\t/**\n\t * A completed batch to record via `processInboundBatch`: its id and its last op's sequence number.\n\t * Undefined when no batch completed on this message (a piecemeal batch still in progress).\n\t */\n\treadonly sequenced?: { readonly batchId: string; readonly sequenceNumber: number };\n\t/**\n\t * The batch id to carry for a piecemeal batch still in progress, or undefined when none is pending.\n\t * The caller stores this and passes it back on the next inbound result.\n\t */\n\treadonly carriedBatchId: string | undefined;\n}\n\n/**\n * Maps an inbound batch result to the version-mark resolver update. A batch's resolved point is its last\n * op's sequence number, so this reports `(batchId, sequenceNumber)` only once a batch completes. It handles\n * both a batch delivered whole (`fullBatch`, incl. an empty grouped batch via the batch-start key message)\n * and one delivered piecemeal (`batchStartingMessage` then `nextBatchMessage` with `batchEnd`), carrying the\n * batch id across the piecemeal messages via `carriedBatchId`.\n *\n * @internal\n */\nexport function inboundVersionMarkUpdate(\n\tinboundResult: InboundMessageResult,\n\tcarriedBatchId: string | undefined,\n): InboundVersionMarkUpdate {\n\tif (\"batchStart\" in inboundResult) {\n\t\tconst batchId = getEffectiveBatchId(inboundResult.batchStart);\n\t\tif (inboundResult.type === \"fullBatch\") {\n\t\t\t// The last op is the batch's resolved point; an empty batch falls back to the batch-start key message.\n\t\t\tconst lastMessage =\n\t\t\t\t// eslint-disable-next-line unicorn/prefer-at -- Array.prototype.at is not in this package's lib target\n\t\t\t\tinboundResult.messages[inboundResult.messages.length - 1] ??\n\t\t\t\tinboundResult.batchStart.keyMessage;\n\t\t\treturn {\n\t\t\t\tsequenced: { batchId, sequenceNumber: lastMessage.sequenceNumber },\n\t\t\t\tcarriedBatchId: undefined,\n\t\t\t};\n\t\t}\n\t\t// batchStartingMessage: carry the id until the batch's end message arrives.\n\t\treturn { carriedBatchId: batchId };\n\t}\n\tif (\n\t\tinboundResult.type === \"nextBatchMessage\" &&\n\t\tinboundResult.batchEnd === true &&\n\t\tcarriedBatchId !== undefined\n\t) {\n\t\treturn {\n\t\t\tsequenced: {\n\t\t\t\tbatchId: carriedBatchId,\n\t\t\t\tsequenceNumber: inboundResult.nextMessage.sequenceNumber,\n\t\t\t},\n\t\t\tcarriedBatchId: undefined,\n\t\t};\n\t}\n\t// A mid-batch message, or a batch end with no carried id: no change.\n\treturn { carriedBatchId };\n}\n"]}
1
+ {"version":3,"file":"inboundBatch.js","sourceRoot":"","sources":["../../src/versionMarks/inboundBatch.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAA6B,MAAM,yBAAyB,CAAC;AAyBzF;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CACvC,aAAmC,EACnC,cAAkC;IAElC,IAAI,YAAY,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,mBAAmB,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QAC9D,IAAI,aAAa,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACxC,uGAAuG;YACvG,MAAM,WAAW;YAChB,uGAAuG;YACvG,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;gBACzD,aAAa,CAAC,UAAU,CAAC,UAAU,CAAC;YACrC,OAAO;gBACN,SAAS,EAAE;oBACV,OAAO;oBACP,cAAc,EAAE,WAAW,CAAC,cAAc;oBAC1C,SAAS,EAAE,WAAW,CAAC,SAAS;iBAChC;gBACD,cAAc,EAAE,SAAS;aACzB,CAAC;QACH,CAAC;QACD,4EAA4E;QAC5E,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC;IACpC,CAAC;IACD,IACC,aAAa,CAAC,IAAI,KAAK,kBAAkB;QACzC,aAAa,CAAC,QAAQ,KAAK,IAAI;QAC/B,cAAc,KAAK,SAAS,EAC3B,CAAC;QACF,OAAO;YACN,SAAS,EAAE;gBACV,OAAO,EAAE,cAAc;gBACvB,cAAc,EAAE,aAAa,CAAC,WAAW,CAAC,cAAc;gBACxD,SAAS,EAAE,aAAa,CAAC,WAAW,CAAC,SAAS;aAC9C;YACD,cAAc,EAAE,SAAS;SACzB,CAAC;IACH,CAAC;IACD,qEAAqE;IACrE,OAAO,EAAE,cAAc,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { getEffectiveBatchId, type InboundMessageResult } from \"../opLifecycle/index.js\";\n\n/**\n * The version-mark resolver update derived from an inbound batch result.\n *\n * @internal\n */\nexport interface InboundVersionMarkUpdate {\n\t/**\n\t * A completed batch to record via `processInboundBatch`: its id and its last op's sequence\n\t * number and timestamp. Undefined when no batch completed on this message (a piecemeal batch\n\t * still in progress).\n\t */\n\treadonly sequenced?: {\n\t\treadonly batchId: string;\n\t\treadonly sequenceNumber: number;\n\t\treadonly timestamp: number;\n\t};\n\t/**\n\t * The batch id to carry for a piecemeal batch still in progress, or undefined when none is pending.\n\t * The caller stores this and passes it back on the next inbound result.\n\t */\n\treadonly carriedBatchId: string | undefined;\n}\n\n/**\n * Maps an inbound batch result to the version-mark resolver update. A batch's resolved point is its last\n * op's sequence number and timestamp, so this reports `(batchId, sequenceNumber, timestamp)` only once a\n * batch completes. It handles both a batch delivered whole (`fullBatch`, incl. an empty grouped batch via\n * the batch-start key message) and one delivered piecemeal (`batchStartingMessage` then `nextBatchMessage`\n * with `batchEnd`), carrying the batch id across the piecemeal messages via `carriedBatchId`.\n *\n * @internal\n */\nexport function inboundVersionMarkUpdate(\n\tinboundResult: InboundMessageResult,\n\tcarriedBatchId: string | undefined,\n): InboundVersionMarkUpdate {\n\tif (\"batchStart\" in inboundResult) {\n\t\tconst batchId = getEffectiveBatchId(inboundResult.batchStart);\n\t\tif (inboundResult.type === \"fullBatch\") {\n\t\t\t// The last op is the batch's resolved point; an empty batch falls back to the batch-start key message.\n\t\t\tconst lastMessage =\n\t\t\t\t// eslint-disable-next-line unicorn/prefer-at -- Array.prototype.at is not in this package's lib target\n\t\t\t\tinboundResult.messages[inboundResult.messages.length - 1] ??\n\t\t\t\tinboundResult.batchStart.keyMessage;\n\t\t\treturn {\n\t\t\t\tsequenced: {\n\t\t\t\t\tbatchId,\n\t\t\t\t\tsequenceNumber: lastMessage.sequenceNumber,\n\t\t\t\t\ttimestamp: lastMessage.timestamp,\n\t\t\t\t},\n\t\t\t\tcarriedBatchId: undefined,\n\t\t\t};\n\t\t}\n\t\t// batchStartingMessage: carry the id until the batch's end message arrives.\n\t\treturn { carriedBatchId: batchId };\n\t}\n\tif (\n\t\tinboundResult.type === \"nextBatchMessage\" &&\n\t\tinboundResult.batchEnd === true &&\n\t\tcarriedBatchId !== undefined\n\t) {\n\t\treturn {\n\t\t\tsequenced: {\n\t\t\t\tbatchId: carriedBatchId,\n\t\t\t\tsequenceNumber: inboundResult.nextMessage.sequenceNumber,\n\t\t\t\ttimestamp: inboundResult.nextMessage.timestamp,\n\t\t\t},\n\t\t\tcarriedBatchId: undefined,\n\t\t};\n\t}\n\t// A mid-batch message, or a batch end with no carried id: no change.\n\treturn { carriedBatchId };\n}\n"]}
@@ -15,13 +15,15 @@ export interface IHistoricalOpReader {
15
15
  fetchMessages(from: number, to?: number, abortSignal?: AbortSignal): Promise<IStream<ISequencedDocumentMessage[]>>;
16
16
  }
17
17
  /**
18
- * Result of resolving a pending batchId.
18
+ * Result of resolving a pending batchId. A resolved result includes the matched batch's last op server
19
+ * timestamp when available. The property is optional for compatibility with previously stored results.
19
20
  *
20
21
  * @legacy @beta
21
22
  */
22
23
  export type ResolveResult = {
23
24
  readonly kind: "resolved";
24
25
  readonly sequenceNumber: number;
26
+ readonly timestamp?: number;
25
27
  } | {
26
28
  readonly kind: "pending";
27
29
  } | {
@@ -42,6 +44,7 @@ export type VersionMarkCapture = {
42
44
  } | {
43
45
  readonly kind: "resolved";
44
46
  readonly sequenceNumber: number;
47
+ readonly timestamp?: number;
45
48
  };
46
49
  /**
47
50
  * Runtime-owned resolver for app-stored version mark locators.
@@ -53,40 +56,45 @@ export interface IVersionMarkResolver {
53
56
  * Captures a version mark at the current point. Seals the current outbound batch first (so a just-made
54
57
  * local edit has a stable `batchId`, which is only assigned when a batch is flushed), then returns the
55
58
  * mark data atomically: a `pending` capture (`batchId` + `sequenceNumberLowerBound`) when there is an
56
- * unacked local batch, or a `resolved` capture (`sequenceNumber`) when there is no in-flight local work.
59
+ * unacked local batch, or a `resolved` capture (`sequenceNumber` + the last processed op's server
60
+ * `timestamp`) when there is no in-flight local work. The timestamp property is optional both for
61
+ * compatibility with previously stored captures and because it is `undefined` when neither a last
62
+ * processed message nor a last-summary message is available.
57
63
  *
58
64
  * @remarks Sealing the batch is a side effect (it submits the current batch), so capture at savepoint
59
65
  * boundaries, not per keystroke.
60
66
  *
61
- * @returns The pending batch identity and exclusive sequence number lower bound, or the current sequence number
62
- * when there is no pending local batch.
67
+ * @returns The pending batch identity and inclusive sequence number lower bound, or the current sequence
68
+ * number and corresponding op timestamp (when available) when there is no pending local batch.
63
69
  */
64
70
  sealAndCaptureVersionMark(): VersionMarkCapture;
65
71
  /**
66
72
  * Resolves a pending mark's batchId to a global sequence number (`sequenceNumberLowerBound` is the
67
- * exclusive lower bound for a history read). A `resolved` sequence number feeds the loader's
73
+ * inclusive lower bound for a history read). A `resolved` sequence number feeds the loader's
68
74
  * `loadContainerToSequenceNumber`.
69
75
  *
70
76
  * @param batchId - The stable identity of the pending batch.
71
- * @param sequenceNumberLowerBound - The exclusive lower bound for the historical op search.
72
- * @returns The resolved sequence number, or a result indicating that the batch remains pending or can
73
- * no longer be resolved.
77
+ * @param sequenceNumberLowerBound - The inclusive lower bound for the historical op search.
78
+ * @returns The resolved sequence number and server timestamp, or a result indicating that the batch
79
+ * remains pending or can no longer be resolved.
74
80
  */
75
81
  resolve(batchId: string, sequenceNumberLowerBound: number): Promise<ResolveResult>;
76
82
  /**
77
- * Subscribes to inbound batch sequencing: fires `(batchId, sequenceNumber)` per batch so any connected
78
- * client can promote a matching pending mark. Returns an unsubscribe function.
83
+ * Subscribes to inbound batch sequencing: fires `(batchId, sequenceNumber, timestamp)` per batch so any
84
+ * connected client can promote a matching pending mark. Returns an unsubscribe function.
79
85
  *
80
- * @param listener - Called with the stable batch identity and its final sequence number.
86
+ * @param listener - Called with the stable batch identity, its final sequence number, and the final op's
87
+ * server timestamp.
81
88
  * @returns A function that unsubscribes the listener.
82
89
  */
83
- onBatchSequenced(listener: (batchId: string, sequenceNumber: number) => void): () => void;
90
+ onBatchSequenced(listener: (batchId: string, sequenceNumber: number, timestamp?: number) => void): () => void;
84
91
  }
85
92
  /**
86
93
  * @internal
87
94
  */
88
95
  export interface VersionMarkResolverRuntimeHooks {
89
96
  readonly getCurrentSequenceNumber: () => number;
97
+ readonly getCurrentTimestamp: () => number | undefined;
90
98
  readonly getCurrentMinimumSequenceNumber: () => number;
91
99
  readonly getCurrentPendingBatchId: () => string | undefined;
92
100
  /** Logs faults from app-supplied `onBatchSequenced` listeners (see {@link VersionMarkResolver.processInboundBatch}). */
@@ -118,7 +126,7 @@ export interface VersionMarkResolverRuntimeHooks {
118
126
  */
119
127
  export declare class VersionMarkResolver implements IVersionMarkResolver {
120
128
  private readonly hooks;
121
- private readonly sequenceNumberByBatchId;
129
+ private readonly resolvedBatchById;
122
130
  private readonly batchListeners;
123
131
  /**
124
132
  * Sticky flag: false until the feature is first used this session (a pending capture or a listener).
@@ -132,25 +140,24 @@ export declare class VersionMarkResolver implements IVersionMarkResolver {
132
140
  sealAndCaptureVersionMark(): VersionMarkCapture;
133
141
  resolve(batchId: string, sequenceNumberLowerBound: number): Promise<ResolveResult>;
134
142
  /**
135
- * Scans sequenced ops after `sequenceNumberLowerBound` for `batchId`, returning its last op's
143
+ * Scans sequenced ops from `sequenceNumberLowerBound` (inclusive) for `batchId`, returning its last op's
136
144
  * sequence number when found and aborting the fetch once found or exhausted. Each op is routed through
137
145
  * the same unpack pipeline the live inbound path uses (chunk reassembly, ungroup, decompress) and
138
- * {@link inboundVersionMarkUpdate} derives its batch identity, so chunked batches resolve here too. The
139
- * scan anchor is not guaranteed to be a batch boundary, so a leading batch clipped by the anchor is
140
- * tolerated (its orphan end marker is dropped rather than fed to the processor). On a miss,
141
- * {@link classifyMiss} distinguishes `pending` (not sequenced yet) from `unresolvable` (ops trimmed).
142
- * See DEV.md.
146
+ * {@link inboundVersionMarkUpdate} derives its batch identity, so virtualized batches resolve here too.
147
+ * The injected historical unpacker owns virtualization details, including tolerating a requested range
148
+ * that begins within an incomplete chunk stream. On a miss, {@link classifyMiss} distinguishes
149
+ * `pending` (not sequenced yet) from `unresolvable` (ops trimmed).
143
150
  */
144
151
  private resolveFromHistory;
145
152
  /**
146
153
  * Classifies a history-scan miss as `pending` or `unresolvable`. See {@link resolveFromHistory}.
147
154
  */
148
155
  private classifyMiss;
149
- onBatchSequenced(listener: (batchId: string, sequenceNumber: number) => void): () => void;
150
- /** The sequence number for a batch seen live this session, or undefined. */
151
- private sessionSequenceFor;
156
+ onBatchSequenced(listener: (batchId: string, sequenceNumber: number, timestamp?: number) => void): () => void;
157
+ /** The resolved point for a batch seen live this session, or undefined. */
158
+ private sessionResolutionFor;
152
159
  /** Records an inbound batch and notifies listeners so any client can promote matching pending marks. */
153
- processInboundBatch(batchId: string, sequenceNumber: number): void;
160
+ processInboundBatch(batchId: string, sequenceNumber: number, timestamp: number): void;
154
161
  /**
155
162
  * Bounds the map to the collaboration window by dropping entries below the MSN (mirrors
156
163
  * `DuplicateBatchDetector`). Safe because below the MSN every client has processed the batch, so its
@@ -1 +1 @@
1
- {"version":3,"file":"versionMarkResolver.d.ts","sourceRoot":"","sources":["../../src/versionMarks/versionMarkResolver.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACX,yBAAyB,EACzB,OAAO,EACP,MAAM,6CAA6C,CAAC;AAGrD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAEnF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAKpE;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IACnC,aAAa,CACZ,IAAI,EAAE,MAAM,EACZ,EAAE,CAAC,EAAE,MAAM,EACX,WAAW,CAAC,EAAE,WAAW,GACvB,OAAO,CAAC,OAAO,CAAC,yBAAyB,EAAE,CAAC,CAAC,CAAC;CACjD;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACtB;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;CAAE,GAC9D;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC;AAErC;;;;;;;GAOG;AACH,MAAM,MAAM,kBAAkB,GAC3B;IACA,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;CACzC,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACpC;;;;;;;;;;;OAWG;IACH,yBAAyB,IAAI,kBAAkB,CAAC;IAChD;;;;;;;;;OASG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,wBAAwB,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACnF;;;;;;OAMG;IACH,gBAAgB,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC1F;AAED;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC/C,QAAQ,CAAC,wBAAwB,EAAE,MAAM,MAAM,CAAC;IAChD,QAAQ,CAAC,+BAA+B,EAAE,MAAM,MAAM,CAAC;IACvD,QAAQ,CAAC,wBAAwB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAC5D,wHAAwH;IACxH,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,EAAE,MAAM,IAAI,CAAC;IACvC;;OAEG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,mBAAmB,GAAG,SAAS,CAAC;IACvE;;;;;;OAMG;IACH,QAAQ,CAAC,0BAA0B,CAAC,EAAE,MAAM,CAC3C,EAAE,EAAE,yBAAyB,KACzB,oBAAoB,GAAG,SAAS,CAAC;CACtC;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,YAAW,oBAAoB;IAY5C,OAAO,CAAC,QAAQ,CAAC,KAAK;IAXzC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAA6B;IACrE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAE3B;IACJ;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAS;gBAEW,KAAK,EAAE,+BAA+B;IAE1E,8GAA8G;IAC9G,IAAW,UAAU,IAAI,OAAO,CAE/B;IAEM,yBAAyB,IAAI,kBAAkB;IAezC,OAAO,CACnB,OAAO,EAAE,MAAM,EACf,wBAAwB,EAAE,MAAM,GAC9B,OAAO,CAAC,aAAa,CAAC;IAgBzB;;;;;;;;;OASG;YACW,kBAAkB;IA0DhC;;OAEG;IACH,OAAO,CAAC,YAAY;IA6Bb,gBAAgB,CACtB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,KAAK,IAAI,GACzD,MAAM,IAAI;IASb,4EAA4E;IAC5E,OAAO,CAAC,kBAAkB;IAI1B,wGAAwG;IACjG,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IAoBzE;;;;;OAKG;IACH,OAAO,CAAC,+BAA+B;CASvC"}
1
+ {"version":3,"file":"versionMarkResolver.d.ts","sourceRoot":"","sources":["../../src/versionMarks/versionMarkResolver.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,6 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  import { assert } from "@fluidframework/core-utils/internal";
6
- import { asBatchMetadata } from "../metadata.js";
7
6
  import { inboundVersionMarkUpdate } from "./inboundBatch.js";
8
7
  /**
9
8
  * Resolves a mark's batchId to a global sequence number. The app owns mark storage and promotion; the
@@ -12,16 +11,17 @@ import { inboundVersionMarkUpdate } from "./inboundBatch.js";
12
11
  * @internal
13
12
  */
14
13
  export class VersionMarkResolver {
14
+ hooks;
15
+ resolvedBatchById = new Map();
16
+ batchListeners = new Set();
17
+ /**
18
+ * Sticky flag: false until the feature is first used this session (a pending capture or a listener).
19
+ * While false the runtime skips all per-inbound-batch tracking, so a container that never uses version
20
+ * marks pays nothing on the hot path (mirrors #22497 gating `DuplicateBatchDetector` on offline load).
21
+ */
22
+ tracking = false;
15
23
  constructor(hooks) {
16
24
  this.hooks = hooks;
17
- this.sequenceNumberByBatchId = new Map();
18
- this.batchListeners = new Set();
19
- /**
20
- * Sticky flag: false until the feature is first used this session (a pending capture or a listener).
21
- * While false the runtime skips all per-inbound-batch tracking, so a container that never uses version
22
- * marks pays nothing on the hot path (mirrors #22497 gating `DuplicateBatchDetector` on offline load).
23
- */
24
- this.tracking = false;
25
25
  }
26
26
  /** Whether inbound batches need tracking (see {@link tracking}). Read by the runtime to gate the hot path. */
27
27
  get isTracking() {
@@ -31,50 +31,106 @@ export class VersionMarkResolver {
31
31
  // Seal the current batch so a just-submitted local edit is flushed into the pending state with a
32
32
  // stable batchId (which is only assigned at flush) before we read it.
33
33
  this.hooks.flushPendingBatch();
34
- const sequenceNumberLowerBound = this.hooks.getCurrentSequenceNumber();
34
+ const referenceSequenceNumber = this.hooks.getCurrentSequenceNumber();
35
35
  const batchId = this.hooks.getCurrentPendingBatchId();
36
+ let result;
36
37
  if (batchId === undefined) {
37
38
  // No unacked local batch: the mark already points at a durable sequence number.
38
- return { kind: "resolved", sequenceNumber: sequenceNumberLowerBound };
39
+ result = {
40
+ kind: "resolved",
41
+ sequenceNumber: referenceSequenceNumber,
42
+ timestamp: this.hooks.getCurrentTimestamp(),
43
+ };
39
44
  }
40
- // A pending mark needs its batch tracked so resolve() can promote it from the live map.
41
- this.tracking = true;
42
- return { kind: "pending", batchId, sequenceNumberLowerBound };
45
+ else {
46
+ // A pending mark needs its batch tracked so resolve() can promote it from the live map.
47
+ this.tracking = true;
48
+ // The pending batch is sequenced after the reference point, so its first possible sequence
49
+ // number is `referenceSequenceNumber + 1`. Store that as an inclusive lower bound so resolve()
50
+ // scans directly from it.
51
+ result = {
52
+ kind: "pending",
53
+ batchId,
54
+ sequenceNumberLowerBound: referenceSequenceNumber + 1,
55
+ };
56
+ }
57
+ this.hooks.logger.sendTelemetryEvent({ eventName: "Capture", kind: result.kind });
58
+ return result;
43
59
  }
44
60
  async resolve(batchId, sequenceNumberLowerBound) {
45
- // Fast path: batch sequenced live this session.
46
- const sequenceNumber = this.sessionSequenceFor(batchId);
47
- if (sequenceNumber !== undefined) {
48
- return { kind: "resolved", sequenceNumber };
61
+ const startTime = Date.now();
62
+ // Track from here so inbound batches are recorded even without a prior capture/subscribe; otherwise
63
+ // a batch sequencing during the scan (or live, in the no-reader case) is skipped and unrecoverable.
64
+ this.tracking = true;
65
+ // Defaults cover the throw path (only the history scan can throw — e.g. an unpacker
66
+ // DataCorruptionError or the 0xd1c reader-contract assert): the Resolve event still fires via
67
+ // `finally`, with outcome "error".
68
+ let path = "history";
69
+ let outcome = "error";
70
+ let resolvedSequenceNumber;
71
+ try {
72
+ // Fast path: batch sequenced live this session.
73
+ const resolvedBatch = this.sessionResolutionFor(batchId);
74
+ if (resolvedBatch === undefined) {
75
+ const reader = this.hooks.getHistoricalOpReader?.();
76
+ if (reader === undefined) {
77
+ // No reader: the batch may still sequence live, so report pending.
78
+ path = "noReader";
79
+ outcome = "pending";
80
+ return { kind: "pending" };
81
+ }
82
+ // Otherwise scan history from the mark's reference point.
83
+ path = "history";
84
+ let result = await this.resolveFromHistory(reader, batchId, sequenceNumberLowerBound);
85
+ if (result.kind !== "resolved") {
86
+ // The batch may have sequenced live while the history scan was in progress. Prefer that
87
+ // authoritative in-session result over a stale history miss.
88
+ const liveResult = this.sessionResolutionFor(batchId);
89
+ if (liveResult !== undefined) {
90
+ path = "session";
91
+ result = { kind: "resolved", ...liveResult };
92
+ }
93
+ }
94
+ outcome = result.kind;
95
+ if (result.kind === "resolved") {
96
+ resolvedSequenceNumber = result.sequenceNumber;
97
+ }
98
+ return result;
99
+ }
100
+ path = "session";
101
+ outcome = "resolved";
102
+ resolvedSequenceNumber = resolvedBatch.sequenceNumber;
103
+ return { kind: "resolved", ...resolvedBatch };
49
104
  }
50
- // Otherwise scan history from the mark's reference point.
51
- const reader = this.hooks.getHistoricalOpReader?.();
52
- if (reader === undefined) {
53
- // No reader: the batch may still sequence live, so report pending.
54
- return { kind: "pending" };
105
+ finally {
106
+ this.hooks.logger.sendTelemetryEvent({
107
+ eventName: "Resolve",
108
+ outcome,
109
+ path,
110
+ durationMs: Date.now() - startTime,
111
+ ...(resolvedSequenceNumber === undefined
112
+ ? {}
113
+ : { sequenceNumber: resolvedSequenceNumber }),
114
+ });
55
115
  }
56
- return this.resolveFromHistory(reader, batchId, sequenceNumberLowerBound);
57
116
  }
58
117
  /**
59
- * Scans sequenced ops after `sequenceNumberLowerBound` for `batchId`, returning its last op's
118
+ * Scans sequenced ops from `sequenceNumberLowerBound` (inclusive) for `batchId`, returning its last op's
60
119
  * sequence number when found and aborting the fetch once found or exhausted. Each op is routed through
61
120
  * the same unpack pipeline the live inbound path uses (chunk reassembly, ungroup, decompress) and
62
- * {@link inboundVersionMarkUpdate} derives its batch identity, so chunked batches resolve here too. The
63
- * scan anchor is not guaranteed to be a batch boundary, so a leading batch clipped by the anchor is
64
- * tolerated (its orphan end marker is dropped rather than fed to the processor). On a miss,
65
- * {@link classifyMiss} distinguishes `pending` (not sequenced yet) from `unresolvable` (ops trimmed).
66
- * See DEV.md.
121
+ * {@link inboundVersionMarkUpdate} derives its batch identity, so virtualized batches resolve here too.
122
+ * The injected historical unpacker owns virtualization details, including tolerating a requested range
123
+ * that begins within an incomplete chunk stream. On a miss, {@link classifyMiss} distinguishes
124
+ * `pending` (not sequenced yet) from `unresolvable` (ops trimmed).
67
125
  */
68
126
  async resolveFromHistory(reader, batchId, sequenceNumberLowerBound) {
69
- const from = sequenceNumberLowerBound + 1;
127
+ const from = sequenceNumberLowerBound;
70
128
  const unpack = this.hooks.createHistoricalOpUnpacker?.();
71
129
  const abortController = new AbortController();
72
130
  try {
73
131
  const stream = await reader.fetchMessages(from, undefined, abortController.signal);
74
132
  let firstScannedSequenceNumber;
75
133
  let carriedBatchId;
76
- // Mirrors the processor's batch-in-progress state so we can drop an orphan end marker (below).
77
- let inBatch = false;
78
134
  while (true) {
79
135
  const result = await stream.read();
80
136
  if (result.done) {
@@ -82,31 +138,21 @@ export class VersionMarkResolver {
82
138
  }
83
139
  for (const op of result.value) {
84
140
  firstScannedSequenceNumber ??= op.sequenceNumber;
85
- if (!inBatch && asBatchMetadata(op.metadata)?.batch === false) {
86
- // The anchor (`sequenceNumberLowerBound + 1`) may land inside a batch whose start
87
- // precedes the window; its orphan end marker would trip the processor's 0x9d5 assert.
88
- // Drop the clipped tail. The target batch is fully in-window, so this can't skip it.
89
- continue;
90
- }
91
- // undefined = a system op, or an incomplete chunk awaiting later fragments.
141
+ // undefined = a system op or virtualized input that has not produced a complete result.
92
142
  const inboundResult = unpack?.(op);
93
143
  if (inboundResult === undefined) {
94
144
  continue;
95
145
  }
96
- // Track batch progress with the processor so the guard above skips only orphan ends.
97
- if (inboundResult.type === "batchStartingMessage") {
98
- inBatch = true;
99
- }
100
- else if (inboundResult.type === "nextBatchMessage" &&
101
- inboundResult.batchEnd === true) {
102
- inBatch = false;
103
- }
104
146
  // Derive batch identity exactly as the live inbound path does, carrying the id across a
105
147
  // piecemeal batch's messages.
106
148
  const update = inboundVersionMarkUpdate(inboundResult, carriedBatchId);
107
149
  carriedBatchId = update.carriedBatchId;
108
150
  if (update.sequenced?.batchId === batchId) {
109
- return { kind: "resolved", sequenceNumber: update.sequenced.sequenceNumber };
151
+ return {
152
+ kind: "resolved",
153
+ sequenceNumber: update.sequenced.sequenceNumber,
154
+ timestamp: update.sequenced.timestamp,
155
+ };
110
156
  }
111
157
  }
112
158
  }
@@ -127,7 +173,7 @@ export class VersionMarkResolver {
127
173
  assert(firstScannedSequenceNumber === undefined || firstScannedSequenceNumber >= from, 0xd1c /* historical op reader returned an op before the requested range start */);
128
174
  const tip = this.hooks.getCurrentSequenceNumber();
129
175
  if (from > tip) {
130
- // Nothing is sequenced at/after the mark's reference point yet, so the batch cannot have landed.
176
+ // Nothing is sequenced at/after the mark's lower bound yet, so the batch cannot have landed.
131
177
  return { kind: "pending" };
132
178
  }
133
179
  if (firstScannedSequenceNumber === undefined) {
@@ -137,10 +183,10 @@ export class VersionMarkResolver {
137
183
  return { kind: "unresolvable" };
138
184
  }
139
185
  if (firstScannedSequenceNumber > from) {
140
- // A trim gap at the anchor: the mark's ops (just after the reference point) are gone.
186
+ // A trim gap at the anchor: the mark's ops (at/after its lower bound) are gone.
141
187
  return { kind: "unresolvable" };
142
188
  }
143
- // Ops are present from the reference point and the batch is not among them: not yet sequenced.
189
+ // Ops are present from the lower bound and the batch is not among them: not yet sequenced.
144
190
  return { kind: "pending" };
145
191
  }
146
192
  onBatchSequenced(listener) {
@@ -151,21 +197,24 @@ export class VersionMarkResolver {
151
197
  this.batchListeners.delete(listener);
152
198
  };
153
199
  }
154
- /** The sequence number for a batch seen live this session, or undefined. */
155
- sessionSequenceFor(batchId) {
156
- return this.sequenceNumberByBatchId.get(batchId);
200
+ /** The resolved point for a batch seen live this session, or undefined. */
201
+ sessionResolutionFor(batchId) {
202
+ return this.resolvedBatchById.get(batchId);
157
203
  }
158
204
  /** Records an inbound batch and notifies listeners so any client can promote matching pending marks. */
159
- processInboundBatch(batchId, sequenceNumber) {
160
- const existingSequenceNumber = this.sequenceNumberByBatchId.get(batchId);
161
- if (existingSequenceNumber === sequenceNumber) {
205
+ processInboundBatch(batchId, sequenceNumber, timestamp) {
206
+ const existingResolution = this.resolvedBatchById.get(batchId);
207
+ if (existingResolution !== undefined) {
208
+ // NOTE: It's possible (but exceedingly rare) that this is a spurious duplicated batch,
209
+ // with a different sequence number/timestamp.
210
+ // Rather than asserting or overwriting, we let the earlier version mark stand, it is sufficient.
162
211
  return;
163
212
  }
164
- this.sequenceNumberByBatchId.set(batchId, sequenceNumber);
213
+ this.resolvedBatchById.set(batchId, { sequenceNumber, timestamp });
165
214
  this.evictBelowMinimumSequenceNumber();
166
215
  for (const listener of this.batchListeners) {
167
216
  try {
168
- listener(batchId, sequenceNumber);
217
+ listener(batchId, sequenceNumber, timestamp);
169
218
  }
170
219
  catch (error) {
171
220
  // Isolate each app listener (like the container's EventEmitterWithErrorHandling): one throw
@@ -183,11 +232,11 @@ export class VersionMarkResolver {
183
232
  */
184
233
  evictBelowMinimumSequenceNumber() {
185
234
  const minimumSequenceNumber = this.hooks.getCurrentMinimumSequenceNumber();
186
- for (const [id, sequenceNumber] of this.sequenceNumberByBatchId) {
187
- if (sequenceNumber >= minimumSequenceNumber) {
235
+ for (const [id, resolution] of this.resolvedBatchById) {
236
+ if (resolution.sequenceNumber >= minimumSequenceNumber) {
188
237
  break;
189
238
  }
190
- this.sequenceNumberByBatchId.delete(id);
239
+ this.resolvedBatchById.delete(id);
191
240
  }
192
241
  }
193
242
  }