@fluidframework/container-loader 1.0.0 → 1.1.0-76254

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 (85) hide show
  1. package/dist/connectionManager.d.ts.map +1 -1
  2. package/dist/connectionManager.js +3 -1
  3. package/dist/connectionManager.js.map +1 -1
  4. package/dist/connectionStateHandler.d.ts +44 -10
  5. package/dist/connectionStateHandler.d.ts.map +1 -1
  6. package/dist/connectionStateHandler.js +90 -35
  7. package/dist/connectionStateHandler.js.map +1 -1
  8. package/dist/container.d.ts +2 -2
  9. package/dist/container.d.ts.map +1 -1
  10. package/dist/container.js +48 -70
  11. package/dist/container.js.map +1 -1
  12. package/dist/containerStorageAdapter.d.ts +2 -2
  13. package/dist/containerStorageAdapter.d.ts.map +1 -1
  14. package/dist/containerStorageAdapter.js +4 -4
  15. package/dist/containerStorageAdapter.js.map +1 -1
  16. package/dist/contracts.d.ts +1 -1
  17. package/dist/contracts.js +1 -1
  18. package/dist/contracts.js.map +1 -1
  19. package/dist/deltaManager.d.ts.map +1 -1
  20. package/dist/deltaManager.js +9 -1
  21. package/dist/deltaManager.js.map +1 -1
  22. package/dist/deltaQueue.d.ts.map +1 -1
  23. package/dist/deltaQueue.js +8 -3
  24. package/dist/deltaQueue.js.map +1 -1
  25. package/dist/packageVersion.d.ts +1 -1
  26. package/dist/packageVersion.d.ts.map +1 -1
  27. package/dist/packageVersion.js +1 -1
  28. package/dist/packageVersion.js.map +1 -1
  29. package/dist/protocolTreeDocumentStorageService.d.ts +2 -2
  30. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  31. package/dist/retriableDocumentStorageService.d.ts +2 -2
  32. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  33. package/dist/retriableDocumentStorageService.js +4 -4
  34. package/dist/retriableDocumentStorageService.js.map +1 -1
  35. package/dist/utils.d.ts.map +1 -1
  36. package/dist/utils.js +3 -2
  37. package/dist/utils.js.map +1 -1
  38. package/lib/connectionManager.d.ts.map +1 -1
  39. package/lib/connectionManager.js +4 -2
  40. package/lib/connectionManager.js.map +1 -1
  41. package/lib/connectionStateHandler.d.ts +44 -10
  42. package/lib/connectionStateHandler.d.ts.map +1 -1
  43. package/lib/connectionStateHandler.js +90 -35
  44. package/lib/connectionStateHandler.js.map +1 -1
  45. package/lib/container.d.ts +2 -2
  46. package/lib/container.d.ts.map +1 -1
  47. package/lib/container.js +49 -71
  48. package/lib/container.js.map +1 -1
  49. package/lib/containerStorageAdapter.d.ts +2 -2
  50. package/lib/containerStorageAdapter.d.ts.map +1 -1
  51. package/lib/containerStorageAdapter.js +4 -4
  52. package/lib/containerStorageAdapter.js.map +1 -1
  53. package/lib/contracts.d.ts +1 -1
  54. package/lib/contracts.js +1 -1
  55. package/lib/contracts.js.map +1 -1
  56. package/lib/deltaManager.d.ts.map +1 -1
  57. package/lib/deltaManager.js +9 -1
  58. package/lib/deltaManager.js.map +1 -1
  59. package/lib/deltaQueue.d.ts.map +1 -1
  60. package/lib/deltaQueue.js +8 -3
  61. package/lib/deltaQueue.js.map +1 -1
  62. package/lib/packageVersion.d.ts +1 -1
  63. package/lib/packageVersion.d.ts.map +1 -1
  64. package/lib/packageVersion.js +1 -1
  65. package/lib/packageVersion.js.map +1 -1
  66. package/lib/protocolTreeDocumentStorageService.d.ts +2 -2
  67. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  68. package/lib/retriableDocumentStorageService.d.ts +2 -2
  69. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  70. package/lib/retriableDocumentStorageService.js +4 -4
  71. package/lib/retriableDocumentStorageService.js.map +1 -1
  72. package/lib/utils.d.ts.map +1 -1
  73. package/lib/utils.js +3 -2
  74. package/lib/utils.js.map +1 -1
  75. package/package.json +15 -28
  76. package/src/connectionManager.ts +4 -6
  77. package/src/connectionStateHandler.ts +113 -54
  78. package/src/container.ts +66 -89
  79. package/src/containerStorageAdapter.ts +4 -4
  80. package/src/contracts.ts +1 -1
  81. package/src/deltaManager.ts +8 -2
  82. package/src/deltaQueue.ts +7 -3
  83. package/src/packageVersion.ts +1 -1
  84. package/src/retriableDocumentStorageService.ts +4 -4
  85. package/src/utils.ts +3 -2
@@ -12,8 +12,8 @@ export declare class ProtocolTreeStorageService implements IDocumentStorageServi
12
12
  get policies(): import("@fluidframework/driver-definitions").IDocumentStorageServicePolicies | undefined;
13
13
  get repositoryUrl(): string;
14
14
  get disposed(): boolean;
15
- getSnapshotTree: (version?: import("@fluidframework/protocol-definitions").IVersion | undefined) => Promise<import("@fluidframework/protocol-definitions").ISnapshotTree | null>;
16
- getVersions: (versionId: string | null, count: number) => Promise<import("@fluidframework/protocol-definitions").IVersion[]>;
15
+ getSnapshotTree: (version?: import("@fluidframework/protocol-definitions").IVersion | undefined, scenarioName?: string | undefined) => Promise<import("@fluidframework/protocol-definitions").ISnapshotTree | null>;
16
+ getVersions: (versionId: string | null, count: number, scenarioName?: string | undefined) => Promise<import("@fluidframework/protocol-definitions").IVersion[]>;
17
17
  createBlob: (file: ArrayBufferLike) => Promise<import("@fluidframework/protocol-definitions").ICreateBlobResponse>;
18
18
  readBlob: (id: string) => Promise<ArrayBufferLike>;
19
19
  downloadSummary: (handle: import("@fluidframework/protocol-definitions").ISummaryHandle) => Promise<ISummaryTree>;
@@ -1 +1 @@
1
- {"version":3,"file":"protocolTreeDocumentStorageService.d.ts","sourceRoot":"","sources":["../src/protocolTreeDocumentStorageService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AACjE,OAAO,EACH,uBAAuB,EACvB,eAAe,EAClB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EACH,YAAY,EACf,MAAM,sCAAsC,CAAC;AAE9C,qBAAa,0BAA2B,YAAW,uBAAuB,EAAE,WAAW;IAE/E,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IACvC,OAAO,CAAC,QAAQ,CAAC,oBAAoB;gBADpB,sBAAsB,EAAE,uBAAuB,GAAG,WAAW,EAC7D,oBAAoB,EAAE,MAAM,YAAY;IAG7D,IAAW,QAAQ,6FAElB;IACD,IAAW,aAAa,WAEvB;IACD,IAAW,QAAQ,YAElB;IAED,eAAe,kKAAiF;IAChG,WAAW,kHAA6E;IACxF,UAAU,yGAA4E;IACtF,QAAQ,2CAA0E;IAClF,eAAe,mGAAiF;IAChG,OAAO,sCAAyE;IAE1E,wBAAwB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;CAMnG"}
1
+ {"version":3,"file":"protocolTreeDocumentStorageService.d.ts","sourceRoot":"","sources":["../src/protocolTreeDocumentStorageService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AACjE,OAAO,EACH,uBAAuB,EACvB,eAAe,EAClB,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EACH,YAAY,EACf,MAAM,sCAAsC,CAAC;AAE9C,qBAAa,0BAA2B,YAAW,uBAAuB,EAAE,WAAW;IAE/E,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IACvC,OAAO,CAAC,QAAQ,CAAC,oBAAoB;gBADpB,sBAAsB,EAAE,uBAAuB,GAAG,WAAW,EAC7D,oBAAoB,EAAE,MAAM,YAAY;IAG7D,IAAW,QAAQ,6FAElB;IACD,IAAW,aAAa,WAEvB;IACD,IAAW,QAAQ,YAElB;IAED,eAAe,qMAAiF;IAChG,WAAW,qJAA6E;IACxF,UAAU,yGAA4E;IACtF,QAAQ,2CAA0E;IAClF,eAAe,mGAAiF;IAChG,OAAO,sCAAyE;IAE1E,wBAAwB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;CAMnG"}
@@ -14,9 +14,9 @@ export declare class RetriableDocumentStorageService implements IDocumentStorage
14
14
  get disposed(): boolean;
15
15
  dispose(): void;
16
16
  get repositoryUrl(): string;
17
- getSnapshotTree(version?: IVersion): Promise<ISnapshotTree | null>;
17
+ getSnapshotTree(version?: IVersion, scenarioName?: string): Promise<ISnapshotTree | null>;
18
18
  readBlob(id: string): Promise<ArrayBufferLike>;
19
- getVersions(versionId: string | null, count: number): Promise<IVersion[]>;
19
+ getVersions(versionId: string | null, count: number, scenarioName?: string): Promise<IVersion[]>;
20
20
  uploadSummaryWithContext(summary: ISummaryTree, context: ISummaryContext): Promise<string>;
21
21
  downloadSummary(handle: ISummaryHandle): Promise<ISummaryTree>;
22
22
  createBlob(file: ArrayBufferLike): Promise<ICreateBlobResponse>;
@@ -1 +1 @@
1
- {"version":3,"file":"retriableDocumentStorageService.d.ts","sourceRoot":"","sources":["../src/retriableDocumentStorageService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EACH,uBAAuB,EACvB,+BAA+B,EAC/B,eAAe,EAClB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACH,mBAAmB,EACnB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,QAAQ,EACX,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAGnF,qBAAa,+BAAgC,YAAW,uBAAuB,EAAE,WAAW;IAGpF,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAH3B,OAAO,CAAC,SAAS,CAAS;gBAEL,sBAAsB,EAAE,uBAAuB,EAC/C,MAAM,EAAE,gBAAgB;IAI7C,IAAW,QAAQ,IAAI,+BAA+B,GAAG,SAAS,CAEjE;IACD,IAAW,QAAQ,YAA6B;IACzC,OAAO;IAId,IAAW,aAAa,IAAI,MAAM,CAEjC;IAEY,eAAe,CAAC,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAOlE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAO9C,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAOzE,wBAAwB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAuB1F,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAO9D,UAAU,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAO5E,OAAO,CAAC,oBAAoB;YAQd,YAAY;CAU7B"}
1
+ {"version":3,"file":"retriableDocumentStorageService.d.ts","sourceRoot":"","sources":["../src/retriableDocumentStorageService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EACH,uBAAuB,EACvB,+BAA+B,EAC/B,eAAe,EAClB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACH,mBAAmB,EACnB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,QAAQ,EACX,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AAGnF,qBAAa,+BAAgC,YAAW,uBAAuB,EAAE,WAAW;IAGpF,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAH3B,OAAO,CAAC,SAAS,CAAS;gBAEL,sBAAsB,EAAE,uBAAuB,EAC/C,MAAM,EAAE,gBAAgB;IAI7C,IAAW,QAAQ,IAAI,+BAA+B,GAAG,SAAS,CAEjE;IACD,IAAW,QAAQ,YAA6B;IACzC,OAAO;IAId,IAAW,aAAa,IAAI,MAAM,CAEjC;IAEY,eAAe,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAOzF,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAO9C,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAOhG,wBAAwB,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAuB1F,eAAe,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAO9D,UAAU,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAO5E,OAAO,CAAC,oBAAoB;YAQd,YAAY;CAU7B"}
@@ -21,14 +21,14 @@ export class RetriableDocumentStorageService {
21
21
  get repositoryUrl() {
22
22
  return this.internalStorageService.repositoryUrl;
23
23
  }
24
- async getSnapshotTree(version) {
25
- return this.runWithRetry(async () => this.internalStorageService.getSnapshotTree(version), "storage_getSnapshotTree");
24
+ async getSnapshotTree(version, scenarioName) {
25
+ return this.runWithRetry(async () => this.internalStorageService.getSnapshotTree(version, scenarioName), "storage_getSnapshotTree");
26
26
  }
27
27
  async readBlob(id) {
28
28
  return this.runWithRetry(async () => this.internalStorageService.readBlob(id), "storage_readBlob");
29
29
  }
30
- async getVersions(versionId, count) {
31
- return this.runWithRetry(async () => this.internalStorageService.getVersions(versionId, count), "storage_getVersions");
30
+ async getVersions(versionId, count, scenarioName) {
31
+ return this.runWithRetry(async () => this.internalStorageService.getVersions(versionId, count, scenarioName), "storage_getVersions");
32
32
  }
33
33
  async uploadSummaryWithContext(summary, context) {
34
34
  // Not using retry loop here. Couple reasons:
@@ -1 +1 @@
1
- {"version":3,"file":"retriableDocumentStorageService.js","sourceRoot":"","sources":["../src/retriableDocumentStorageService.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\nimport { assert } from \"@fluidframework/common-utils\";\nimport { GenericError } from \"@fluidframework/container-utils\";\nimport {\n IDocumentStorageService,\n IDocumentStorageServicePolicies,\n ISummaryContext,\n} from \"@fluidframework/driver-definitions\";\nimport {\n ICreateBlobResponse,\n ISnapshotTree,\n ISummaryHandle,\n ISummaryTree,\n IVersion,\n} from \"@fluidframework/protocol-definitions\";\nimport { IDisposable, ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { runWithRetry } from \"@fluidframework/driver-utils\";\n\nexport class RetriableDocumentStorageService implements IDocumentStorageService, IDisposable {\n private _disposed = false;\n constructor(\n private readonly internalStorageService: IDocumentStorageService,\n private readonly logger: ITelemetryLogger,\n ) {\n }\n\n public get policies(): IDocumentStorageServicePolicies | undefined {\n return this.internalStorageService.policies;\n }\n public get disposed() { return this._disposed; }\n public dispose() {\n this._disposed = true;\n }\n\n public get repositoryUrl(): string {\n return this.internalStorageService.repositoryUrl;\n }\n\n public async getSnapshotTree(version?: IVersion): Promise<ISnapshotTree | null> {\n return this.runWithRetry(\n async () => this.internalStorageService.getSnapshotTree(version),\n \"storage_getSnapshotTree\",\n );\n }\n\n public async readBlob(id: string): Promise<ArrayBufferLike> {\n return this.runWithRetry(\n async () => this.internalStorageService.readBlob(id),\n \"storage_readBlob\",\n );\n }\n\n public async getVersions(versionId: string | null, count: number): Promise<IVersion[]> {\n return this.runWithRetry(\n async () => this.internalStorageService.getVersions(versionId, count),\n \"storage_getVersions\",\n );\n }\n\n public async uploadSummaryWithContext(summary: ISummaryTree, context: ISummaryContext): Promise<string> {\n // Not using retry loop here. Couple reasons:\n // 1. If client lost connectivity, then retry loop will result in uploading stale summary\n // by stale summarizer after connectivity comes back. It will cause failures for this client and for\n // real (new) summarizer. This problem in particular should be solved in future by supplying abort handle\n // on all APIs and caller (ContainerRuntime.submitSummary) aborting call on loss of connectivity\n // 2. Similar, if we get 429 with retryAfter = 10 minutes, it's likely not the right call to retry summary\n // upload in 10 minutes - it's better to keep processing ops and retry later. Though caller needs to take\n // retryAfter into account!\n // But retry loop is required for creation flow (Container.attach)\n assert((context.referenceSequenceNumber === 0) === (context.ackHandle === undefined),\n 0x251 /* \"creation summary has to have seq=0 && handle === undefined\" */);\n if (context.referenceSequenceNumber !== 0) {\n return this.internalStorageService.uploadSummaryWithContext(summary, context);\n }\n\n // Creation flow with attachment blobs - need to do retries!\n return this.runWithRetry(\n async () => this.internalStorageService.uploadSummaryWithContext(summary, context),\n \"storage_uploadSummaryWithContext\",\n );\n }\n\n public async downloadSummary(handle: ISummaryHandle): Promise<ISummaryTree> {\n return this.runWithRetry(\n async () => this.internalStorageService.downloadSummary(handle),\n \"storage_downloadSummary\",\n );\n }\n\n public async createBlob(file: ArrayBufferLike): Promise<ICreateBlobResponse> {\n return this.runWithRetry(\n async () => this.internalStorageService.createBlob(file),\n \"storage_createBlob\",\n );\n }\n\n private checkStorageDisposed() {\n if (this._disposed) {\n // pre-0.58 error message: storageServiceDisposedCannotRetry\n throw new GenericError(\"Storage Service is disposed. Cannot retry\", { canRetry: false });\n }\n return undefined;\n }\n\n private async runWithRetry<T>(api: () => Promise<T>, callName: string): Promise<T> {\n return runWithRetry(\n api,\n callName,\n this.logger,\n {\n onRetry: () => this.checkStorageDisposed(),\n },\n );\n }\n}\n"]}
1
+ {"version":3,"file":"retriableDocumentStorageService.js","sourceRoot":"","sources":["../src/retriableDocumentStorageService.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\nimport { assert } from \"@fluidframework/common-utils\";\nimport { GenericError } from \"@fluidframework/container-utils\";\nimport {\n IDocumentStorageService,\n IDocumentStorageServicePolicies,\n ISummaryContext,\n} from \"@fluidframework/driver-definitions\";\nimport {\n ICreateBlobResponse,\n ISnapshotTree,\n ISummaryHandle,\n ISummaryTree,\n IVersion,\n} from \"@fluidframework/protocol-definitions\";\nimport { IDisposable, ITelemetryLogger } from \"@fluidframework/common-definitions\";\nimport { runWithRetry } from \"@fluidframework/driver-utils\";\n\nexport class RetriableDocumentStorageService implements IDocumentStorageService, IDisposable {\n private _disposed = false;\n constructor(\n private readonly internalStorageService: IDocumentStorageService,\n private readonly logger: ITelemetryLogger,\n ) {\n }\n\n public get policies(): IDocumentStorageServicePolicies | undefined {\n return this.internalStorageService.policies;\n }\n public get disposed() { return this._disposed; }\n public dispose() {\n this._disposed = true;\n }\n\n public get repositoryUrl(): string {\n return this.internalStorageService.repositoryUrl;\n }\n\n public async getSnapshotTree(version?: IVersion, scenarioName?: string): Promise<ISnapshotTree | null> {\n return this.runWithRetry(\n async () => this.internalStorageService.getSnapshotTree(version, scenarioName),\n \"storage_getSnapshotTree\",\n );\n }\n\n public async readBlob(id: string): Promise<ArrayBufferLike> {\n return this.runWithRetry(\n async () => this.internalStorageService.readBlob(id),\n \"storage_readBlob\",\n );\n }\n\n public async getVersions(versionId: string | null, count: number, scenarioName?: string): Promise<IVersion[]> {\n return this.runWithRetry(\n async () => this.internalStorageService.getVersions(versionId, count, scenarioName),\n \"storage_getVersions\",\n );\n }\n\n public async uploadSummaryWithContext(summary: ISummaryTree, context: ISummaryContext): Promise<string> {\n // Not using retry loop here. Couple reasons:\n // 1. If client lost connectivity, then retry loop will result in uploading stale summary\n // by stale summarizer after connectivity comes back. It will cause failures for this client and for\n // real (new) summarizer. This problem in particular should be solved in future by supplying abort handle\n // on all APIs and caller (ContainerRuntime.submitSummary) aborting call on loss of connectivity\n // 2. Similar, if we get 429 with retryAfter = 10 minutes, it's likely not the right call to retry summary\n // upload in 10 minutes - it's better to keep processing ops and retry later. Though caller needs to take\n // retryAfter into account!\n // But retry loop is required for creation flow (Container.attach)\n assert((context.referenceSequenceNumber === 0) === (context.ackHandle === undefined),\n 0x251 /* \"creation summary has to have seq=0 && handle === undefined\" */);\n if (context.referenceSequenceNumber !== 0) {\n return this.internalStorageService.uploadSummaryWithContext(summary, context);\n }\n\n // Creation flow with attachment blobs - need to do retries!\n return this.runWithRetry(\n async () => this.internalStorageService.uploadSummaryWithContext(summary, context),\n \"storage_uploadSummaryWithContext\",\n );\n }\n\n public async downloadSummary(handle: ISummaryHandle): Promise<ISummaryTree> {\n return this.runWithRetry(\n async () => this.internalStorageService.downloadSummary(handle),\n \"storage_downloadSummary\",\n );\n }\n\n public async createBlob(file: ArrayBufferLike): Promise<ICreateBlobResponse> {\n return this.runWithRetry(\n async () => this.internalStorageService.createBlob(file),\n \"storage_createBlob\",\n );\n }\n\n private checkStorageDisposed() {\n if (this._disposed) {\n // pre-0.58 error message: storageServiceDisposedCannotRetry\n throw new GenericError(\"Storage Service is disposed. Cannot retry\", { canRetry: false });\n }\n return undefined;\n }\n\n private async runWithRetry<T>(api: () => Promise<T>, callName: string): Promise<T> {\n return runWithRetry(\n api,\n callName,\n this.logger,\n {\n onRetry: () => this.checkStorageDisposed(),\n },\n );\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAe,MAAM,sCAAsC,CAAC;AAIhG,MAAM,WAAW,6BAA8B,SAAQ,aAAa;IAChE,aAAa,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAAC;KAAE,CAAC;IACpD,KAAK,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,6BAA6B,CAAC;KAAE,CAAC;CAC7D;AAED,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACtC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAW5D;AAqDD;;;;GAIG;AACH,wBAAgB,0CAA0C,CACtD,mBAAmB,EAAE,YAAY,EACjC,cAAc,EAAE,YAAY,GAC7B,6BAA6B,CAW/B;AAID,eAAO,MAAM,sCAAsC,8BAA+B,YAAY,kCAU7F,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,aAAa,GAAG,aAAa,CAE9E"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAe,MAAM,sCAAsC,CAAC;AAKhG,MAAM,WAAW,6BAA8B,SAAQ,aAAa;IAChE,aAAa,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAAC;KAAE,CAAC;IACpD,KAAK,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,6BAA6B,CAAC;KAAE,CAAC;CAC7D;AAED,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACtC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAW5D;AAqDD;;;;GAIG;AACH,wBAAgB,0CAA0C,CACtD,mBAAmB,EAAE,YAAY,EACjC,cAAc,EAAE,YAAY,GAC7B,6BAA6B,CAW/B;AAID,eAAO,MAAM,sCAAsC,8BAA+B,YAAY,kCAU7F,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,aAAa,GAAG,aAAa,CAE9E"}
package/lib/utils.js CHANGED
@@ -6,11 +6,12 @@ import { parse } from "url";
6
6
  import { v4 as uuid } from "uuid";
7
7
  import { assert, stringToBuffer, Uint8ArrayToArrayBuffer, unreachableCase, } from "@fluidframework/common-utils";
8
8
  import { SummaryType } from "@fluidframework/protocol-definitions";
9
+ import { LoggingError } from "@fluidframework/telemetry-utils";
9
10
  export function parseUrl(url) {
10
11
  var _a;
11
12
  const parsed = parse(url, true);
12
13
  if (typeof parsed.pathname !== "string") {
13
- throw new Error("Failed to parse pathname");
14
+ throw new LoggingError("Failed to parse pathname");
14
15
  }
15
16
  const query = (_a = parsed.search) !== null && _a !== void 0 ? _a : "";
16
17
  const regex = /^\/([^/]*\/[^/]*)(\/?.*)$/;
@@ -57,7 +58,7 @@ function convertSummaryToSnapshotWithEmbeddedBlobContents(summary) {
57
58
  break;
58
59
  }
59
60
  case SummaryType.Handle:
60
- throw new Error("No handles should be there in summary in detached container!!");
61
+ throw new LoggingError("No handles should be there in summary in detached container!!");
61
62
  break;
62
63
  default: {
63
64
  unreachableCase(summaryObject, `Unknown tree type ${summaryObject.type}`);
package/lib/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.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\nimport { parse } from \"url\";\nimport { v4 as uuid } from \"uuid\";\nimport {\n assert,\n stringToBuffer,\n Uint8ArrayToArrayBuffer,\n unreachableCase,\n} from \"@fluidframework/common-utils\";\nimport { ISummaryTree, ISnapshotTree, SummaryType } from \"@fluidframework/protocol-definitions\";\n\n// This is used when we rehydrate a container from the snapshot. Here we put the blob contents\n// in separate property: blobContents.\nexport interface ISnapshotTreeWithBlobContents extends ISnapshotTree {\n blobsContents: { [path: string]: ArrayBufferLike; };\n trees: { [path: string]: ISnapshotTreeWithBlobContents; };\n}\n\nexport interface IParsedUrl {\n id: string;\n path: string;\n query: string;\n /**\n * Null means do not use snapshots, undefined means load latest snapshot\n * otherwise it's version ID passed to IDocumentStorageService.getVersions() to figure out what snapshot to use.\n * If needed, can add undefined which is treated by Container.load() as load latest snapshot.\n */\n version: string | null | undefined;\n}\n\nexport function parseUrl(url: string): IParsedUrl | undefined {\n const parsed = parse(url, true);\n if (typeof parsed.pathname !== \"string\") {\n throw new Error(\"Failed to parse pathname\");\n }\n const query = parsed.search ?? \"\";\n const regex = /^\\/([^/]*\\/[^/]*)(\\/?.*)$/;\n const match = regex.exec(parsed.pathname);\n return (match?.length === 3)\n ? { id: match[1], path: match[2], query, version: parsed.query.version as string }\n : undefined;\n}\n\n/**\n * Converts summary tree (for upload) to snapshot tree (for download).\n * Summary tree blobs contain contents, but snapshot tree blobs normally\n * contain IDs pointing to storage. This will create 2 blob entries in the\n * snapshot tree for each blob in the summary tree. One will be the regular\n * path pointing to a uniquely generated ID. Then there will be another\n * entry with the path as that uniquely generated ID, and value as the\n * blob contents as a base-64 string.\n * @param summary - summary to convert\n */\nfunction convertSummaryToSnapshotWithEmbeddedBlobContents(\n summary: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n const treeNode: ISnapshotTreeWithBlobContents = {\n blobs: {},\n blobsContents: {},\n trees: {},\n id: uuid(),\n unreferenced: summary.unreferenced,\n };\n const keys = Object.keys(summary.tree);\n for (const key of keys) {\n const summaryObject = summary.tree[key];\n\n switch (summaryObject.type) {\n case SummaryType.Tree: {\n treeNode.trees[key] = convertSummaryToSnapshotWithEmbeddedBlobContents(summaryObject);\n break;\n }\n case SummaryType.Attachment:\n treeNode.blobs[key] = summaryObject.id;\n break;\n case SummaryType.Blob: {\n const blobId = uuid();\n treeNode.blobs[key] = blobId;\n const contentBuffer = typeof summaryObject.content === \"string\" ?\n stringToBuffer(summaryObject.content, \"utf8\") : Uint8ArrayToArrayBuffer(summaryObject.content);\n treeNode.blobsContents[blobId] = contentBuffer;\n break;\n }\n case SummaryType.Handle:\n throw new Error(\"No handles should be there in summary in detached container!!\");\n break;\n default: {\n unreachableCase(summaryObject, `Unknown tree type ${(summaryObject as any).type}`);\n }\n }\n }\n return treeNode;\n}\n\n/**\n * Combine and convert protocol and app summary tree to format which is readable by container while rehydrating.\n * @param protocolSummaryTree - Protocol Summary Tree\n * @param appSummaryTree - App Summary Tree\n */\nexport function convertProtocolAndAppSummaryToSnapshotTree(\n protocolSummaryTree: ISummaryTree,\n appSummaryTree: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n // Shallow copy is fine, since we are doing a deep clone below.\n const combinedSummary: ISummaryTree = {\n type: SummaryType.Tree,\n tree: { ...appSummaryTree.tree },\n };\n\n combinedSummary.tree[\".protocol\"] = protocolSummaryTree;\n const snapshotTreeWithBlobContents =\n convertSummaryToSnapshotWithEmbeddedBlobContents(combinedSummary);\n return snapshotTreeWithBlobContents;\n}\n\n// This function converts the snapshot taken in detached container(by serialize api) to snapshotTree with which\n// a detached container can be rehydrated.\nexport const getSnapshotTreeFromSerializedContainer = (detachedContainerSnapshot: ISummaryTree) => {\n const protocolSummaryTree = detachedContainerSnapshot.tree[\".protocol\"] as ISummaryTree;\n const appSummaryTree = detachedContainerSnapshot.tree[\".app\"] as ISummaryTree;\n assert(protocolSummaryTree !== undefined && appSummaryTree !== undefined,\n 0x1e0 /* \"Protocol and App summary trees should be present\" */);\n const snapshotTreeWithBlobContents = convertProtocolAndAppSummaryToSnapshotTree(\n protocolSummaryTree,\n appSummaryTree,\n );\n return snapshotTreeWithBlobContents;\n};\n\nexport function getProtocolSnapshotTree(snapshot: ISnapshotTree): ISnapshotTree {\n return \".protocol\" in snapshot.trees ? snapshot.trees[\".protocol\"] : snapshot;\n}\n"]}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.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\nimport { parse } from \"url\";\nimport { v4 as uuid } from \"uuid\";\nimport {\n assert,\n stringToBuffer,\n Uint8ArrayToArrayBuffer,\n unreachableCase,\n} from \"@fluidframework/common-utils\";\nimport { ISummaryTree, ISnapshotTree, SummaryType } from \"@fluidframework/protocol-definitions\";\nimport { LoggingError } from \"@fluidframework/telemetry-utils\";\n\n// This is used when we rehydrate a container from the snapshot. Here we put the blob contents\n// in separate property: blobContents.\nexport interface ISnapshotTreeWithBlobContents extends ISnapshotTree {\n blobsContents: { [path: string]: ArrayBufferLike; };\n trees: { [path: string]: ISnapshotTreeWithBlobContents; };\n}\n\nexport interface IParsedUrl {\n id: string;\n path: string;\n query: string;\n /**\n * Null means do not use snapshots, undefined means load latest snapshot\n * otherwise it's version ID passed to IDocumentStorageService.getVersions() to figure out what snapshot to use.\n * If needed, can add undefined which is treated by Container.load() as load latest snapshot.\n */\n version: string | null | undefined;\n}\n\nexport function parseUrl(url: string): IParsedUrl | undefined {\n const parsed = parse(url, true);\n if (typeof parsed.pathname !== \"string\") {\n throw new LoggingError(\"Failed to parse pathname\");\n }\n const query = parsed.search ?? \"\";\n const regex = /^\\/([^/]*\\/[^/]*)(\\/?.*)$/;\n const match = regex.exec(parsed.pathname);\n return (match?.length === 3)\n ? { id: match[1], path: match[2], query, version: parsed.query.version as string }\n : undefined;\n}\n\n/**\n * Converts summary tree (for upload) to snapshot tree (for download).\n * Summary tree blobs contain contents, but snapshot tree blobs normally\n * contain IDs pointing to storage. This will create 2 blob entries in the\n * snapshot tree for each blob in the summary tree. One will be the regular\n * path pointing to a uniquely generated ID. Then there will be another\n * entry with the path as that uniquely generated ID, and value as the\n * blob contents as a base-64 string.\n * @param summary - summary to convert\n */\nfunction convertSummaryToSnapshotWithEmbeddedBlobContents(\n summary: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n const treeNode: ISnapshotTreeWithBlobContents = {\n blobs: {},\n blobsContents: {},\n trees: {},\n id: uuid(),\n unreferenced: summary.unreferenced,\n };\n const keys = Object.keys(summary.tree);\n for (const key of keys) {\n const summaryObject = summary.tree[key];\n\n switch (summaryObject.type) {\n case SummaryType.Tree: {\n treeNode.trees[key] = convertSummaryToSnapshotWithEmbeddedBlobContents(summaryObject);\n break;\n }\n case SummaryType.Attachment:\n treeNode.blobs[key] = summaryObject.id;\n break;\n case SummaryType.Blob: {\n const blobId = uuid();\n treeNode.blobs[key] = blobId;\n const contentBuffer = typeof summaryObject.content === \"string\" ?\n stringToBuffer(summaryObject.content, \"utf8\") : Uint8ArrayToArrayBuffer(summaryObject.content);\n treeNode.blobsContents[blobId] = contentBuffer;\n break;\n }\n case SummaryType.Handle:\n throw new LoggingError(\"No handles should be there in summary in detached container!!\");\n break;\n default: {\n unreachableCase(summaryObject, `Unknown tree type ${(summaryObject as any).type}`);\n }\n }\n }\n return treeNode;\n}\n\n/**\n * Combine and convert protocol and app summary tree to format which is readable by container while rehydrating.\n * @param protocolSummaryTree - Protocol Summary Tree\n * @param appSummaryTree - App Summary Tree\n */\nexport function convertProtocolAndAppSummaryToSnapshotTree(\n protocolSummaryTree: ISummaryTree,\n appSummaryTree: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n // Shallow copy is fine, since we are doing a deep clone below.\n const combinedSummary: ISummaryTree = {\n type: SummaryType.Tree,\n tree: { ...appSummaryTree.tree },\n };\n\n combinedSummary.tree[\".protocol\"] = protocolSummaryTree;\n const snapshotTreeWithBlobContents =\n convertSummaryToSnapshotWithEmbeddedBlobContents(combinedSummary);\n return snapshotTreeWithBlobContents;\n}\n\n// This function converts the snapshot taken in detached container(by serialize api) to snapshotTree with which\n// a detached container can be rehydrated.\nexport const getSnapshotTreeFromSerializedContainer = (detachedContainerSnapshot: ISummaryTree) => {\n const protocolSummaryTree = detachedContainerSnapshot.tree[\".protocol\"] as ISummaryTree;\n const appSummaryTree = detachedContainerSnapshot.tree[\".app\"] as ISummaryTree;\n assert(protocolSummaryTree !== undefined && appSummaryTree !== undefined,\n 0x1e0 /* \"Protocol and App summary trees should be present\" */);\n const snapshotTreeWithBlobContents = convertProtocolAndAppSummaryToSnapshotTree(\n protocolSummaryTree,\n appSummaryTree,\n );\n return snapshotTreeWithBlobContents;\n};\n\nexport function getProtocolSnapshotTree(snapshot: ISnapshotTree): ISnapshotTree {\n return \".protocol\" in snapshot.trees ? snapshot.trees[\".protocol\"] : snapshot;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-loader",
3
- "version": "1.0.0",
3
+ "version": "1.1.0-76254",
4
4
  "description": "Fluid container loader",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -63,26 +63,26 @@
63
63
  "dependencies": {
64
64
  "@fluidframework/common-definitions": "^0.20.1",
65
65
  "@fluidframework/common-utils": "^0.32.1",
66
- "@fluidframework/container-definitions": "^1.0.0",
67
- "@fluidframework/container-utils": "^1.0.0",
68
- "@fluidframework/core-interfaces": "^1.0.0",
69
- "@fluidframework/driver-definitions": "^1.0.0",
70
- "@fluidframework/driver-utils": "^1.0.0",
71
- "@fluidframework/protocol-base": "^0.1036.4000",
66
+ "@fluidframework/container-definitions": "1.1.0-76254",
67
+ "@fluidframework/container-utils": "1.1.0-76254",
68
+ "@fluidframework/core-interfaces": "1.1.0-76254",
69
+ "@fluidframework/driver-definitions": "1.1.0-76254",
70
+ "@fluidframework/driver-utils": "1.1.0-76254",
71
+ "@fluidframework/protocol-base": "^0.1036.5000-0",
72
72
  "@fluidframework/protocol-definitions": "^0.1028.2000",
73
- "@fluidframework/telemetry-utils": "^1.0.0",
73
+ "@fluidframework/telemetry-utils": "1.1.0-76254",
74
74
  "abort-controller": "^3.0.0",
75
75
  "double-ended-queue": "^2.1.0-0",
76
76
  "lodash": "^4.17.21",
77
77
  "uuid": "^8.3.1"
78
78
  },
79
79
  "devDependencies": {
80
- "@fluidframework/build-common": "^0.23.0",
81
- "@fluidframework/build-tools": "^0.2.71273",
82
- "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@0.59.3000",
80
+ "@fluidframework/build-common": "^0.24.0-0",
81
+ "@fluidframework/build-tools": "^0.2.74327",
82
+ "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@^1.0.0",
83
83
  "@fluidframework/eslint-config-fluid": "^0.28.2000",
84
- "@fluidframework/mocha-test-setup": "^1.0.0",
85
- "@fluidframework/test-loader-utils": "^1.0.0",
84
+ "@fluidframework/mocha-test-setup": "1.1.0-76254",
85
+ "@fluidframework/test-loader-utils": "1.1.0-76254",
86
86
  "@microsoft/api-extractor": "^7.22.2",
87
87
  "@rushstack/eslint-config": "^2.5.1",
88
88
  "@types/double-ended-queue": "^2.1.0",
@@ -102,20 +102,7 @@
102
102
  "typescript-formatter": "7.1.0"
103
103
  },
104
104
  "typeValidation": {
105
- "version": "1.0.0",
106
- "broken": {
107
- "ClassDeclaration_Container": {
108
- "backCompat": false
109
- },
110
- "EnumDeclaration_ConnectionState": {
111
- "backCompat": false
112
- },
113
- "InterfaceDeclaration_ILoaderServices": {
114
- "backCompat": false
115
- },
116
- "ClassDeclaration_Loader": {
117
- "backCompat": false
118
- }
119
- }
105
+ "version": "1.1.0",
106
+ "broken": {}
120
107
  }
121
108
  }
@@ -15,9 +15,7 @@ import {
15
15
  IConnectionDetails,
16
16
  ICriticalContainerError,
17
17
  } from "@fluidframework/container-definitions";
18
- import {
19
- GenericError,
20
- } from "@fluidframework/container-utils";
18
+ import { GenericError, UsageError } from "@fluidframework/container-utils";
21
19
  import {
22
20
  IDocumentService,
23
21
  IDocumentDeltaConnection,
@@ -370,9 +368,9 @@ export class ConnectionManager implements IConnectionManager {
370
368
  this._forceReadonly = readonly;
371
369
 
372
370
  if (oldValue !== this.readonly) {
373
- assert(this._reconnectMode !== ReconnectMode.Never,
374
- 0x279 /* "API is not supported for non-connecting or closed container" */);
375
-
371
+ if (this._reconnectMode === ReconnectMode.Never) {
372
+ throw new UsageError("API is not supported for non-connecting or closed container");
373
+ }
376
374
  let reconnect = false;
377
375
  if (this.readonly === true) {
378
376
  // If we switch to readonly while connected, we should disconnect first
@@ -3,33 +3,55 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { ITelemetryLogger } from "@fluidframework/common-definitions";
6
+ import { ITelemetryLogger, ITelemetryProperties } from "@fluidframework/common-definitions";
7
7
  import { assert, Timer } from "@fluidframework/common-utils";
8
8
  import { IConnectionDetails } from "@fluidframework/container-definitions";
9
- import { ProtocolOpHandler } from "@fluidframework/protocol-base";
10
- import { ConnectionMode, IQuorumClients, ISequencedClient } from "@fluidframework/protocol-definitions";
9
+ import { ILocalSequencedClient, IProtocolHandler } from "@fluidframework/protocol-base";
10
+ import { ConnectionMode, IQuorumClients } from "@fluidframework/protocol-definitions";
11
11
  import { PerformanceEvent } from "@fluidframework/telemetry-utils";
12
12
  import { ConnectionState } from "./connectionState";
13
13
 
14
- export interface IConnectionStateHandler {
14
+ /** Constructor parameter type for passing in dependencies needed by the ConnectionStateHandler */
15
+ export interface IConnectionStateHandlerInputs {
16
+ /** Provides access to the clients currently in the quorum */
15
17
  quorumClients: () => IQuorumClients | undefined;
16
- logConnectionStateChangeTelemetry: (
17
- value: ConnectionState,
18
- oldState: ConnectionState,
19
- reason?: string | undefined
20
- ) => void;
18
+ /** Log to telemetry any change in state, included to Connecting */
19
+ logConnectionStateChangeTelemetry:
20
+ (value: ConnectionState, oldState: ConnectionState, reason?: string | undefined) => void;
21
+ /** Whether to expect the client to join in write mode on next connection */
21
22
  shouldClientJoinWrite: () => boolean;
23
+ /** (Optional) How long should we wait on our previous client's Leave op before transitioning to Connected again */
22
24
  maxClientLeaveWaitTime: number | undefined;
23
- logConnectionIssue: (eventName: string) => void;
25
+ /** Log an issue encountered while in the Connecting state. details will be logged as a JSON string */
26
+ logConnectionIssue: (eventName: string, details?: ITelemetryProperties) => void;
27
+ /** Callback whenever the ConnectionState changes between Disconnected and Connected */
24
28
  connectionStateChanged: () => void;
25
29
  }
26
30
 
27
- export interface ILocalSequencedClient extends ISequencedClient {
28
- shouldHaveLeft?: boolean;
29
- }
30
-
31
- const JoinOpTimer = 45000;
32
-
31
+ const JoinOpTimeoutMs = 45000;
32
+
33
+ /**
34
+ * In the lifetime of a container, the connection will likely disconnect and reconnect periodically.
35
+ * This class ensures that any ops sent by this container instance on previous connection are either
36
+ * sequenced or blocked by the server before emitting the new "connected" event and allowing runtime to resubmit ops.
37
+ *
38
+ * Each connection is assigned a clientId by the service, and the connection is book-ended by a Join and a Leave op
39
+ * generated by the service. Due to the distributed nature of the ordering service, in the case of reconnect we cannot
40
+ * make any assumptions about ordering of operations between the old and new connections - i.e. new Join op could
41
+ * be sequenced before old Leave op (and some acks from pending ops that were in flight when we disconnected).
42
+ *
43
+ * The job of this class is to encapsulate the transition period during reconnect, which is identified by
44
+ * ConnectionState.CatchingUp. Specifically, before moving to Connected state with the new clientId, it ensures that:
45
+ * (A) We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops
46
+ * that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other
47
+ * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.
48
+ * (B) We process the Join op for the new clientId (identified when the underlying connection was first established),
49
+ * indicating the service is ready to sequence ops sent with the new clientId.
50
+ *
51
+ * For (A) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.
52
+ * For (B) we log telemetry if it takes too long, but still only transition to Connected when the Join op is processed
53
+ * and we are added to the Quorum.
54
+ */
33
55
  export class ConnectionStateHandler {
34
56
  private _connectionState = ConnectionState.Disconnected;
35
57
  private _pendingClientId: string | undefined;
@@ -55,7 +77,7 @@ export class ConnectionStateHandler {
55
77
  }
56
78
 
57
79
  constructor(
58
- private readonly handler: IConnectionStateHandler,
80
+ private readonly handler: IConnectionStateHandlerInputs,
59
81
  private readonly logger: ITelemetryLogger,
60
82
  private _clientId?: string,
61
83
  ) {
@@ -74,13 +96,21 @@ export class ConnectionStateHandler {
74
96
  // timing out ops fetches. So attempt recovery infrequently. Also fetch uses 30 second timeout, so
75
97
  // if retrying fixes the problem, we should not see these events.
76
98
  this.joinOpTimer = new Timer(
77
- JoinOpTimer,
99
+ JoinOpTimeoutMs,
78
100
  () => {
79
101
  // I've observed timer firing within couple ms from disconnect event, looks like
80
102
  // queued timer callback is not cancelled if timer is cancelled while callback sits in the queue.
81
- if (this.connectionState === ConnectionState.CatchingUp) {
82
- this.handler.logConnectionIssue("NoJoinOp");
103
+ if (this.connectionState !== ConnectionState.CatchingUp) {
104
+ return;
83
105
  }
106
+ const quorumClients = this.handler.quorumClients();
107
+ const details = {
108
+ quorumInitialized: quorumClients !== undefined,
109
+ hasPendingClientId: this.pendingClientId !== undefined,
110
+ inQuorum: quorumClients?.getMember(this.pendingClientId ?? "") !== undefined,
111
+ waitingForLeaveOp: this.waitingForLeaveOp,
112
+ };
113
+ this.handler.logConnectionIssue("NoJoinOp", details);
84
114
  },
85
115
  );
86
116
  }
@@ -95,6 +125,10 @@ export class ConnectionStateHandler {
95
125
  this.joinOpTimer.clear();
96
126
  }
97
127
 
128
+ private get waitingForLeaveOp() {
129
+ return this.prevClientLeftTimer.hasTimer;
130
+ }
131
+
98
132
  public dispose() {
99
133
  assert(!this.joinOpTimer.hasTimer, 0x2a5 /* "join timer" */);
100
134
  this.prevClientLeftTimer.clear();
@@ -103,7 +137,7 @@ export class ConnectionStateHandler {
103
137
  public containerSaved() {
104
138
  // If we were waiting for moving to Connected state, then only apply for state change. Since the container
105
139
  // is now saved and we don't have any ops to roundtrip, we can clear the timer and apply for connected state.
106
- if (this.prevClientLeftTimer.hasTimer) {
140
+ if (this.waitingForLeaveOp) {
107
141
  this.prevClientLeftTimer.clear();
108
142
  this.applyForConnectedState("containerSaved");
109
143
  }
@@ -120,11 +154,13 @@ export class ConnectionStateHandler {
120
154
  this.handler.logConnectionIssue("ReceivedJoinOp");
121
155
  }
122
156
  // Start the event in case we are waiting for leave or timeout.
123
- if (this.prevClientLeftTimer.hasTimer) {
157
+ if (this.waitingForLeaveOp) {
124
158
  this.waitEvent = PerformanceEvent.start(this.logger, {
125
159
  eventName: "WaitBeforeClientLeave",
126
- waitOnClientId: this._clientId,
127
- hadOutstandingOps: this.handler.shouldClientJoinWrite(),
160
+ details: JSON.stringify({
161
+ waitOnClientId: this._clientId,
162
+ hadOutstandingOps: this.handler.shouldClientJoinWrite(),
163
+ }),
128
164
  });
129
165
  }
130
166
  this.applyForConnectedState("addMemberEvent");
@@ -135,17 +171,17 @@ export class ConnectionStateHandler {
135
171
  const quorumClients = this.handler.quorumClients();
136
172
  assert(quorumClients !== undefined, 0x236 /* "In all cases it should be already installed" */);
137
173
 
138
- assert(this.prevClientLeftTimer.hasTimer === false ||
174
+ assert(this.waitingForLeaveOp === false ||
139
175
  (this.clientId !== undefined && quorumClients.getMember(this.clientId) !== undefined),
140
176
  0x2e2 /* "Must only wait for leave message when clientId in quorum" */);
141
177
 
142
178
  // Move to connected state only if we are in Connecting state, we have seen our join op
143
179
  // and there is no timer running which means we are not waiting for previous client to leave
144
- // or timeout has occured while doing so.
180
+ // or timeout has occurred while doing so.
145
181
  if (this.pendingClientId !== this.clientId
146
182
  && this.pendingClientId !== undefined
147
183
  && quorumClients.getMember(this.pendingClientId) !== undefined
148
- && !this.prevClientLeftTimer.hasTimer
184
+ && !this.waitingForLeaveOp
149
185
  ) {
150
186
  this.waitEvent?.end({ source });
151
187
  this.setConnectionState(ConnectionState.Connected);
@@ -154,12 +190,13 @@ export class ConnectionStateHandler {
154
190
  this.logger.sendTelemetryEvent({
155
191
  eventName: "connectedStateRejected",
156
192
  category: source === "timeout" ? "error" : "generic",
157
- source,
158
- pendingClientId: this.pendingClientId,
159
- clientId: this.clientId,
160
- hasTimer: this.prevClientLeftTimer.hasTimer,
161
- inQuorum: quorumClients !== undefined && this.pendingClientId !== undefined
162
- && quorumClients.getMember(this.pendingClientId) !== undefined,
193
+ details: JSON.stringify({
194
+ source,
195
+ pendingClientId: this.pendingClientId,
196
+ clientId: this.clientId,
197
+ waitingForLeaveOp: this.waitingForLeaveOp,
198
+ inQuorum: quorumClients?.getMember(this.pendingClientId ?? "") !== undefined,
199
+ }),
163
200
  });
164
201
  }
165
202
  }
@@ -179,6 +216,13 @@ export class ConnectionStateHandler {
179
216
  this.setConnectionState(ConnectionState.Disconnected, reason);
180
217
  }
181
218
 
219
+ /**
220
+ * The "connect" event indicates the connection to the Relay Service is live.
221
+ * However, some additional conditions must be met before we can fully transition to
222
+ * "Connected" state. This function handles that interim period, known as "Connecting" state.
223
+ * @param connectionMode - Read or Write connection
224
+ * @param details - Connection details returned from the ordering service
225
+ */
182
226
  public receivedConnectEvent(
183
227
  connectionMode: ConnectionMode,
184
228
  details: IConnectionDetails,
@@ -186,36 +230,49 @@ export class ConnectionStateHandler {
186
230
  const oldState = this._connectionState;
187
231
  this._connectionState = ConnectionState.CatchingUp;
188
232
 
233
+ const writeConnection = connectionMode === "write";
234
+ assert(writeConnection || !this.handler.shouldClientJoinWrite(),
235
+ 0x30a /* shouldClientJoinWrite should imply this is a writeConnection */);
236
+ assert(writeConnection || !this.waitingForLeaveOp,
237
+ 0x2a6 /* "waitingForLeaveOp should imply writeConnection (we need to be ready to flush pending ops)" */);
238
+
239
+ // Note that this may be undefined since the connection is established proactively on load
240
+ // and the quorum may still be under initialization.
241
+ const quorumClients: IQuorumClients | undefined = this.handler.quorumClients();
242
+
189
243
  // Stash the clientID to detect when transitioning from connecting (socket.io channel open) to connected
190
244
  // (have received the join message for the client ID)
191
245
  // This is especially important in the reconnect case. It's possible there could be outstanding
192
246
  // ops sent by this client, so we should keep the old client id until we see our own client's
193
- // join message. after we see the join message for out new connection with our new client id,
247
+ // join message. after we see the join message for our new connection with our new client id,
194
248
  // we know there can no longer be outstanding ops that we sent with the previous client id.
195
249
  this._pendingClientId = details.clientId;
196
250
 
197
- // Report telemetry after we set client id, but before transitioning to Connected state below!
251
+ // IMPORTANT: Report telemetry after we set _pendingClientId, but before transitioning to Connected state
198
252
  this.handler.logConnectionStateChangeTelemetry(ConnectionState.CatchingUp, oldState);
199
253
 
200
- const quorumClients = this.handler.quorumClients();
201
- // Check if we already processed our own join op through delta storage!
202
- // we are fetching ops from storage in parallel to connecting to ordering service
203
- // Given async processes, it's possible that we have already processed our own join message before
254
+ // For write connections, this pending clientId could be in the quorum already (i.e. join op already processed).
255
+ // We are fetching ops from storage in parallel to connecting to Relay Service,
256
+ // and given async processes, it's possible that we have already processed our own join message before
204
257
  // connection was fully established.
205
- // Note that we might be still initializing quorum - connection is established proactively on load!
206
- if (quorumClients?.getMember(details.clientId) !== undefined
207
- || connectionMode === "read"
208
- ) {
209
- assert(!this.prevClientLeftTimer.hasTimer, 0x2a6 /* "there should be no timer for 'read' connections" */);
210
- this.setConnectionState(ConnectionState.Connected);
211
- } else if (connectionMode === "write") {
258
+ // If quorumClients itself is undefined, we expect it will process the join op after it's initialized.
259
+ const waitingForJoinOp = writeConnection && quorumClients?.getMember(this._pendingClientId) === undefined;
260
+
261
+ if (waitingForJoinOp) {
262
+ // Previous client left, and we are waiting for our own join op. When it is processed we'll join the quorum
263
+ // and attempt to transition to Connected state via receivedAddMemberEvent.
212
264
  this.startJoinOpTimer();
265
+ } else if (!this.waitingForLeaveOp) {
266
+ // We're not waiting for Join or Leave op (if read-only connection those don't even apply),
267
+ // go ahead and declare the state to be Connected!
268
+ // If we are waiting for Leave op still, do nothing for now, we will transition to Connected later.
269
+ this.setConnectionState(ConnectionState.Connected);
213
270
  }
214
271
  }
215
272
 
216
- private setConnectionState(value: ConnectionState.Disconnected, reason: string);
217
- private setConnectionState(value: ConnectionState.Connected);
218
- private setConnectionState(value: ConnectionState, reason?: string) {
273
+ private setConnectionState(value: ConnectionState.Disconnected, reason: string): void;
274
+ private setConnectionState(value: ConnectionState.Connected): void;
275
+ private setConnectionState(value: ConnectionState, reason?: string): void {
219
276
  if (this.connectionState === value) {
220
277
  // Already in the desired state - exit early
221
278
  this.logger.sendErrorEvent({ eventName: "setConnectionStateSame", value });
@@ -255,9 +312,11 @@ export class ConnectionStateHandler {
255
312
  // Adding this event temporarily so that we can get help debugging if something goes wrong.
256
313
  this.logger.sendTelemetryEvent({
257
314
  eventName: "noWaitOnDisconnected",
258
- inQuorum: client !== undefined,
259
- hasTimer: this.prevClientLeftTimer.hasTimer,
260
- shouldClientJoinWrite: this.handler.shouldClientJoinWrite(),
315
+ details: JSON.stringify({
316
+ inQuorum: client !== undefined,
317
+ waitingForLeaveOp: this.waitingForLeaveOp,
318
+ hadOutstandingOps: this.handler.shouldClientJoinWrite(),
319
+ }),
261
320
  });
262
321
  }
263
322
  }
@@ -269,8 +328,8 @@ export class ConnectionStateHandler {
269
328
  this.handler.connectionStateChanged();
270
329
  }
271
330
 
272
- public initProtocol(protocol: ProtocolOpHandler) {
273
- protocol.quorum.on("addMember", (clientId, details) => {
331
+ public initProtocol(protocol: IProtocolHandler) {
332
+ protocol.quorum.on("addMember", (clientId, _details) => {
274
333
  this.receivedAddMemberEvent(clientId);
275
334
  });
276
335