@fluidframework/container-loader 2.0.0-internal.5.4.0 → 2.0.0-internal.6.1.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 (102) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/dist/connectionManager.d.ts +4 -4
  3. package/dist/connectionManager.d.ts.map +1 -1
  4. package/dist/connectionManager.js +57 -49
  5. package/dist/connectionManager.js.map +1 -1
  6. package/dist/connectionStateHandler.d.ts +15 -14
  7. package/dist/connectionStateHandler.d.ts.map +1 -1
  8. package/dist/connectionStateHandler.js +26 -28
  9. package/dist/connectionStateHandler.js.map +1 -1
  10. package/dist/container.d.ts +10 -5
  11. package/dist/container.d.ts.map +1 -1
  12. package/dist/container.js +183 -134
  13. package/dist/container.js.map +1 -1
  14. package/dist/containerContext.d.ts +2 -12
  15. package/dist/containerContext.d.ts.map +1 -1
  16. package/dist/containerContext.js +1 -20
  17. package/dist/containerContext.js.map +1 -1
  18. package/dist/containerStorageAdapter.js +3 -5
  19. package/dist/containerStorageAdapter.js.map +1 -1
  20. package/dist/contracts.d.ts +20 -7
  21. package/dist/contracts.d.ts.map +1 -1
  22. package/dist/contracts.js +3 -3
  23. package/dist/contracts.js.map +1 -1
  24. package/dist/debugLogger.js +2 -3
  25. package/dist/debugLogger.js.map +1 -1
  26. package/dist/deltaManager.d.ts +19 -6
  27. package/dist/deltaManager.d.ts.map +1 -1
  28. package/dist/deltaManager.js +67 -28
  29. package/dist/deltaManager.js.map +1 -1
  30. package/dist/deltaQueue.js +1 -2
  31. package/dist/deltaQueue.js.map +1 -1
  32. package/dist/loader.d.ts +12 -0
  33. package/dist/loader.d.ts.map +1 -1
  34. package/dist/loader.js +57 -42
  35. package/dist/loader.js.map +1 -1
  36. package/dist/packageVersion.d.ts +1 -1
  37. package/dist/packageVersion.js +1 -1
  38. package/dist/packageVersion.js.map +1 -1
  39. package/dist/protocol.d.ts +4 -2
  40. package/dist/protocol.d.ts.map +1 -1
  41. package/dist/protocol.js +25 -4
  42. package/dist/protocol.js.map +1 -1
  43. package/dist/utils.d.ts +8 -1
  44. package/dist/utils.d.ts.map +1 -1
  45. package/dist/utils.js +24 -6
  46. package/dist/utils.js.map +1 -1
  47. package/lib/connectionManager.d.ts +4 -4
  48. package/lib/connectionManager.d.ts.map +1 -1
  49. package/lib/connectionManager.js +58 -50
  50. package/lib/connectionManager.js.map +1 -1
  51. package/lib/connectionStateHandler.d.ts +15 -14
  52. package/lib/connectionStateHandler.d.ts.map +1 -1
  53. package/lib/connectionStateHandler.js +26 -28
  54. package/lib/connectionStateHandler.js.map +1 -1
  55. package/lib/container.d.ts +10 -5
  56. package/lib/container.d.ts.map +1 -1
  57. package/lib/container.js +182 -133
  58. package/lib/container.js.map +1 -1
  59. package/lib/containerContext.d.ts +2 -12
  60. package/lib/containerContext.d.ts.map +1 -1
  61. package/lib/containerContext.js +1 -20
  62. package/lib/containerContext.js.map +1 -1
  63. package/lib/containerStorageAdapter.js +3 -5
  64. package/lib/containerStorageAdapter.js.map +1 -1
  65. package/lib/contracts.d.ts +20 -7
  66. package/lib/contracts.d.ts.map +1 -1
  67. package/lib/contracts.js +3 -3
  68. package/lib/contracts.js.map +1 -1
  69. package/lib/debugLogger.js +2 -3
  70. package/lib/debugLogger.js.map +1 -1
  71. package/lib/deltaManager.d.ts +19 -6
  72. package/lib/deltaManager.d.ts.map +1 -1
  73. package/lib/deltaManager.js +67 -28
  74. package/lib/deltaManager.js.map +1 -1
  75. package/lib/deltaQueue.js +1 -2
  76. package/lib/deltaQueue.js.map +1 -1
  77. package/lib/loader.d.ts +12 -0
  78. package/lib/loader.d.ts.map +1 -1
  79. package/lib/loader.js +57 -42
  80. package/lib/loader.js.map +1 -1
  81. package/lib/packageVersion.d.ts +1 -1
  82. package/lib/packageVersion.js +1 -1
  83. package/lib/packageVersion.js.map +1 -1
  84. package/lib/protocol.d.ts +4 -2
  85. package/lib/protocol.d.ts.map +1 -1
  86. package/lib/protocol.js +25 -4
  87. package/lib/protocol.js.map +1 -1
  88. package/lib/utils.d.ts +8 -1
  89. package/lib/utils.d.ts.map +1 -1
  90. package/lib/utils.js +22 -5
  91. package/lib/utils.js.map +1 -1
  92. package/package.json +15 -19
  93. package/src/connectionManager.ts +53 -34
  94. package/src/connectionStateHandler.ts +30 -37
  95. package/src/container.ts +156 -76
  96. package/src/containerContext.ts +0 -24
  97. package/src/contracts.ts +27 -10
  98. package/src/deltaManager.ts +41 -18
  99. package/src/loader.ts +37 -23
  100. package/src/packageVersion.ts +1 -1
  101. package/src/protocol.ts +33 -2
  102. package/src/utils.ts +29 -0
@@ -7,13 +7,11 @@ import { v4 as uuid } from "uuid";
7
7
  import { IEventProvider } from "@fluidframework/common-definitions";
8
8
  import { ITelemetryProperties, ITelemetryErrorEvent } from "@fluidframework/core-interfaces";
9
9
  import {
10
- IDeltaHandlerStrategy,
10
+ ICriticalContainerError,
11
11
  IDeltaManager,
12
12
  IDeltaManagerEvents,
13
13
  IDeltaQueue,
14
- ICriticalContainerError,
15
14
  IThrottlingWarning,
16
- IConnectionDetailsInternal,
17
15
  } from "@fluidframework/container-definitions";
18
16
  import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
19
17
  import {
@@ -27,7 +25,6 @@ import {
27
25
  IDocumentDeltaStorageService,
28
26
  IDocumentService,
29
27
  DriverErrorType,
30
- IAnyDriverError,
31
28
  } from "@fluidframework/driver-definitions";
32
29
  import {
33
30
  IDocumentMessage,
@@ -44,14 +41,19 @@ import {
44
41
  DataProcessingError,
45
42
  UsageError,
46
43
  } from "@fluidframework/container-utils";
47
- import { IConnectionManagerFactoryArgs, IConnectionManager } from "./contracts";
44
+ import {
45
+ IConnectionDetailsInternal,
46
+ IConnectionManager,
47
+ IConnectionManagerFactoryArgs,
48
+ IConnectionStateChangeReason,
49
+ } from "./contracts";
48
50
  import { DeltaQueue } from "./deltaQueue";
49
51
  import { OnlyValidTermValue } from "./protocol";
50
52
 
51
53
  export interface IConnectionArgs {
52
54
  mode?: ConnectionMode;
53
55
  fetchOpsFromStorage?: boolean;
54
- reason: string;
56
+ reason: IConnectionStateChangeReason;
55
57
  }
56
58
 
57
59
  /**
@@ -62,8 +64,11 @@ export interface IDeltaManagerInternalEvents extends IDeltaManagerEvents {
62
64
  (event: "throttled", listener: (error: IThrottlingWarning) => void);
63
65
  (event: "closed" | "disposed", listener: (error?: ICriticalContainerError) => void);
64
66
  (event: "connect", listener: (details: IConnectionDetailsInternal, opsBehind?: number) => void);
65
- (event: "establishingConnection", listener: (reason: string) => void);
66
- (event: "cancelEstablishingConnection", listener: (reason: string) => void);
67
+ (event: "establishingConnection", listener: (reason: IConnectionStateChangeReason) => void);
68
+ (
69
+ event: "cancelEstablishingConnection",
70
+ listener: (reason: IConnectionStateChangeReason) => void,
71
+ );
67
72
  }
68
73
 
69
74
  /**
@@ -73,6 +78,21 @@ interface IBatchMetadata {
73
78
  batch?: boolean;
74
79
  }
75
80
 
81
+ /**
82
+ * Interface used to define a strategy for handling incoming delta messages
83
+ */
84
+ export interface IDeltaHandlerStrategy {
85
+ /**
86
+ * Processes the message.
87
+ */
88
+ process: (message: ISequencedDocumentMessage) => void;
89
+
90
+ /**
91
+ * Processes the signal.
92
+ */
93
+ processSignal: (message: ISignalMessage) => void;
94
+ }
95
+
76
96
  /**
77
97
  * Determines if message was sent by client, not service
78
98
  */
@@ -330,6 +350,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
330
350
  return {
331
351
  sequenceNumber: this.lastSequenceNumber,
332
352
  opsSize: this.opsSize > 0 ? this.opsSize : undefined,
353
+ deltaManagerState: this._disposed ? "disposed" : this._closed ? "closed" : "open",
333
354
  ...this.connectionManager.connectionProps,
334
355
  };
335
356
  }
@@ -383,15 +404,17 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
383
404
  reconnectionDelayHandler: (delayMs: number, error: unknown) =>
384
405
  this.emitDelayInfo(this.deltaStreamDelayId, delayMs, error),
385
406
  closeHandler: (error: any) => this.close(error),
386
- disconnectHandler: (reason: string, error?: IAnyDriverError) =>
387
- this.disconnectHandler(reason, error),
407
+ disconnectHandler: (reason: IConnectionStateChangeReason) =>
408
+ this.disconnectHandler(reason),
388
409
  connectHandler: (connection: IConnectionDetailsInternal) =>
389
410
  this.connectHandler(connection),
390
411
  pongHandler: (latency: number) => this.emit("pong", latency),
391
412
  readonlyChangeHandler: (readonly?: boolean) =>
392
413
  safeRaiseEvent(this, this.logger, "readonly", readonly),
393
- establishConnectionHandler: (reason: string) => this.establishingConnection(reason),
394
- cancelConnectionHandler: (reason: string) => this.cancelEstablishingConnection(reason),
414
+ establishConnectionHandler: (reason: IConnectionStateChangeReason) =>
415
+ this.establishingConnection(reason),
416
+ cancelConnectionHandler: (reason: IConnectionStateChangeReason) =>
417
+ this.cancelEstablishingConnection(reason),
395
418
  };
396
419
 
397
420
  this.connectionManager = createConnectionManager(props);
@@ -431,11 +454,11 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
431
454
  // - inbound & inboundSignal are resumed in attachOpHandler() when we have handler setup
432
455
  }
433
456
 
434
- private cancelEstablishingConnection(reason: string) {
457
+ private cancelEstablishingConnection(reason: IConnectionStateChangeReason) {
435
458
  this.emit("cancelEstablishingConnection", reason);
436
459
  }
437
460
 
438
- private establishingConnection(reason: string) {
461
+ private establishingConnection(reason: IConnectionStateChangeReason) {
439
462
  this.emit("establishingConnection", reason);
440
463
  }
441
464
 
@@ -495,7 +518,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
495
518
  minSequenceNumber: number,
496
519
  sequenceNumber: number,
497
520
  handler: IDeltaHandlerStrategy,
498
- prefetchType: "cached" | "all" | "none" = "none",
521
+ prefetchType: "sequenceNumber" | "cached" | "all" | "none" = "none",
499
522
  ) {
500
523
  this.initSequenceNumber = sequenceNumber;
501
524
  this.lastProcessedSequenceNumber = sequenceNumber;
@@ -569,7 +592,7 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
569
592
  // on the wire, we might be always behind.
570
593
  // See comment at the end of "connect" handler
571
594
  if (fetchOpsFromStorage) {
572
- this.fetchMissingDeltas(args.reason);
595
+ this.fetchMissingDeltas(args.reason.text);
573
596
  }
574
597
 
575
598
  this.connectionManager.connect(args.reason, args.mode);
@@ -755,9 +778,9 @@ export class DeltaManager<TConnectionManager extends IConnectionManager>
755
778
  }
756
779
  }
757
780
 
758
- private disconnectHandler(reason: string, error?: IAnyDriverError) {
781
+ private disconnectHandler(reason: IConnectionStateChangeReason) {
759
782
  this.messageBuffer.length = 0;
760
- this.emit("disconnect", reason, error);
783
+ this.emit("disconnect", reason);
761
784
  }
762
785
 
763
786
  /**
package/src/loader.ts CHANGED
@@ -37,7 +37,7 @@ import {
37
37
  IResolvedUrl,
38
38
  IUrlResolver,
39
39
  } from "@fluidframework/driver-definitions";
40
- import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
40
+ import { UsageError } from "@fluidframework/container-utils";
41
41
  import { Container, IPendingContainerState } from "./container";
42
42
  import { IParsedUrl, parseUrl } from "./utils";
43
43
  import { pkgVersion } from "./packageVersion";
@@ -45,11 +45,7 @@ import { ProtocolHandlerBuilder } from "./protocol";
45
45
  import { DebugLogger } from "./debugLogger";
46
46
 
47
47
  function canUseCache(request: IRequest): boolean {
48
- if (request.headers === undefined) {
49
- return true;
50
- }
51
-
52
- return request.headers[LoaderHeader.cache] !== false;
48
+ return request.headers?.[LoaderHeader.cache] === true;
53
49
  }
54
50
 
55
51
  function ensureResolvedUrlDefined(
@@ -68,6 +64,9 @@ export class RelativeLoader implements ILoader {
68
64
  private readonly loader: ILoader | undefined,
69
65
  ) {}
70
66
 
67
+ /**
68
+ * @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
69
+ */
71
70
  public get IFluidRouter(): IFluidRouter {
72
71
  return this;
73
72
  }
@@ -99,6 +98,9 @@ export class RelativeLoader implements ILoader {
99
98
  return this.loader.resolve(request);
100
99
  }
101
100
 
101
+ /**
102
+ * @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
103
+ */
102
104
  public async request(request: IRequest): Promise<IResponse> {
103
105
  if (request.url.startsWith("/")) {
104
106
  const container = await this.resolve(request);
@@ -348,6 +350,9 @@ export class Loader implements IHostLoader {
348
350
  });
349
351
  }
350
352
 
353
+ /**
354
+ * @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
355
+ */
351
356
  public get IFluidRouter(): IFluidRouter {
352
357
  return this;
353
358
  }
@@ -383,6 +388,9 @@ export class Loader implements IHostLoader {
383
388
  });
384
389
  }
385
390
 
391
+ /**
392
+ * @deprecated - Will be removed in future major release. Migrate all usage of IFluidRouter to the Container's IFluidRouter/request.
393
+ */
386
394
  public async request(request: IRequest): Promise<IResponse> {
387
395
  return PerformanceEvent.timedExecAsync(
388
396
  this.mc.logger,
@@ -456,11 +464,29 @@ export class Loader implements IHostLoader {
456
464
  // If set in both query string and headers, use query string. Also write the value from the query string into the header either way.
457
465
  request.headers[LoaderHeader.version] =
458
466
  parsed.version ?? request.headers[LoaderHeader.version];
467
+ const cacheHeader = request.headers[LoaderHeader.cache];
459
468
  const canCache =
460
- this.cachingEnabled &&
461
- request.headers[LoaderHeader.cache] !== false &&
469
+ // Take header value if present, else use ILoaderOptions.cache value
470
+ (cacheHeader !== undefined ? cacheHeader === true : this.cachingEnabled) &&
462
471
  pendingLocalState === undefined;
463
- const fromSequenceNumber = request.headers[LoaderHeader.sequenceNumber] ?? -1;
472
+ const fromSequenceNumber = request.headers[LoaderHeader.sequenceNumber] as
473
+ | number
474
+ | undefined;
475
+ const opsBeforeReturn = request.headers[LoaderHeader.loadMode]?.opsBeforeReturn as
476
+ | string
477
+ | undefined;
478
+
479
+ if (
480
+ opsBeforeReturn === "sequenceNumber" &&
481
+ (fromSequenceNumber === undefined || fromSequenceNumber < 0)
482
+ ) {
483
+ // If opsBeforeReturn is set to "sequenceNumber", then fromSequenceNumber should be set to a non-negative integer.
484
+ throw new UsageError("sequenceNumber must be set to a non-negative integer");
485
+ } else if (opsBeforeReturn !== "sequenceNumber" && fromSequenceNumber !== undefined) {
486
+ // If opsBeforeReturn is not set to "sequenceNumber", then fromSequenceNumber should be undefined (default value).
487
+ // In this case, we should throw an error since opsBeforeReturn is not explicitly set to "sequenceNumber".
488
+ throw new UsageError('opsBeforeReturn must be set to "sequenceNumber"');
489
+ }
464
490
 
465
491
  let container: Container;
466
492
  if (canCache) {
@@ -477,24 +503,11 @@ export class Loader implements IHostLoader {
477
503
  container = await this.loadContainer(request, resolvedAsFluid, pendingLocalState);
478
504
  }
479
505
 
480
- if (container.deltaManager.lastSequenceNumber <= fromSequenceNumber) {
481
- await new Promise<void>((resolve, reject) => {
482
- function opHandler(message: ISequencedDocumentMessage) {
483
- if (message.sequenceNumber > fromSequenceNumber) {
484
- resolve();
485
- container.removeListener("op", opHandler);
486
- }
487
- }
488
-
489
- container.on("op", opHandler);
490
- });
491
- }
492
-
493
506
  return { container, parsed };
494
507
  }
495
508
 
496
509
  private get cachingEnabled() {
497
- return this.services.options.cache !== false;
510
+ return this.services.options.cache === true;
498
511
  }
499
512
 
500
513
  private async loadContainer(
@@ -508,6 +521,7 @@ export class Loader implements IHostLoader {
508
521
  version: request.headers?.[LoaderHeader.version] ?? undefined,
509
522
  loadMode: request.headers?.[LoaderHeader.loadMode],
510
523
  pendingLocalState,
524
+ loadToSequenceNumber: request.headers?.[LoaderHeader.sequenceNumber],
511
525
  },
512
526
  {
513
527
  canReconnect: request.headers?.[LoaderHeader.reconnect],
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "2.0.0-internal.5.4.0";
9
+ export const pkgVersion = "2.0.0-internal.6.1.0";
package/src/protocol.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { IAudienceOwner } from "@fluidframework/container-definitions";
7
+ import { canBeCoalescedByService } from "@fluidframework/driver-utils";
7
8
  import {
8
9
  IProtocolHandler as IBaseProtocolHandler,
9
10
  IQuorumSnapshot,
@@ -11,8 +12,12 @@ import {
11
12
  } from "@fluidframework/protocol-base";
12
13
  import {
13
14
  IDocumentAttributes,
15
+ IProcessMessageResult,
16
+ ISequencedClient,
17
+ ISequencedDocumentMessage,
14
18
  ISignalClient,
15
19
  ISignalMessage,
20
+ MessageType,
16
21
  } from "@fluidframework/protocol-definitions";
17
22
 
18
23
  // "term" was an experimental feature that is being removed. The only safe value to use is 1.
@@ -44,12 +49,12 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
44
49
  attributes: IDocumentAttributes,
45
50
  quorumSnapshot: IQuorumSnapshot,
46
51
  sendProposal: (key: string, value: any) => number,
47
- readonly audience: IAudienceOwner,
52
+ public readonly audience: IAudienceOwner,
53
+ private readonly shouldClientHaveLeft: (clientId: string) => boolean,
48
54
  ) {
49
55
  super(
50
56
  attributes.minimumSequenceNumber,
51
57
  attributes.sequenceNumber,
52
- OnlyValidTermValue,
53
58
  quorumSnapshot.members,
54
59
  quorumSnapshot.proposals,
55
60
  quorumSnapshot.values,
@@ -66,6 +71,32 @@ export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandl
66
71
  }
67
72
  }
68
73
 
74
+ public processMessage(
75
+ message: ISequencedDocumentMessage,
76
+ local: boolean,
77
+ ): IProcessMessageResult {
78
+ const client: ISequencedClient | undefined = this.quorum.getMember(message.clientId);
79
+
80
+ // Check and report if we're getting messages from a clientId that we previously
81
+ // flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
82
+ if (message.clientId != null) {
83
+ if (client === undefined && message.type !== MessageType.ClientJoin) {
84
+ // pre-0.58 error message: messageClientIdMissingFromQuorum
85
+ throw new Error("Remote message's clientId is missing from the quorum");
86
+ }
87
+
88
+ // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
89
+ // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
90
+ // document we don't need to blow up aggressively.
91
+ if (this.shouldClientHaveLeft(message.clientId) && !canBeCoalescedByService(message)) {
92
+ // pre-0.58 error message: messageClientIdShouldHaveLeft
93
+ throw new Error("Remote message's clientId already should have left");
94
+ }
95
+ }
96
+
97
+ return super.processMessage(message, local);
98
+ }
99
+
69
100
  public processSignal(message: ISignalMessage) {
70
101
  const innerContent = message.content as { content: any; type: string };
71
102
  switch (innerContent.type) {
package/src/utils.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  import { ISummaryTree, ISnapshotTree, SummaryType } from "@fluidframework/protocol-definitions";
15
15
  import { LoggingError } from "@fluidframework/telemetry-utils";
16
16
  import {
17
+ CombinedAppAndProtocolSummary,
17
18
  DeltaStreamConnectionForbiddenError,
18
19
  isCombinedAppAndProtocolSummary,
19
20
  } from "@fluidframework/driver-utils";
@@ -51,6 +52,34 @@ export function parseUrl(url: string): IParsedUrl | undefined {
51
52
  : undefined;
52
53
  }
53
54
 
55
+ /**
56
+ * Combine the app summary and protocol summary in 1 tree.
57
+ * @param appSummary - Summary of the app.
58
+ * @param protocolSummary - Summary of the protocol.
59
+ * @internal
60
+ */
61
+ export function combineAppAndProtocolSummary(
62
+ appSummary: ISummaryTree,
63
+ protocolSummary: ISummaryTree,
64
+ ): CombinedAppAndProtocolSummary {
65
+ assert(
66
+ !isCombinedAppAndProtocolSummary(appSummary),
67
+ 0x5a8 /* app summary is already a combined tree! */,
68
+ );
69
+ assert(
70
+ !isCombinedAppAndProtocolSummary(protocolSummary),
71
+ 0x5a9 /* protocol summary is already a combined tree! */,
72
+ );
73
+ const createNewSummary: CombinedAppAndProtocolSummary = {
74
+ type: SummaryType.Tree,
75
+ tree: {
76
+ ".protocol": protocolSummary,
77
+ ".app": appSummary,
78
+ },
79
+ };
80
+ return createNewSummary;
81
+ }
82
+
54
83
  /**
55
84
  * Converts summary tree (for upload) to snapshot tree (for download).
56
85
  * Summary tree blobs contain contents, but snapshot tree blobs normally