@fluidframework/replay-driver 2.0.0-internal.3.0.2 → 2.0.0-internal.3.2.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 (45) hide show
  1. package/.eslintrc.js +5 -7
  2. package/api-extractor.json +2 -2
  3. package/dist/emptyDeltaStorageService.d.ts.map +1 -1
  4. package/dist/emptyDeltaStorageService.js.map +1 -1
  5. package/dist/packageVersion.d.ts +1 -1
  6. package/dist/packageVersion.js +1 -1
  7. package/dist/packageVersion.js.map +1 -1
  8. package/dist/replayController.d.ts.map +1 -1
  9. package/dist/replayController.js.map +1 -1
  10. package/dist/replayDocumentDeltaConnection.d.ts.map +1 -1
  11. package/dist/replayDocumentDeltaConnection.js +12 -9
  12. package/dist/replayDocumentDeltaConnection.js.map +1 -1
  13. package/dist/replayDocumentService.d.ts.map +1 -1
  14. package/dist/replayDocumentService.js.map +1 -1
  15. package/dist/replayDocumentServiceFactory.d.ts.map +1 -1
  16. package/dist/replayDocumentServiceFactory.js.map +1 -1
  17. package/dist/storageImplementations.d.ts.map +1 -1
  18. package/dist/storageImplementations.js.map +1 -1
  19. package/lib/emptyDeltaStorageService.d.ts.map +1 -1
  20. package/lib/emptyDeltaStorageService.js.map +1 -1
  21. package/lib/packageVersion.d.ts +1 -1
  22. package/lib/packageVersion.js +1 -1
  23. package/lib/packageVersion.js.map +1 -1
  24. package/lib/replayController.d.ts.map +1 -1
  25. package/lib/replayController.js.map +1 -1
  26. package/lib/replayDocumentDeltaConnection.d.ts.map +1 -1
  27. package/lib/replayDocumentDeltaConnection.js +12 -9
  28. package/lib/replayDocumentDeltaConnection.js.map +1 -1
  29. package/lib/replayDocumentService.d.ts.map +1 -1
  30. package/lib/replayDocumentService.js.map +1 -1
  31. package/lib/replayDocumentServiceFactory.d.ts.map +1 -1
  32. package/lib/replayDocumentServiceFactory.js.map +1 -1
  33. package/lib/storageImplementations.d.ts.map +1 -1
  34. package/lib/storageImplementations.js.map +1 -1
  35. package/package.json +34 -33
  36. package/prettier.config.cjs +1 -1
  37. package/src/emptyDeltaStorageService.ts +14 -13
  38. package/src/packageVersion.ts +1 -1
  39. package/src/replayController.ts +59 -55
  40. package/src/replayDocumentDeltaConnection.ts +323 -310
  41. package/src/replayDocumentService.ts +46 -44
  42. package/src/replayDocumentServiceFactory.ts +58 -44
  43. package/src/storageImplementations.ts +154 -150
  44. package/tsconfig.esnext.json +6 -6
  45. package/tsconfig.json +8 -13
@@ -5,22 +5,22 @@
5
5
 
6
6
  import { IDisposable } from "@fluidframework/common-definitions";
7
7
  import {
8
- IDocumentDeltaConnection,
9
- IDocumentDeltaStorageService,
10
- IDocumentDeltaConnectionEvents,
11
- IDocumentService,
8
+ IDocumentDeltaConnection,
9
+ IDocumentDeltaStorageService,
10
+ IDocumentDeltaConnectionEvents,
11
+ IDocumentService,
12
12
  } from "@fluidframework/driver-definitions";
13
13
  import {
14
- ConnectionMode,
15
- IClientConfiguration,
16
- IConnected,
17
- IDocumentMessage,
18
- ISequencedDocumentMessage,
19
- ISignalClient,
20
- ISignalMessage,
21
- ITokenClaims,
22
- IVersion,
23
- ScopeType,
14
+ ConnectionMode,
15
+ IClientConfiguration,
16
+ IConnected,
17
+ IDocumentMessage,
18
+ ISequencedDocumentMessage,
19
+ ISignalClient,
20
+ ISignalMessage,
21
+ ITokenClaims,
22
+ IVersion,
23
+ ScopeType,
24
24
  } from "@fluidframework/protocol-definitions";
25
25
  import { delay, TypedEventEmitter } from "@fluidframework/common-utils";
26
26
  import { ReplayController } from "./replayController";
@@ -28,303 +28,316 @@ import { ReplayController } from "./replayController";
28
28
  const ReplayDocumentId = "documentId";
29
29
 
30
30
  export class ReplayControllerStatic extends ReplayController {
31
- private static readonly DelayInterval = 50;
32
- private static readonly ReplayResolution = 15;
33
-
34
- private firstTimeStamp: number | undefined;
35
- private replayCurrent = 0;
36
- // Simulated delay interval for emitting the ops
37
-
38
- /**
39
- * Helper class
40
- *
41
- * @param replayFrom - First op to be played on socket.
42
- * @param replayTo - Last op number to be played on socket.
43
- * @param unitIsTime - True is user want to play ops that are within a replay resolution window.
44
- */
45
- public constructor(
46
- public readonly replayFrom: number,
47
- public readonly replayTo: number,
48
- public readonly unitIsTime?: boolean) {
49
- super();
50
- if (unitIsTime !== true) {
51
- // There is no code in here to start with snapshot, thus we have to start with op #0.
52
- this.replayTo = 0;
53
- }
54
- }
55
-
56
- public async initStorage(documentService: IDocumentService) {
57
- return true;
58
- }
59
-
60
- public async getVersions(versionId: string | null, count: number): Promise<IVersion[]> {
61
- return [];
62
- }
63
-
64
- public async getSnapshotTree(version?: IVersion) {
65
- return version ? Promise.reject(new Error("Invalid operation")) : null;
66
- }
67
-
68
- public async readBlob(blobId: string): Promise<ArrayBufferLike> {
69
- return Promise.reject(new Error("Invalid operation"));
70
- }
71
-
72
- public async getStartingOpSequence(): Promise<number> {
73
- return 0;
74
- }
75
-
76
- public fetchTo(currentOp: number) {
77
- if (!(this.unitIsTime !== true && this.replayTo >= 0)) {
78
- return undefined;
79
- }
80
- return this.replayTo;
81
- }
82
-
83
- public isDoneFetch(currentOp: number, lastTimeStamp?: number) {
84
- if (this.replayTo >= 0) {
85
- if (this.unitIsTime === true) {
86
- return (
87
- lastTimeStamp !== undefined
88
- && this.firstTimeStamp !== undefined
89
- && lastTimeStamp - this.firstTimeStamp >= this.replayTo);
90
- }
91
- return currentOp >= this.replayTo;
92
- }
93
- return lastTimeStamp === undefined; // No more ops
94
- }
95
-
96
- public skipToIndex(fetchedOps: ISequencedDocumentMessage[]) {
97
- if (this.replayFrom <= 0) {
98
- return 0;
99
- }
100
- if (this.unitIsTime === true) {
101
- for (let i = 0; i < fetchedOps.length; i += 1) {
102
- const timeStamp = fetchedOps[i].timestamp;
103
- if (timeStamp !== undefined) {
104
- if (this.firstTimeStamp === undefined) {
105
- this.firstTimeStamp = timeStamp;
106
- }
107
- if (timeStamp - this.firstTimeStamp >= this.replayFrom) {
108
- return i;
109
- }
110
- }
111
- }
112
- } else if (this.replayFrom > this.replayCurrent) {
113
- return this.replayFrom - this.replayCurrent;
114
- }
115
- return 0;
116
- }
117
-
118
- public async replay(
119
- emitter: (op: ISequencedDocumentMessage[]) => void,
120
- fetchedOps: ISequencedDocumentMessage[]): Promise<void> {
121
- let current = this.skipToIndex(fetchedOps);
122
-
123
- return new Promise((resolve) => {
124
- const replayNextOps = () => {
125
- // Emit the ops from replay to the end every "deltainterval" milliseconds
126
- // to simulate the socket stream
127
- const currentOp = fetchedOps[current];
128
- const playbackOps = [currentOp];
129
- let nextInterval = ReplayControllerStatic.DelayInterval;
130
- current += 1;
131
-
132
- if (this.unitIsTime === true) {
133
- const currentTimeStamp = currentOp.timestamp;
134
- if (currentTimeStamp !== undefined) {
135
- // Emit more ops that is in the ReplayResolution window
136
-
137
- while (current < fetchedOps.length) {
138
- const op = fetchedOps[current];
139
- if (op.timestamp === undefined) {
140
- // Missing timestamp, just delay the standard amount of time
141
- break;
142
- }
143
- const timeDiff = op.timestamp - currentTimeStamp;
144
- if (timeDiff >= ReplayControllerStatic.ReplayResolution) {
145
- // Time exceeded the resolution window, break out the loop
146
- // and delay for the time difference.
147
- nextInterval = timeDiff;
148
- break;
149
- }
150
- if (timeDiff < 0) {
151
- // Time have regressed, just delay the standard amount of time
152
- break;
153
- }
154
-
155
- // The op is within the ReplayResolution emit it now
156
- playbackOps.push(op);
157
- current += 1;
158
- }
159
-
160
- if (this.firstTimeStamp !== undefined
161
- && this.replayTo >= 0
162
- && currentTimeStamp + nextInterval - this.firstTimeStamp > this.replayTo) {
163
- nextInterval = -1;
164
- }
165
- }
166
- }
167
- scheduleNext(nextInterval);
168
- emitter(playbackOps);
169
- };
170
- const scheduleNext = (nextInterval: number) => {
171
- if (nextInterval >= 0 && current < fetchedOps.length) {
172
- setTimeout(replayNextOps, nextInterval);
173
- } else {
174
- this.replayCurrent += current;
175
- resolve();
176
- }
177
- };
178
- scheduleNext(ReplayControllerStatic.DelayInterval);
179
- });
180
- }
31
+ private static readonly DelayInterval = 50;
32
+ private static readonly ReplayResolution = 15;
33
+
34
+ private firstTimeStamp: number | undefined;
35
+ private replayCurrent = 0;
36
+ // Simulated delay interval for emitting the ops
37
+
38
+ /**
39
+ * Helper class
40
+ *
41
+ * @param replayFrom - First op to be played on socket.
42
+ * @param replayTo - Last op number to be played on socket.
43
+ * @param unitIsTime - True is user want to play ops that are within a replay resolution window.
44
+ */
45
+ public constructor(
46
+ public readonly replayFrom: number,
47
+ public readonly replayTo: number,
48
+ public readonly unitIsTime?: boolean,
49
+ ) {
50
+ super();
51
+ if (unitIsTime !== true) {
52
+ // There is no code in here to start with snapshot, thus we have to start with op #0.
53
+ this.replayTo = 0;
54
+ }
55
+ }
56
+
57
+ public async initStorage(documentService: IDocumentService) {
58
+ return true;
59
+ }
60
+
61
+ public async getVersions(versionId: string | null, count: number): Promise<IVersion[]> {
62
+ return [];
63
+ }
64
+
65
+ public async getSnapshotTree(version?: IVersion) {
66
+ return version ? Promise.reject(new Error("Invalid operation")) : null;
67
+ }
68
+
69
+ public async readBlob(blobId: string): Promise<ArrayBufferLike> {
70
+ return Promise.reject(new Error("Invalid operation"));
71
+ }
72
+
73
+ public async getStartingOpSequence(): Promise<number> {
74
+ return 0;
75
+ }
76
+
77
+ public fetchTo(currentOp: number) {
78
+ if (!(this.unitIsTime !== true && this.replayTo >= 0)) {
79
+ return undefined;
80
+ }
81
+ return this.replayTo;
82
+ }
83
+
84
+ public isDoneFetch(currentOp: number, lastTimeStamp?: number) {
85
+ if (this.replayTo >= 0) {
86
+ if (this.unitIsTime === true) {
87
+ return (
88
+ lastTimeStamp !== undefined &&
89
+ this.firstTimeStamp !== undefined &&
90
+ lastTimeStamp - this.firstTimeStamp >= this.replayTo
91
+ );
92
+ }
93
+ return currentOp >= this.replayTo;
94
+ }
95
+ return lastTimeStamp === undefined; // No more ops
96
+ }
97
+
98
+ public skipToIndex(fetchedOps: ISequencedDocumentMessage[]) {
99
+ if (this.replayFrom <= 0) {
100
+ return 0;
101
+ }
102
+ if (this.unitIsTime === true) {
103
+ for (let i = 0; i < fetchedOps.length; i += 1) {
104
+ const timeStamp = fetchedOps[i].timestamp;
105
+ if (timeStamp !== undefined) {
106
+ if (this.firstTimeStamp === undefined) {
107
+ this.firstTimeStamp = timeStamp;
108
+ }
109
+ if (timeStamp - this.firstTimeStamp >= this.replayFrom) {
110
+ return i;
111
+ }
112
+ }
113
+ }
114
+ } else if (this.replayFrom > this.replayCurrent) {
115
+ return this.replayFrom - this.replayCurrent;
116
+ }
117
+ return 0;
118
+ }
119
+
120
+ public async replay(
121
+ emitter: (op: ISequencedDocumentMessage[]) => void,
122
+ fetchedOps: ISequencedDocumentMessage[],
123
+ ): Promise<void> {
124
+ let current = this.skipToIndex(fetchedOps);
125
+
126
+ return new Promise((resolve) => {
127
+ const replayNextOps = () => {
128
+ // Emit the ops from replay to the end every "deltainterval" milliseconds
129
+ // to simulate the socket stream
130
+ const currentOp = fetchedOps[current];
131
+ const playbackOps = [currentOp];
132
+ let nextInterval = ReplayControllerStatic.DelayInterval;
133
+ current += 1;
134
+
135
+ if (this.unitIsTime === true) {
136
+ const currentTimeStamp = currentOp.timestamp;
137
+ if (currentTimeStamp !== undefined) {
138
+ // Emit more ops that is in the ReplayResolution window
139
+
140
+ while (current < fetchedOps.length) {
141
+ const op = fetchedOps[current];
142
+ if (op.timestamp === undefined) {
143
+ // Missing timestamp, just delay the standard amount of time
144
+ break;
145
+ }
146
+ const timeDiff = op.timestamp - currentTimeStamp;
147
+ if (timeDiff >= ReplayControllerStatic.ReplayResolution) {
148
+ // Time exceeded the resolution window, break out the loop
149
+ // and delay for the time difference.
150
+ nextInterval = timeDiff;
151
+ break;
152
+ }
153
+ if (timeDiff < 0) {
154
+ // Time have regressed, just delay the standard amount of time
155
+ break;
156
+ }
157
+
158
+ // The op is within the ReplayResolution emit it now
159
+ playbackOps.push(op);
160
+ current += 1;
161
+ }
162
+
163
+ if (
164
+ this.firstTimeStamp !== undefined &&
165
+ this.replayTo >= 0 &&
166
+ currentTimeStamp + nextInterval - this.firstTimeStamp > this.replayTo
167
+ ) {
168
+ nextInterval = -1;
169
+ }
170
+ }
171
+ }
172
+ scheduleNext(nextInterval);
173
+ emitter(playbackOps);
174
+ };
175
+ const scheduleNext = (nextInterval: number) => {
176
+ if (nextInterval >= 0 && current < fetchedOps.length) {
177
+ setTimeout(replayNextOps, nextInterval);
178
+ } else {
179
+ this.replayCurrent += current;
180
+ resolve();
181
+ }
182
+ };
183
+ scheduleNext(ReplayControllerStatic.DelayInterval);
184
+ });
185
+ }
181
186
  }
182
187
 
183
188
  export class ReplayDocumentDeltaConnection
184
- extends TypedEventEmitter<IDocumentDeltaConnectionEvents>
185
- implements IDocumentDeltaConnection, IDisposable {
186
- /**
187
- * Creates a new delta connection and mimics the delta connection to replay ops on it.
188
- * @param documentService - The document service to be used to get underlying endpoints.
189
- */
190
- public static create(
191
- documentStorageService: IDocumentDeltaStorageService,
192
- controller: ReplayController): IDocumentDeltaConnection {
193
- const connection: IConnected = {
194
- claims: ReplayDocumentDeltaConnection.claims,
195
- clientId: "PseudoClientId",
196
- existing: true,
197
- initialMessages: [],
198
- initialSignals: [],
199
- initialClients: [],
200
- maxMessageSize: ReplayDocumentDeltaConnection.ReplayMaxMessageSize,
201
- mode: "read",
202
- serviceConfiguration: {
203
- blockSize: 64436,
204
- maxMessageSize: ReplayDocumentDeltaConnection.ReplayMaxMessageSize,
205
- },
206
- supportedVersions: [ReplayDocumentDeltaConnection.replayProtocolVersion],
207
- version: ReplayDocumentDeltaConnection.replayProtocolVersion,
208
- };
209
- const deltaConnection = new ReplayDocumentDeltaConnection(connection);
210
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
211
- deltaConnection.fetchAndEmitOps(documentStorageService, controller);
212
-
213
- return deltaConnection;
214
- }
215
-
216
- private static readonly replayProtocolVersion = "^0.1.0";
217
- // Since the replay service never actually sends messages the size below is arbitrary
218
- private static readonly ReplayMaxMessageSize = 16 * 1024;
219
-
220
- private static readonly claims: ITokenClaims = {
221
- documentId: ReplayDocumentId,
222
- scopes: [ScopeType.DocRead],
223
- tenantId: "",
224
- user: {
225
- id: "",
226
- },
227
- iat: Math.round(new Date().getTime() / 1000),
228
- exp: Math.round(new Date().getTime() / 1000) + 60 * 60, // 1 hour expiration
229
- ver: "1.0",
230
- };
231
-
232
- public get clientId(): string {
233
- return this.details.clientId;
234
- }
235
-
236
- public get mode(): ConnectionMode {
237
- return this.details.mode;
238
- }
239
-
240
- public get claims(): ITokenClaims {
241
- return this.details.claims;
242
- }
243
-
244
- public get existing(): boolean {
245
- return this.details.existing;
246
- }
247
-
248
- public get version(): string {
249
- return this.details.version;
250
- }
251
-
252
- public get initialMessages(): ISequencedDocumentMessage[] {
253
- return this.details.initialMessages;
254
- }
255
-
256
- public get initialSignals(): ISignalMessage[] {
257
- return this.details.initialSignals;
258
- }
259
-
260
- public get initialClients(): ISignalClient[] {
261
- return this.details.initialClients;
262
- }
263
-
264
- public get serviceConfiguration(): IClientConfiguration {
265
- return this.details.serviceConfiguration;
266
- }
267
-
268
- public readonly maxMessageSize = ReplayDocumentDeltaConnection.ReplayMaxMessageSize;
269
-
270
- constructor(
271
- public details: IConnected,
272
- ) {
273
- super();
274
- }
275
-
276
- public submit(documentMessage: IDocumentMessage[]): void {
277
- // ReplayDocumentDeltaConnection.submit() can't be called - client never sees its own join on,
278
- // and thus can never move to sending ops.
279
- throw new Error("ReplayDocumentDeltaConnection.submit() can't be called");
280
- }
281
-
282
- public async submitSignal(message: any) {
283
- }
284
-
285
- private _disposed = false;
286
- public get disposed() { return this._disposed; }
287
- public dispose() { this._disposed = true; }
288
-
289
- /**
290
- * This gets the specified ops from the delta storage endpoint and replays them in the replayer.
291
- */
292
- private async fetchAndEmitOps(
293
- documentStorageService: IDocumentDeltaStorageService,
294
- controller: ReplayController,
295
- ): Promise<void> {
296
- let done;
297
- let replayPromiseChain = Promise.resolve();
298
-
299
- let currentOp = await controller.getStartingOpSequence();
300
-
301
- do {
302
- const fetchTo = controller.fetchTo(currentOp);
303
-
304
- const abortController = new AbortController();
305
- const stream = documentStorageService.fetchMessages(currentOp + 1, fetchTo, abortController.signal);
306
- do {
307
- const result = await stream.read();
308
-
309
- if (result.done) {
310
- // No more ops. But, they can show up later, either because document was just created,
311
- // or because another client keeps submitting new ops.
312
- done = controller.isDoneFetch(currentOp, undefined);
313
- if (!done) {
314
- await delay(2000);
315
- }
316
- break;
317
- }
318
- replayPromiseChain = replayPromiseChain.then(
319
- async () => controller.replay((ops) => this.emit("op", ReplayDocumentId, ops), messages));
320
-
321
- const messages = result.value;
322
- currentOp += messages.length;
323
- done = controller.isDoneFetch(currentOp, messages[messages.length - 1].timestamp);
324
- } while (!done);
325
-
326
- abortController.abort();
327
- } while (!done);
328
- return replayPromiseChain;
329
- }
189
+ extends TypedEventEmitter<IDocumentDeltaConnectionEvents>
190
+ implements IDocumentDeltaConnection, IDisposable
191
+ {
192
+ /**
193
+ * Creates a new delta connection and mimics the delta connection to replay ops on it.
194
+ * @param documentService - The document service to be used to get underlying endpoints.
195
+ */
196
+ public static create(
197
+ documentStorageService: IDocumentDeltaStorageService,
198
+ controller: ReplayController,
199
+ ): IDocumentDeltaConnection {
200
+ const connection: IConnected = {
201
+ claims: ReplayDocumentDeltaConnection.claims,
202
+ clientId: "PseudoClientId",
203
+ existing: true,
204
+ initialMessages: [],
205
+ initialSignals: [],
206
+ initialClients: [],
207
+ maxMessageSize: ReplayDocumentDeltaConnection.ReplayMaxMessageSize,
208
+ mode: "read",
209
+ serviceConfiguration: {
210
+ blockSize: 64436,
211
+ maxMessageSize: ReplayDocumentDeltaConnection.ReplayMaxMessageSize,
212
+ },
213
+ supportedVersions: [ReplayDocumentDeltaConnection.replayProtocolVersion],
214
+ version: ReplayDocumentDeltaConnection.replayProtocolVersion,
215
+ };
216
+ const deltaConnection = new ReplayDocumentDeltaConnection(connection);
217
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
218
+ deltaConnection.fetchAndEmitOps(documentStorageService, controller);
219
+
220
+ return deltaConnection;
221
+ }
222
+
223
+ private static readonly replayProtocolVersion = "^0.1.0";
224
+ // Since the replay service never actually sends messages the size below is arbitrary
225
+ private static readonly ReplayMaxMessageSize = 16 * 1024;
226
+
227
+ private static readonly claims: ITokenClaims = {
228
+ documentId: ReplayDocumentId,
229
+ scopes: [ScopeType.DocRead],
230
+ tenantId: "",
231
+ user: {
232
+ id: "",
233
+ },
234
+ iat: Math.round(new Date().getTime() / 1000),
235
+ exp: Math.round(new Date().getTime() / 1000) + 60 * 60, // 1 hour expiration
236
+ ver: "1.0",
237
+ };
238
+
239
+ public get clientId(): string {
240
+ return this.details.clientId;
241
+ }
242
+
243
+ public get mode(): ConnectionMode {
244
+ return this.details.mode;
245
+ }
246
+
247
+ public get claims(): ITokenClaims {
248
+ return this.details.claims;
249
+ }
250
+
251
+ public get existing(): boolean {
252
+ return this.details.existing;
253
+ }
254
+
255
+ public get version(): string {
256
+ return this.details.version;
257
+ }
258
+
259
+ public get initialMessages(): ISequencedDocumentMessage[] {
260
+ return this.details.initialMessages;
261
+ }
262
+
263
+ public get initialSignals(): ISignalMessage[] {
264
+ return this.details.initialSignals;
265
+ }
266
+
267
+ public get initialClients(): ISignalClient[] {
268
+ return this.details.initialClients;
269
+ }
270
+
271
+ public get serviceConfiguration(): IClientConfiguration {
272
+ return this.details.serviceConfiguration;
273
+ }
274
+
275
+ public readonly maxMessageSize = ReplayDocumentDeltaConnection.ReplayMaxMessageSize;
276
+
277
+ constructor(public details: IConnected) {
278
+ super();
279
+ }
280
+
281
+ public submit(documentMessage: IDocumentMessage[]): void {
282
+ // ReplayDocumentDeltaConnection.submit() can't be called - client never sees its own join on,
283
+ // and thus can never move to sending ops.
284
+ throw new Error("ReplayDocumentDeltaConnection.submit() can't be called");
285
+ }
286
+
287
+ public async submitSignal(message: any) {}
288
+
289
+ private _disposed = false;
290
+ public get disposed() {
291
+ return this._disposed;
292
+ }
293
+ public dispose() {
294
+ this._disposed = true;
295
+ }
296
+
297
+ /**
298
+ * This gets the specified ops from the delta storage endpoint and replays them in the replayer.
299
+ */
300
+ private async fetchAndEmitOps(
301
+ documentStorageService: IDocumentDeltaStorageService,
302
+ controller: ReplayController,
303
+ ): Promise<void> {
304
+ let done;
305
+ let replayPromiseChain = Promise.resolve();
306
+
307
+ let currentOp = await controller.getStartingOpSequence();
308
+
309
+ do {
310
+ const fetchTo = controller.fetchTo(currentOp);
311
+
312
+ const abortController = new AbortController();
313
+ const stream = documentStorageService.fetchMessages(
314
+ currentOp + 1,
315
+ fetchTo,
316
+ abortController.signal,
317
+ );
318
+ do {
319
+ const result = await stream.read();
320
+
321
+ if (result.done) {
322
+ // No more ops. But, they can show up later, either because document was just created,
323
+ // or because another client keeps submitting new ops.
324
+ done = controller.isDoneFetch(currentOp, undefined);
325
+ if (!done) {
326
+ await delay(2000);
327
+ }
328
+ break;
329
+ }
330
+ replayPromiseChain = replayPromiseChain.then(async () =>
331
+ controller.replay((ops) => this.emit("op", ReplayDocumentId, ops), messages),
332
+ );
333
+
334
+ const messages = result.value;
335
+ currentOp += messages.length;
336
+ done = controller.isDoneFetch(currentOp, messages[messages.length - 1].timestamp);
337
+ } while (!done);
338
+
339
+ abortController.abort();
340
+ } while (!done);
341
+ return replayPromiseChain;
342
+ }
330
343
  }