@fluidframework/container-loader 2.0.0-internal.3.0.5 → 2.0.0-internal.3.1.1

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 (148) hide show
  1. package/.eslintrc.js +18 -21
  2. package/.mocharc.js +2 -2
  3. package/README.md +45 -43
  4. package/api-extractor.json +2 -2
  5. package/closeAndGetPendingLocalState.md +51 -0
  6. package/dist/audience.d.ts.map +1 -1
  7. package/dist/audience.js.map +1 -1
  8. package/dist/catchUpMonitor.d.ts.map +1 -1
  9. package/dist/catchUpMonitor.js.map +1 -1
  10. package/dist/collabWindowTracker.d.ts.map +1 -1
  11. package/dist/collabWindowTracker.js.map +1 -1
  12. package/dist/connectionManager.d.ts +2 -2
  13. package/dist/connectionManager.d.ts.map +1 -1
  14. package/dist/connectionManager.js +51 -24
  15. package/dist/connectionManager.js.map +1 -1
  16. package/dist/connectionState.d.ts.map +1 -1
  17. package/dist/connectionState.js.map +1 -1
  18. package/dist/connectionStateHandler.d.ts.map +1 -1
  19. package/dist/connectionStateHandler.js +35 -16
  20. package/dist/connectionStateHandler.js.map +1 -1
  21. package/dist/container.d.ts +1 -10
  22. package/dist/container.d.ts.map +1 -1
  23. package/dist/container.js +89 -44
  24. package/dist/container.js.map +1 -1
  25. package/dist/containerContext.d.ts.map +1 -1
  26. package/dist/containerContext.js +6 -2
  27. package/dist/containerContext.js.map +1 -1
  28. package/dist/containerStorageAdapter.d.ts.map +1 -1
  29. package/dist/containerStorageAdapter.js +2 -4
  30. package/dist/containerStorageAdapter.js.map +1 -1
  31. package/dist/contracts.d.ts.map +1 -1
  32. package/dist/contracts.js.map +1 -1
  33. package/dist/deltaManager.d.ts +3 -3
  34. package/dist/deltaManager.d.ts.map +1 -1
  35. package/dist/deltaManager.js +56 -27
  36. package/dist/deltaManager.js.map +1 -1
  37. package/dist/deltaManagerProxy.d.ts.map +1 -1
  38. package/dist/deltaManagerProxy.js.map +1 -1
  39. package/dist/deltaQueue.d.ts.map +1 -1
  40. package/dist/deltaQueue.js +4 -2
  41. package/dist/deltaQueue.js.map +1 -1
  42. package/dist/index.d.ts +1 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/loader.d.ts +3 -3
  46. package/dist/loader.d.ts.map +1 -1
  47. package/dist/loader.js +18 -15
  48. package/dist/loader.js.map +1 -1
  49. package/dist/packageVersion.d.ts +1 -1
  50. package/dist/packageVersion.js +1 -1
  51. package/dist/packageVersion.js.map +1 -1
  52. package/dist/protocol.d.ts.map +1 -1
  53. package/dist/protocol.js +2 -1
  54. package/dist/protocol.js.map +1 -1
  55. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  56. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  57. package/dist/quorum.d.ts.map +1 -1
  58. package/dist/quorum.js.map +1 -1
  59. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  60. package/dist/retriableDocumentStorageService.js +6 -2
  61. package/dist/retriableDocumentStorageService.js.map +1 -1
  62. package/dist/utils.d.ts.map +1 -1
  63. package/dist/utils.js +6 -4
  64. package/dist/utils.js.map +1 -1
  65. package/lib/audience.d.ts.map +1 -1
  66. package/lib/audience.js.map +1 -1
  67. package/lib/catchUpMonitor.d.ts.map +1 -1
  68. package/lib/catchUpMonitor.js.map +1 -1
  69. package/lib/collabWindowTracker.d.ts.map +1 -1
  70. package/lib/collabWindowTracker.js.map +1 -1
  71. package/lib/connectionManager.d.ts +2 -2
  72. package/lib/connectionManager.d.ts.map +1 -1
  73. package/lib/connectionManager.js +53 -26
  74. package/lib/connectionManager.js.map +1 -1
  75. package/lib/connectionState.d.ts.map +1 -1
  76. package/lib/connectionState.js.map +1 -1
  77. package/lib/connectionStateHandler.d.ts.map +1 -1
  78. package/lib/connectionStateHandler.js +35 -16
  79. package/lib/connectionStateHandler.js.map +1 -1
  80. package/lib/container.d.ts +1 -10
  81. package/lib/container.d.ts.map +1 -1
  82. package/lib/container.js +93 -48
  83. package/lib/container.js.map +1 -1
  84. package/lib/containerContext.d.ts.map +1 -1
  85. package/lib/containerContext.js +6 -2
  86. package/lib/containerContext.js.map +1 -1
  87. package/lib/containerStorageAdapter.d.ts.map +1 -1
  88. package/lib/containerStorageAdapter.js +2 -4
  89. package/lib/containerStorageAdapter.js.map +1 -1
  90. package/lib/contracts.d.ts.map +1 -1
  91. package/lib/contracts.js.map +1 -1
  92. package/lib/deltaManager.d.ts +3 -3
  93. package/lib/deltaManager.d.ts.map +1 -1
  94. package/lib/deltaManager.js +58 -29
  95. package/lib/deltaManager.js.map +1 -1
  96. package/lib/deltaManagerProxy.d.ts.map +1 -1
  97. package/lib/deltaManagerProxy.js.map +1 -1
  98. package/lib/deltaQueue.d.ts.map +1 -1
  99. package/lib/deltaQueue.js +4 -2
  100. package/lib/deltaQueue.js.map +1 -1
  101. package/lib/index.d.ts +1 -1
  102. package/lib/index.d.ts.map +1 -1
  103. package/lib/index.js.map +1 -1
  104. package/lib/loader.d.ts +3 -3
  105. package/lib/loader.d.ts.map +1 -1
  106. package/lib/loader.js +18 -15
  107. package/lib/loader.js.map +1 -1
  108. package/lib/packageVersion.d.ts +1 -1
  109. package/lib/packageVersion.js +1 -1
  110. package/lib/packageVersion.js.map +1 -1
  111. package/lib/protocol.d.ts.map +1 -1
  112. package/lib/protocol.js +2 -1
  113. package/lib/protocol.js.map +1 -1
  114. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  115. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  116. package/lib/quorum.d.ts.map +1 -1
  117. package/lib/quorum.js.map +1 -1
  118. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  119. package/lib/retriableDocumentStorageService.js +6 -2
  120. package/lib/retriableDocumentStorageService.js.map +1 -1
  121. package/lib/utils.d.ts.map +1 -1
  122. package/lib/utils.js +6 -4
  123. package/lib/utils.js.map +1 -1
  124. package/package.json +115 -114
  125. package/prettier.config.cjs +1 -1
  126. package/src/audience.ts +51 -46
  127. package/src/catchUpMonitor.ts +39 -37
  128. package/src/collabWindowTracker.ts +75 -70
  129. package/src/connectionManager.ts +1006 -944
  130. package/src/connectionState.ts +19 -19
  131. package/src/connectionStateHandler.ts +544 -465
  132. package/src/container.ts +2056 -1909
  133. package/src/containerContext.ts +350 -340
  134. package/src/containerStorageAdapter.ts +163 -153
  135. package/src/contracts.ts +155 -153
  136. package/src/deltaManager.ts +1069 -992
  137. package/src/deltaManagerProxy.ts +143 -137
  138. package/src/deltaQueue.ts +155 -151
  139. package/src/index.ts +14 -17
  140. package/src/loader.ts +428 -430
  141. package/src/packageVersion.ts +1 -1
  142. package/src/protocol.ts +93 -87
  143. package/src/protocolTreeDocumentStorageService.ts +30 -33
  144. package/src/quorum.ts +34 -34
  145. package/src/retriableDocumentStorageService.ts +118 -102
  146. package/src/utils.ts +89 -82
  147. package/tsconfig.esnext.json +6 -6
  148. package/tsconfig.json +8 -12
@@ -6,4 +6,4 @@
6
6
  */
7
7
 
8
8
  export const pkgName = "@fluidframework/container-loader";
9
- export const pkgVersion = "2.0.0-internal.3.0.5";
9
+ export const pkgVersion = "2.0.0-internal.3.1.1";
package/src/protocol.ts CHANGED
@@ -5,116 +5,122 @@
5
5
 
6
6
  import { IAudienceOwner } from "@fluidframework/container-definitions";
7
7
  import {
8
- ILocalSequencedClient,
9
- IProtocolHandler as IBaseProtocolHandler,
10
- IQuorumSnapshot,
11
- ProtocolOpHandler,
8
+ ILocalSequencedClient,
9
+ IProtocolHandler as IBaseProtocolHandler,
10
+ IQuorumSnapshot,
11
+ ProtocolOpHandler,
12
12
  } from "@fluidframework/protocol-base";
13
13
  import {
14
- IDocumentAttributes,
15
- IProcessMessageResult,
16
- ISequencedDocumentMessage,
17
- ISignalClient,
18
- ISignalMessage,
19
- MessageType,
14
+ IDocumentAttributes,
15
+ IProcessMessageResult,
16
+ ISequencedDocumentMessage,
17
+ ISignalClient,
18
+ ISignalMessage,
19
+ MessageType,
20
20
  } from "@fluidframework/protocol-definitions";
21
21
  import { canBeCoalescedByService } from "@fluidframework/driver-utils";
22
22
 
23
23
  // ADO: #1986: Start using enum from protocol-base.
24
24
  export enum SignalType {
25
- ClientJoin = "join", // same value as MessageType.ClientJoin,
26
- ClientLeave = "leave", // same value as MessageType.ClientLeave,
27
- Clear = "clear", // used only by client for synthetic signals
25
+ ClientJoin = "join", // same value as MessageType.ClientJoin,
26
+ ClientLeave = "leave", // same value as MessageType.ClientLeave,
27
+ Clear = "clear", // used only by client for synthetic signals
28
28
  }
29
29
 
30
30
  /**
31
31
  * Function to be used for creating a protocol handler.
32
32
  */
33
33
  export type ProtocolHandlerBuilder = (
34
- attributes: IDocumentAttributes,
35
- snapshot: IQuorumSnapshot,
36
- sendProposal: (key: string, value: any) => number,
34
+ attributes: IDocumentAttributes,
35
+ snapshot: IQuorumSnapshot,
36
+ sendProposal: (key: string, value: any) => number,
37
37
  ) => IProtocolHandler;
38
38
 
39
39
  export interface IProtocolHandler extends IBaseProtocolHandler {
40
- readonly audience: IAudienceOwner;
41
- processSignal(message: ISignalMessage);
40
+ readonly audience: IAudienceOwner;
41
+ processSignal(message: ISignalMessage);
42
42
  }
43
43
 
44
44
  export class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandler {
45
- constructor(
46
- attributes: IDocumentAttributes,
47
- quorumSnapshot: IQuorumSnapshot,
48
- sendProposal: (key: string, value: any) => number,
49
- readonly audience: IAudienceOwner,
50
- ) {
51
- super(
52
- attributes.minimumSequenceNumber,
53
- attributes.sequenceNumber,
54
- attributes.term,
55
- quorumSnapshot.members,
56
- quorumSnapshot.proposals,
57
- quorumSnapshot.values,
58
- sendProposal,
59
- );
45
+ constructor(
46
+ attributes: IDocumentAttributes,
47
+ quorumSnapshot: IQuorumSnapshot,
48
+ sendProposal: (key: string, value: any) => number,
49
+ readonly audience: IAudienceOwner,
50
+ ) {
51
+ super(
52
+ attributes.minimumSequenceNumber,
53
+ attributes.sequenceNumber,
54
+ attributes.term,
55
+ quorumSnapshot.members,
56
+ quorumSnapshot.proposals,
57
+ quorumSnapshot.values,
58
+ sendProposal,
59
+ );
60
60
 
61
- // Join / leave signals are ignored for "write" clients in favor of join / leave ops
62
- this.quorum.on("addMember", (clientId, details) => audience.addMember(clientId, details.client));
63
- this.quorum.on("removeMember", (clientId) => audience.removeMember(clientId));
64
- for (const [clientId, details] of this.quorum.getMembers()) {
65
- this.audience.addMember(clientId, details.client);
66
- }
67
- }
61
+ // Join / leave signals are ignored for "write" clients in favor of join / leave ops
62
+ this.quorum.on("addMember", (clientId, details) =>
63
+ audience.addMember(clientId, details.client),
64
+ );
65
+ this.quorum.on("removeMember", (clientId) => audience.removeMember(clientId));
66
+ for (const [clientId, details] of this.quorum.getMembers()) {
67
+ this.audience.addMember(clientId, details.client);
68
+ }
69
+ }
68
70
 
69
- public processMessage(message: ISequencedDocumentMessage, local: boolean): IProcessMessageResult {
70
- const client: ILocalSequencedClient | undefined = this.quorum.getMember(message.clientId);
71
+ public processMessage(
72
+ message: ISequencedDocumentMessage,
73
+ local: boolean,
74
+ ): IProcessMessageResult {
75
+ const client: ILocalSequencedClient | undefined = this.quorum.getMember(message.clientId);
71
76
 
72
- // Check and report if we're getting messages from a clientId that we previously
73
- // flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
74
- if (message.clientId != null) {
75
- if (client === undefined && message.type !== MessageType.ClientJoin) {
76
- // pre-0.58 error message: messageClientIdMissingFromQuorum
77
- throw new Error("Remote message's clientId is missing from the quorum");
78
- }
77
+ // Check and report if we're getting messages from a clientId that we previously
78
+ // flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
79
+ if (message.clientId != null) {
80
+ if (client === undefined && message.type !== MessageType.ClientJoin) {
81
+ // pre-0.58 error message: messageClientIdMissingFromQuorum
82
+ throw new Error("Remote message's clientId is missing from the quorum");
83
+ }
79
84
 
80
- if (client?.shouldHaveLeft === true && !canBeCoalescedByService(message)) {
81
- // pre-0.58 error message: messageClientIdShouldHaveLeft
82
- throw new Error("Remote message's clientId already should have left");
83
- }
84
- }
85
+ if (client?.shouldHaveLeft === true && !canBeCoalescedByService(message)) {
86
+ // pre-0.58 error message: messageClientIdShouldHaveLeft
87
+ throw new Error("Remote message's clientId already should have left");
88
+ }
89
+ }
85
90
 
86
- return super.processMessage(message, local);
87
- }
91
+ return super.processMessage(message, local);
92
+ }
88
93
 
89
- public processSignal(message: ISignalMessage) {
90
- const innerContent = message.content as { content: any; type: string; };
91
- switch (innerContent.type) {
92
- case SignalType.Clear: {
93
- const members = this.audience.getMembers();
94
- for (const [clientId, client] of members) {
95
- if (client.mode === "read") {
96
- this.audience.removeMember(clientId);
97
- }
98
- }
99
- break;
100
- }
101
- case SignalType.ClientJoin: {
102
- const newClient = innerContent.content as ISignalClient;
103
- // Ignore write clients - quorum will control such clients.
104
- if (newClient.client.mode === "read") {
105
- this.audience.addMember(newClient.clientId, newClient.client);
106
- }
107
- break;
108
- }
109
- case SignalType.ClientLeave: {
110
- const leftClientId = innerContent.content as string;
111
- // Ignore write clients - quorum will control such clients.
112
- if (this.audience.getMember(leftClientId)?.mode === "read") {
113
- this.audience.removeMember(leftClientId);
114
- }
115
- break;
116
- }
117
- default: break;
118
- }
119
- }
94
+ public processSignal(message: ISignalMessage) {
95
+ const innerContent = message.content as { content: any; type: string };
96
+ switch (innerContent.type) {
97
+ case SignalType.Clear: {
98
+ const members = this.audience.getMembers();
99
+ for (const [clientId, client] of members) {
100
+ if (client.mode === "read") {
101
+ this.audience.removeMember(clientId);
102
+ }
103
+ }
104
+ break;
105
+ }
106
+ case SignalType.ClientJoin: {
107
+ const newClient = innerContent.content as ISignalClient;
108
+ // Ignore write clients - quorum will control such clients.
109
+ if (newClient.client.mode === "read") {
110
+ this.audience.addMember(newClient.clientId, newClient.client);
111
+ }
112
+ break;
113
+ }
114
+ case SignalType.ClientLeave: {
115
+ const leftClientId = innerContent.content as string;
116
+ // Ignore write clients - quorum will control such clients.
117
+ if (this.audience.getMember(leftClientId)?.mode === "read") {
118
+ this.audience.removeMember(leftClientId);
119
+ }
120
+ break;
121
+ }
122
+ default:
123
+ break;
124
+ }
125
+ }
120
126
  }
@@ -4,42 +4,39 @@
4
4
  */
5
5
 
6
6
  import { IDisposable } from "@fluidframework/common-definitions";
7
- import {
8
- IDocumentStorageService,
9
- ISummaryContext,
10
- } from "@fluidframework/driver-definitions";
7
+ import { IDocumentStorageService, ISummaryContext } from "@fluidframework/driver-definitions";
11
8
  import { combineAppAndProtocolSummary } from "@fluidframework/driver-utils";
12
- import {
13
- ISummaryTree,
14
- } from "@fluidframework/protocol-definitions";
9
+ import { ISummaryTree } from "@fluidframework/protocol-definitions";
15
10
 
16
11
  export class ProtocolTreeStorageService implements IDocumentStorageService, IDisposable {
17
- constructor(
18
- private readonly internalStorageService: IDocumentStorageService & IDisposable,
19
- private readonly generateProtocolTree: () => ISummaryTree,
20
- ) {
21
- }
22
- public get policies() {
23
- return this.internalStorageService.policies;
24
- }
25
- public get repositoryUrl() {
26
- return this.internalStorageService.repositoryUrl;
27
- }
28
- public get disposed() {
29
- return this.internalStorageService.disposed;
30
- }
12
+ constructor(
13
+ private readonly internalStorageService: IDocumentStorageService & IDisposable,
14
+ private readonly generateProtocolTree: () => ISummaryTree,
15
+ ) {}
16
+ public get policies() {
17
+ return this.internalStorageService.policies;
18
+ }
19
+ public get repositoryUrl() {
20
+ return this.internalStorageService.repositoryUrl;
21
+ }
22
+ public get disposed() {
23
+ return this.internalStorageService.disposed;
24
+ }
31
25
 
32
- getSnapshotTree = this.internalStorageService.getSnapshotTree.bind(this.internalStorageService);
33
- getVersions = this.internalStorageService.getVersions.bind(this.internalStorageService);
34
- createBlob = this.internalStorageService.createBlob.bind(this.internalStorageService);
35
- readBlob = this.internalStorageService.readBlob.bind(this.internalStorageService);
36
- downloadSummary = this.internalStorageService.downloadSummary.bind(this.internalStorageService);
37
- dispose = this.internalStorageService.dispose.bind(this.internalStorageService);
26
+ getSnapshotTree = this.internalStorageService.getSnapshotTree.bind(this.internalStorageService);
27
+ getVersions = this.internalStorageService.getVersions.bind(this.internalStorageService);
28
+ createBlob = this.internalStorageService.createBlob.bind(this.internalStorageService);
29
+ readBlob = this.internalStorageService.readBlob.bind(this.internalStorageService);
30
+ downloadSummary = this.internalStorageService.downloadSummary.bind(this.internalStorageService);
31
+ dispose = this.internalStorageService.dispose.bind(this.internalStorageService);
38
32
 
39
- async uploadSummaryWithContext(summary: ISummaryTree, context: ISummaryContext): Promise<string> {
40
- return this.internalStorageService.uploadSummaryWithContext(
41
- combineAppAndProtocolSummary(summary, this.generateProtocolTree()),
42
- context,
43
- );
44
- }
33
+ async uploadSummaryWithContext(
34
+ summary: ISummaryTree,
35
+ context: ISummaryContext,
36
+ ): Promise<string> {
37
+ return this.internalStorageService.uploadSummaryWithContext(
38
+ combineAppAndProtocolSummary(summary, this.generateProtocolTree()),
39
+ context,
40
+ );
41
+ }
45
42
  }
package/src/quorum.ts CHANGED
@@ -5,55 +5,55 @@
5
5
  import { assert, EventForwarder, doIfNotDisposed } from "@fluidframework/common-utils";
6
6
  import { IFluidCodeDetails } from "@fluidframework/core-interfaces";
7
7
  import {
8
- ICommittedProposal,
9
- IQuorum,
10
- IQuorumEvents,
11
- ISequencedClient,
8
+ ICommittedProposal,
9
+ IQuorum,
10
+ IQuorumEvents,
11
+ ISequencedClient,
12
12
  } from "@fluidframework/protocol-definitions";
13
13
 
14
14
  /**
15
15
  * Proxies Quorum events.
16
16
  */
17
17
  export class QuorumProxy extends EventForwarder<IQuorumEvents> implements IQuorum {
18
- public readonly propose: (key: string, value: any) => Promise<void>;
19
- public readonly has: (key: string) => boolean;
20
- public readonly get: (key: string) => any;
21
- public readonly getMembers: () => Map<string, ISequencedClient>;
22
- public readonly getMember: (clientId: string) => ISequencedClient | undefined;
18
+ public readonly propose: (key: string, value: any) => Promise<void>;
19
+ public readonly has: (key: string) => boolean;
20
+ public readonly get: (key: string) => any;
21
+ public readonly getMembers: () => Map<string, ISequencedClient>;
22
+ public readonly getMember: (clientId: string) => ISequencedClient | undefined;
23
23
 
24
- constructor(quorum: IQuorum) {
25
- super(quorum);
24
+ constructor(quorum: IQuorum) {
25
+ super(quorum);
26
26
 
27
- // This is heavily used object, increase limit at which Node prints warnings.
28
- super.setMaxListeners(50);
27
+ // This is heavily used object, increase limit at which Node prints warnings.
28
+ super.setMaxListeners(50);
29
29
 
30
- this.propose = doIfNotDisposed(this, quorum.propose.bind(quorum));
31
- this.has = doIfNotDisposed(this, quorum.has.bind(quorum));
32
- this.get = doIfNotDisposed(this, quorum.get.bind(quorum));
33
- this.getMembers = doIfNotDisposed(this, quorum.getMembers.bind(quorum));
34
- this.getMember = doIfNotDisposed(this, quorum.getMember.bind(quorum));
35
- }
30
+ this.propose = doIfNotDisposed(this, quorum.propose.bind(quorum));
31
+ this.has = doIfNotDisposed(this, quorum.has.bind(quorum));
32
+ this.get = doIfNotDisposed(this, quorum.get.bind(quorum));
33
+ this.getMembers = doIfNotDisposed(this, quorum.getMembers.bind(quorum));
34
+ this.getMember = doIfNotDisposed(this, quorum.getMember.bind(quorum));
35
+ }
36
36
  }
37
37
 
38
38
  export function getCodeDetailsFromQuorumValues(
39
- quorumValues: [string, ICommittedProposal][],
39
+ quorumValues: [string, ICommittedProposal][],
40
40
  ): IFluidCodeDetails {
41
- const qValuesMap = new Map(quorumValues);
42
- const proposal = qValuesMap.get("code");
43
- assert(proposal !== undefined, 0x2dc /* "Cannot find code proposal" */);
44
- return proposal?.value as IFluidCodeDetails;
41
+ const qValuesMap = new Map(quorumValues);
42
+ const proposal = qValuesMap.get("code");
43
+ assert(proposal !== undefined, 0x2dc /* "Cannot find code proposal" */);
44
+ return proposal?.value as IFluidCodeDetails;
45
45
  }
46
46
 
47
47
  export function initQuorumValuesFromCodeDetails(
48
- source: IFluidCodeDetails,
48
+ source: IFluidCodeDetails,
49
49
  ): [string, ICommittedProposal][] {
50
- // Seed the base quorum to be an empty list with a code quorum set
51
- const committedCodeProposal: ICommittedProposal = {
52
- key: "code",
53
- value: source,
54
- approvalSequenceNumber: 0,
55
- commitSequenceNumber: 0,
56
- sequenceNumber: 0,
57
- };
58
- return [["code", committedCodeProposal]];
50
+ // Seed the base quorum to be an empty list with a code quorum set
51
+ const committedCodeProposal: ICommittedProposal = {
52
+ key: "code",
53
+ value: source,
54
+ approvalSequenceNumber: 0,
55
+ commitSequenceNumber: 0,
56
+ sequenceNumber: 0,
57
+ };
58
+ return [["code", committedCodeProposal]];
59
59
  }
@@ -6,124 +6,140 @@
6
6
  import { assert } from "@fluidframework/common-utils";
7
7
  import { GenericError } from "@fluidframework/container-utils";
8
8
  import {
9
- FetchSource,
10
- IDocumentStorageService,
11
- IDocumentStorageServicePolicies,
12
- ISummaryContext,
9
+ FetchSource,
10
+ IDocumentStorageService,
11
+ IDocumentStorageServicePolicies,
12
+ ISummaryContext,
13
13
  } from "@fluidframework/driver-definitions";
14
14
  import {
15
- ICreateBlobResponse,
16
- ISnapshotTree,
17
- ISummaryHandle,
18
- ISummaryTree,
19
- IVersion,
15
+ ICreateBlobResponse,
16
+ ISnapshotTree,
17
+ ISummaryHandle,
18
+ ISummaryTree,
19
+ IVersion,
20
20
  } from "@fluidframework/protocol-definitions";
21
21
  import { IDisposable, ITelemetryLogger } from "@fluidframework/common-definitions";
22
22
  import { runWithRetry } from "@fluidframework/driver-utils";
23
23
 
24
24
  export class RetriableDocumentStorageService implements IDocumentStorageService, IDisposable {
25
- private _disposed = false;
26
- constructor(
27
- private readonly internalStorageService: IDocumentStorageService,
28
- private readonly logger: ITelemetryLogger,
29
- ) {
30
- }
25
+ private _disposed = false;
26
+ constructor(
27
+ private readonly internalStorageService: IDocumentStorageService,
28
+ private readonly logger: ITelemetryLogger,
29
+ ) {}
31
30
 
32
- public get policies(): IDocumentStorageServicePolicies | undefined {
33
- return this.internalStorageService.policies;
34
- }
35
- public get disposed() { return this._disposed; }
36
- public dispose() {
37
- this._disposed = true;
38
- }
31
+ public get policies(): IDocumentStorageServicePolicies | undefined {
32
+ return this.internalStorageService.policies;
33
+ }
34
+ public get disposed() {
35
+ return this._disposed;
36
+ }
37
+ public dispose() {
38
+ this._disposed = true;
39
+ }
39
40
 
40
- public get repositoryUrl(): string {
41
- return this.internalStorageService.repositoryUrl;
42
- }
41
+ public get repositoryUrl(): string {
42
+ return this.internalStorageService.repositoryUrl;
43
+ }
43
44
 
44
- public async getSnapshotTree(version?: IVersion, scenarioName?: string): Promise<ISnapshotTree | null> {
45
- return this.runWithRetry(
46
- async () => this.internalStorageService.getSnapshotTree(version, scenarioName),
47
- "storage_getSnapshotTree",
48
- );
49
- }
45
+ public async getSnapshotTree(
46
+ version?: IVersion,
47
+ scenarioName?: string,
48
+ ): Promise<ISnapshotTree | null> {
49
+ return this.runWithRetry(
50
+ async () => this.internalStorageService.getSnapshotTree(version, scenarioName),
51
+ "storage_getSnapshotTree",
52
+ );
53
+ }
50
54
 
51
- public async readBlob(id: string): Promise<ArrayBufferLike> {
52
- return this.runWithRetry(
53
- async () => this.internalStorageService.readBlob(id),
54
- "storage_readBlob",
55
- );
56
- }
55
+ public async readBlob(id: string): Promise<ArrayBufferLike> {
56
+ return this.runWithRetry(
57
+ async () => this.internalStorageService.readBlob(id),
58
+ "storage_readBlob",
59
+ );
60
+ }
57
61
 
58
- public async getVersions(
59
- versionId: string | null,
60
- count: number,
61
- scenarioName?: string,
62
- fetchSource?: FetchSource,
63
- ): Promise<IVersion[]> {
64
- return this.runWithRetry(
65
- async () => this.internalStorageService.getVersions(versionId, count, scenarioName, fetchSource),
66
- "storage_getVersions",
67
- );
68
- }
62
+ public async getVersions(
63
+ versionId: string | null,
64
+ count: number,
65
+ scenarioName?: string,
66
+ fetchSource?: FetchSource,
67
+ ): Promise<IVersion[]> {
68
+ return this.runWithRetry(
69
+ async () =>
70
+ this.internalStorageService.getVersions(
71
+ versionId,
72
+ count,
73
+ scenarioName,
74
+ fetchSource,
75
+ ),
76
+ "storage_getVersions",
77
+ );
78
+ }
69
79
 
70
- public async uploadSummaryWithContext(summary: ISummaryTree, context: ISummaryContext): Promise<string> {
71
- // Not using retry loop here. Couple reasons:
72
- // 1. If client lost connectivity, then retry loop will result in uploading stale summary
73
- // by stale summarizer after connectivity comes back. It will cause failures for this client and for
74
- // real (new) summarizer. This problem in particular should be solved in future by supplying abort handle
75
- // on all APIs and caller (ContainerRuntime.submitSummary) aborting call on loss of connectivity
76
- // 2. Similar, if we get 429 with retryAfter = 10 minutes, it's likely not the right call to retry summary
77
- // upload in 10 minutes - it's better to keep processing ops and retry later. Though caller needs to take
78
- // retryAfter into account!
79
- // But retry loop is required for creation flow (Container.attach)
80
- assert((context.referenceSequenceNumber === 0) === (context.ackHandle === undefined),
81
- 0x251 /* "creation summary has to have seq=0 && handle === undefined" */);
82
- if (context.referenceSequenceNumber !== 0) {
83
- return this.internalStorageService.uploadSummaryWithContext(summary, context);
84
- }
80
+ public async uploadSummaryWithContext(
81
+ summary: ISummaryTree,
82
+ context: ISummaryContext,
83
+ ): Promise<string> {
84
+ // Not using retry loop here. Couple reasons:
85
+ // 1. If client lost connectivity, then retry loop will result in uploading stale summary
86
+ // by stale summarizer after connectivity comes back. It will cause failures for this client and for
87
+ // real (new) summarizer. This problem in particular should be solved in future by supplying abort handle
88
+ // on all APIs and caller (ContainerRuntime.submitSummary) aborting call on loss of connectivity
89
+ // 2. Similar, if we get 429 with retryAfter = 10 minutes, it's likely not the right call to retry summary
90
+ // upload in 10 minutes - it's better to keep processing ops and retry later. Though caller needs to take
91
+ // retryAfter into account!
92
+ // But retry loop is required for creation flow (Container.attach)
93
+ assert(
94
+ (context.referenceSequenceNumber === 0) === (context.ackHandle === undefined),
95
+ 0x251 /* "creation summary has to have seq=0 && handle === undefined" */,
96
+ );
97
+ if (context.referenceSequenceNumber !== 0) {
98
+ return this.internalStorageService.uploadSummaryWithContext(summary, context);
99
+ }
85
100
 
86
- // Creation flow with attachment blobs - need to do retries!
87
- return this.runWithRetry(
88
- async () => this.internalStorageService.uploadSummaryWithContext(summary, context),
89
- "storage_uploadSummaryWithContext",
90
- );
91
- }
101
+ // Creation flow with attachment blobs - need to do retries!
102
+ return this.runWithRetry(
103
+ async () => this.internalStorageService.uploadSummaryWithContext(summary, context),
104
+ "storage_uploadSummaryWithContext",
105
+ );
106
+ }
92
107
 
93
- public async downloadSummary(handle: ISummaryHandle): Promise<ISummaryTree> {
94
- return this.runWithRetry(
95
- async () => this.internalStorageService.downloadSummary(handle),
96
- "storage_downloadSummary",
97
- );
98
- }
108
+ public async downloadSummary(handle: ISummaryHandle): Promise<ISummaryTree> {
109
+ return this.runWithRetry(
110
+ async () => this.internalStorageService.downloadSummary(handle),
111
+ "storage_downloadSummary",
112
+ );
113
+ }
99
114
 
100
- public async createBlob(file: ArrayBufferLike): Promise<ICreateBlobResponse> {
101
- return this.runWithRetry(
102
- async () => this.internalStorageService.createBlob(file),
103
- "storage_createBlob",
104
- );
105
- }
115
+ public async createBlob(file: ArrayBufferLike): Promise<ICreateBlobResponse> {
116
+ return this.runWithRetry(
117
+ async () => this.internalStorageService.createBlob(file),
118
+ "storage_createBlob",
119
+ );
120
+ }
106
121
 
107
- private checkStorageDisposed(callName: string, error: unknown) {
108
- if (this._disposed) {
109
- this.logger.sendTelemetryEvent({
110
- eventName: `${callName}_abortedStorageDisposed`,
111
- fetchCallName: callName, // fetchCallName matches logs in runWithRetry.ts
112
- }, error);
113
- // pre-0.58 error message: storageServiceDisposedCannotRetry
114
- throw new GenericError("Storage Service is disposed. Cannot retry", { canRetry: false });
115
- }
116
- return;
117
- }
122
+ private checkStorageDisposed(callName: string, error: unknown) {
123
+ if (this._disposed) {
124
+ this.logger.sendTelemetryEvent(
125
+ {
126
+ eventName: `${callName}_abortedStorageDisposed`,
127
+ fetchCallName: callName, // fetchCallName matches logs in runWithRetry.ts
128
+ },
129
+ error,
130
+ );
131
+ // pre-0.58 error message: storageServiceDisposedCannotRetry
132
+ throw new GenericError("Storage Service is disposed. Cannot retry", {
133
+ canRetry: false,
134
+ });
135
+ }
136
+ return;
137
+ }
118
138
 
119
- private async runWithRetry<T>(api: () => Promise<T>, callName: string): Promise<T> {
120
- return runWithRetry(
121
- api,
122
- callName,
123
- this.logger,
124
- {
125
- onRetry: (_delayInMs: number, error: unknown) => this.checkStorageDisposed(callName, error),
126
- },
127
- );
128
- }
139
+ private async runWithRetry<T>(api: () => Promise<T>, callName: string): Promise<T> {
140
+ return runWithRetry(api, callName, this.logger, {
141
+ onRetry: (_delayInMs: number, error: unknown) =>
142
+ this.checkStorageDisposed(callName, error),
143
+ });
144
+ }
129
145
  }