@fluidframework/container-loader 2.0.0-dev.3.1.0.125672 → 2.0.0-dev.4.2.0.153917

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 (124) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +45 -4
  3. package/closeAndGetPendingLocalState.md +51 -0
  4. package/dist/connectionManager.d.ts +2 -1
  5. package/dist/connectionManager.d.ts.map +1 -1
  6. package/dist/connectionManager.js +59 -17
  7. package/dist/connectionManager.js.map +1 -1
  8. package/dist/connectionStateHandler.d.ts +4 -4
  9. package/dist/connectionStateHandler.d.ts.map +1 -1
  10. package/dist/connectionStateHandler.js +7 -0
  11. package/dist/connectionStateHandler.js.map +1 -1
  12. package/dist/container.d.ts +44 -4
  13. package/dist/container.d.ts.map +1 -1
  14. package/dist/container.js +160 -105
  15. package/dist/container.js.map +1 -1
  16. package/dist/containerContext.d.ts +18 -8
  17. package/dist/containerContext.d.ts.map +1 -1
  18. package/dist/containerContext.js +47 -4
  19. package/dist/containerContext.js.map +1 -1
  20. package/dist/containerStorageAdapter.d.ts +41 -2
  21. package/dist/containerStorageAdapter.d.ts.map +1 -1
  22. package/dist/containerStorageAdapter.js +87 -11
  23. package/dist/containerStorageAdapter.js.map +1 -1
  24. package/dist/contracts.d.ts +2 -2
  25. package/dist/contracts.d.ts.map +1 -1
  26. package/dist/contracts.js.map +1 -1
  27. package/dist/deltaManager.d.ts +4 -5
  28. package/dist/deltaManager.d.ts.map +1 -1
  29. package/dist/deltaManager.js +7 -10
  30. package/dist/deltaManager.js.map +1 -1
  31. package/dist/deltaManagerProxy.d.ts +10 -22
  32. package/dist/deltaManagerProxy.d.ts.map +1 -1
  33. package/dist/deltaManagerProxy.js +14 -50
  34. package/dist/deltaManagerProxy.js.map +1 -1
  35. package/dist/index.d.ts +3 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/loader.d.ts +10 -1
  40. package/dist/loader.d.ts.map +1 -1
  41. package/dist/loader.js +25 -16
  42. package/dist/loader.js.map +1 -1
  43. package/dist/packageVersion.d.ts +1 -1
  44. package/dist/packageVersion.js +1 -1
  45. package/dist/packageVersion.js.map +1 -1
  46. package/dist/protocol.d.ts +1 -0
  47. package/dist/protocol.d.ts.map +1 -1
  48. package/dist/protocol.js +4 -2
  49. package/dist/protocol.js.map +1 -1
  50. package/dist/protocolTreeDocumentStorageService.d.ts +6 -2
  51. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  52. package/dist/protocolTreeDocumentStorageService.js +7 -4
  53. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  54. package/dist/utils.d.ts.map +1 -1
  55. package/dist/utils.js +2 -1
  56. package/dist/utils.js.map +1 -1
  57. package/lib/connectionManager.d.ts +2 -1
  58. package/lib/connectionManager.d.ts.map +1 -1
  59. package/lib/connectionManager.js +60 -18
  60. package/lib/connectionManager.js.map +1 -1
  61. package/lib/connectionStateHandler.d.ts +4 -4
  62. package/lib/connectionStateHandler.d.ts.map +1 -1
  63. package/lib/connectionStateHandler.js +7 -0
  64. package/lib/connectionStateHandler.js.map +1 -1
  65. package/lib/container.d.ts +44 -4
  66. package/lib/container.d.ts.map +1 -1
  67. package/lib/container.js +164 -109
  68. package/lib/container.js.map +1 -1
  69. package/lib/containerContext.d.ts +18 -8
  70. package/lib/containerContext.d.ts.map +1 -1
  71. package/lib/containerContext.js +48 -5
  72. package/lib/containerContext.js.map +1 -1
  73. package/lib/containerStorageAdapter.d.ts +41 -2
  74. package/lib/containerStorageAdapter.d.ts.map +1 -1
  75. package/lib/containerStorageAdapter.js +85 -11
  76. package/lib/containerStorageAdapter.js.map +1 -1
  77. package/lib/contracts.d.ts +2 -2
  78. package/lib/contracts.d.ts.map +1 -1
  79. package/lib/contracts.js.map +1 -1
  80. package/lib/deltaManager.d.ts +4 -5
  81. package/lib/deltaManager.d.ts.map +1 -1
  82. package/lib/deltaManager.js +7 -10
  83. package/lib/deltaManager.js.map +1 -1
  84. package/lib/deltaManagerProxy.d.ts +10 -22
  85. package/lib/deltaManagerProxy.d.ts.map +1 -1
  86. package/lib/deltaManagerProxy.js +14 -50
  87. package/lib/deltaManagerProxy.js.map +1 -1
  88. package/lib/index.d.ts +3 -2
  89. package/lib/index.d.ts.map +1 -1
  90. package/lib/index.js +2 -2
  91. package/lib/index.js.map +1 -1
  92. package/lib/loader.d.ts +10 -1
  93. package/lib/loader.d.ts.map +1 -1
  94. package/lib/loader.js +24 -16
  95. package/lib/loader.js.map +1 -1
  96. package/lib/packageVersion.d.ts +1 -1
  97. package/lib/packageVersion.js +1 -1
  98. package/lib/packageVersion.js.map +1 -1
  99. package/lib/protocol.d.ts +1 -0
  100. package/lib/protocol.d.ts.map +1 -1
  101. package/lib/protocol.js +3 -1
  102. package/lib/protocol.js.map +1 -1
  103. package/lib/protocolTreeDocumentStorageService.d.ts +6 -2
  104. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  105. package/lib/protocolTreeDocumentStorageService.js +7 -4
  106. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  107. package/lib/utils.d.ts.map +1 -1
  108. package/lib/utils.js +2 -1
  109. package/lib/utils.js.map +1 -1
  110. package/package.json +64 -56
  111. package/src/connectionManager.ts +65 -24
  112. package/src/connectionStateHandler.ts +17 -5
  113. package/src/container.ts +239 -137
  114. package/src/containerContext.ts +74 -11
  115. package/src/containerStorageAdapter.ts +113 -9
  116. package/src/contracts.ts +2 -2
  117. package/src/deltaManager.ts +12 -14
  118. package/src/deltaManagerProxy.ts +18 -73
  119. package/src/index.ts +3 -3
  120. package/src/loader.ts +31 -26
  121. package/src/packageVersion.ts +1 -1
  122. package/src/protocol.ts +4 -1
  123. package/src/protocolTreeDocumentStorageService.ts +6 -3
  124. package/src/utils.ts +7 -4
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\tassert,\n\tstringToBuffer,\n\tUint8ArrayToArrayBuffer,\n\tunreachableCase,\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\tblobsContents: { [path: string]: ArrayBufferLike };\n\ttrees: { [path: string]: ISnapshotTreeWithBlobContents };\n}\n\nexport interface IParsedUrl {\n\tid: string;\n\tpath: string;\n\tquery: string;\n\t/**\n\t * Null means do not use snapshots, undefined means load latest snapshot\n\t * otherwise it's version ID passed to IDocumentStorageService.getVersions() to figure out what snapshot to use.\n\t * If needed, can add undefined which is treated by Container.load() as load latest snapshot.\n\t */\n\tversion: string | null | undefined;\n}\n\nexport function parseUrl(url: string): IParsedUrl | undefined {\n\tconst parsed = parse(url, true);\n\tif (typeof parsed.pathname !== \"string\") {\n\t\tthrow new LoggingError(\"Failed to parse pathname\");\n\t}\n\tconst query = parsed.search ?? \"\";\n\tconst regex = /^\\/([^/]*\\/[^/]*)(\\/?.*)$/;\n\tconst match = regex.exec(parsed.pathname);\n\treturn match?.length === 3\n\t\t? { id: match[1], path: match[2], query, version: parsed.query.version as string }\n\t\t: 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\tsummary: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n\tconst treeNode: ISnapshotTreeWithBlobContents = {\n\t\tblobs: {},\n\t\tblobsContents: {},\n\t\ttrees: {},\n\t\tid: uuid(),\n\t\tunreferenced: summary.unreferenced,\n\t};\n\tconst keys = Object.keys(summary.tree);\n\tfor (const key of keys) {\n\t\tconst summaryObject = summary.tree[key];\n\n\t\tswitch (summaryObject.type) {\n\t\t\tcase SummaryType.Tree: {\n\t\t\t\ttreeNode.trees[key] =\n\t\t\t\t\tconvertSummaryToSnapshotWithEmbeddedBlobContents(summaryObject);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SummaryType.Attachment:\n\t\t\t\ttreeNode.blobs[key] = summaryObject.id;\n\t\t\t\tbreak;\n\t\t\tcase SummaryType.Blob: {\n\t\t\t\tconst blobId = uuid();\n\t\t\t\ttreeNode.blobs[key] = blobId;\n\t\t\t\tconst contentBuffer =\n\t\t\t\t\ttypeof summaryObject.content === \"string\"\n\t\t\t\t\t\t? stringToBuffer(summaryObject.content, \"utf8\")\n\t\t\t\t\t\t: Uint8ArrayToArrayBuffer(summaryObject.content);\n\t\t\t\ttreeNode.blobsContents[blobId] = contentBuffer;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SummaryType.Handle:\n\t\t\t\tthrow new LoggingError(\n\t\t\t\t\t\"No handles should be there in summary in detached container!!\",\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tdefault: {\n\t\t\t\tunreachableCase(summaryObject, `Unknown tree type ${(summaryObject as any).type}`);\n\t\t\t}\n\t\t}\n\t}\n\treturn 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\tprotocolSummaryTree: ISummaryTree,\n\tappSummaryTree: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n\t// Shallow copy is fine, since we are doing a deep clone below.\n\tconst combinedSummary: ISummaryTree = {\n\t\ttype: SummaryType.Tree,\n\t\ttree: { ...appSummaryTree.tree },\n\t};\n\n\tcombinedSummary.tree[\".protocol\"] = protocolSummaryTree;\n\tconst snapshotTreeWithBlobContents =\n\t\tconvertSummaryToSnapshotWithEmbeddedBlobContents(combinedSummary);\n\treturn 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\tconst protocolSummaryTree = detachedContainerSnapshot.tree[\".protocol\"] as ISummaryTree;\n\tconst appSummaryTree = detachedContainerSnapshot.tree[\".app\"] as ISummaryTree;\n\tassert(\n\t\tprotocolSummaryTree !== undefined && appSummaryTree !== undefined,\n\t\t0x1e0 /* \"Protocol and App summary trees should be present\" */,\n\t);\n\tconst snapshotTreeWithBlobContents = convertProtocolAndAppSummaryToSnapshotTree(\n\t\tprotocolSummaryTree,\n\t\tappSummaryTree,\n\t);\n\treturn snapshotTreeWithBlobContents;\n};\n\nexport function getProtocolSnapshotTree(snapshot: ISnapshotTree): ISnapshotTree {\n\treturn \".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\tassert,\n\tstringToBuffer,\n\tUint8ArrayToArrayBuffer,\n\tunreachableCase,\n} from \"@fluidframework/common-utils\";\nimport { ISummaryTree, ISnapshotTree, SummaryType } from \"@fluidframework/protocol-definitions\";\nimport { LoggingError } from \"@fluidframework/telemetry-utils\";\nimport { isCombinedAppAndProtocolSummary } from \"@fluidframework/driver-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\tblobsContents: { [path: string]: ArrayBufferLike };\n\ttrees: { [path: string]: ISnapshotTreeWithBlobContents };\n}\n\nexport interface IParsedUrl {\n\tid: string;\n\tpath: string;\n\tquery: string;\n\t/**\n\t * Null means do not use snapshots, undefined means load latest snapshot\n\t * otherwise it's version ID passed to IDocumentStorageService.getVersions() to figure out what snapshot to use.\n\t * If needed, can add undefined which is treated by Container.load() as load latest snapshot.\n\t */\n\tversion: string | null | undefined;\n}\n\nexport function parseUrl(url: string): IParsedUrl | undefined {\n\tconst parsed = parse(url, true);\n\tif (typeof parsed.pathname !== \"string\") {\n\t\tthrow new LoggingError(\"Failed to parse pathname\");\n\t}\n\tconst query = parsed.search ?? \"\";\n\tconst regex = /^\\/([^/]*\\/[^/]*)(\\/?.*)$/;\n\tconst match = regex.exec(parsed.pathname);\n\treturn match?.length === 3\n\t\t? { id: match[1], path: match[2], query, version: parsed.query.version as string }\n\t\t: 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\tsummary: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n\tconst treeNode: ISnapshotTreeWithBlobContents = {\n\t\tblobs: {},\n\t\tblobsContents: {},\n\t\ttrees: {},\n\t\tid: uuid(),\n\t\tunreferenced: summary.unreferenced,\n\t};\n\tconst keys = Object.keys(summary.tree);\n\tfor (const key of keys) {\n\t\tconst summaryObject = summary.tree[key];\n\n\t\tswitch (summaryObject.type) {\n\t\t\tcase SummaryType.Tree: {\n\t\t\t\ttreeNode.trees[key] =\n\t\t\t\t\tconvertSummaryToSnapshotWithEmbeddedBlobContents(summaryObject);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SummaryType.Attachment:\n\t\t\t\ttreeNode.blobs[key] = summaryObject.id;\n\t\t\t\tbreak;\n\t\t\tcase SummaryType.Blob: {\n\t\t\t\tconst blobId = uuid();\n\t\t\t\ttreeNode.blobs[key] = blobId;\n\t\t\t\tconst contentBuffer =\n\t\t\t\t\ttypeof summaryObject.content === \"string\"\n\t\t\t\t\t\t? stringToBuffer(summaryObject.content, \"utf8\")\n\t\t\t\t\t\t: Uint8ArrayToArrayBuffer(summaryObject.content);\n\t\t\t\ttreeNode.blobsContents[blobId] = contentBuffer;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SummaryType.Handle:\n\t\t\t\tthrow new LoggingError(\n\t\t\t\t\t\"No handles should be there in summary in detached container!!\",\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\tdefault: {\n\t\t\t\tunreachableCase(summaryObject, `Unknown tree type ${(summaryObject as any).type}`);\n\t\t\t}\n\t\t}\n\t}\n\treturn 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\tprotocolSummaryTree: ISummaryTree,\n\tappSummaryTree: ISummaryTree,\n): ISnapshotTreeWithBlobContents {\n\t// Shallow copy is fine, since we are doing a deep clone below.\n\tconst combinedSummary: ISummaryTree = {\n\t\ttype: SummaryType.Tree,\n\t\ttree: { ...appSummaryTree.tree },\n\t};\n\n\tcombinedSummary.tree[\".protocol\"] = protocolSummaryTree;\n\tconst snapshotTreeWithBlobContents =\n\t\tconvertSummaryToSnapshotWithEmbeddedBlobContents(combinedSummary);\n\treturn 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 = (\n\tdetachedContainerSnapshot: ISummaryTree,\n): ISnapshotTreeWithBlobContents => {\n\tassert(\n\t\tisCombinedAppAndProtocolSummary(detachedContainerSnapshot),\n\t\t0x1e0 /* \"Protocol and App summary trees should be present\" */,\n\t);\n\tconst protocolSummaryTree = detachedContainerSnapshot.tree[\".protocol\"];\n\tconst appSummaryTree = detachedContainerSnapshot.tree[\".app\"];\n\tconst snapshotTreeWithBlobContents = convertProtocolAndAppSummaryToSnapshotTree(\n\t\tprotocolSummaryTree,\n\t\tappSummaryTree,\n\t);\n\treturn snapshotTreeWithBlobContents;\n};\n\nexport function getProtocolSnapshotTree(snapshot: ISnapshotTree): ISnapshotTree {\n\treturn \".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": "2.0.0-dev.3.1.0.125672",
3
+ "version": "2.0.0-dev.4.2.0.153917",
4
4
  "description": "Fluid container loader",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -14,34 +14,6 @@
14
14
  "main": "dist/index.js",
15
15
  "module": "lib/index.js",
16
16
  "types": "dist/index.d.ts",
17
- "scripts": {
18
- "build": "npm run build:genver && concurrently npm:build:compile npm:lint && npm run build:docs",
19
- "build:commonjs": "npm run tsc && npm run typetests:gen && npm run build:test",
20
- "build:compile": "concurrently npm:build:commonjs npm:build:esnext",
21
- "build:docs": "api-extractor run --local --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/doc-models/* ../../../_api-extractor-temp/",
22
- "build:esnext": "tsc --project ./tsconfig.esnext.json",
23
- "build:full": "npm run build",
24
- "build:full:compile": "npm run build:compile",
25
- "build:genver": "gen-version",
26
- "build:test": "tsc --project ./src/test/tsconfig.json",
27
- "ci:build:docs": "api-extractor run --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/* ../../../_api-extractor-temp/",
28
- "clean": "rimraf dist lib *.tsbuildinfo *.build.log",
29
- "eslint": "eslint --format stylish src",
30
- "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
31
- "format": "npm run prettier:fix",
32
- "lint": "npm run prettier && npm run eslint",
33
- "lint:fix": "npm run prettier:fix && npm run eslint:fix",
34
- "prettier": "prettier --check . --ignore-path ../../../.prettierignore",
35
- "prettier:fix": "prettier --write . --ignore-path ../../../.prettierignore",
36
- "test": "npm run test:mocha",
37
- "test:coverage": "nyc npm test -- --reporter xunit --reporter-option output=nyc/junit-report.xml",
38
- "test:mocha": "mocha --ignore 'dist/test/types/*' --recursive dist/test -r node_modules/@fluidframework/mocha-test-setup --unhandled-rejections=strict",
39
- "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
40
- "tsc": "tsc",
41
- "tsc:watch": "tsc --watch",
42
- "typetests:gen": "flub generate typetests --generate --dir .",
43
- "typetests:prepare": "flub generate typetests --prepare --dir . --pin"
44
- },
45
17
  "nyc": {
46
18
  "all": true,
47
19
  "cache-dir": "nyc/.cache",
@@ -64,15 +36,15 @@
64
36
  },
65
37
  "dependencies": {
66
38
  "@fluidframework/common-definitions": "^0.20.1",
67
- "@fluidframework/common-utils": "^1.0.0",
68
- "@fluidframework/container-definitions": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
69
- "@fluidframework/container-utils": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
70
- "@fluidframework/core-interfaces": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
71
- "@fluidframework/driver-definitions": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
72
- "@fluidframework/driver-utils": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
73
- "@fluidframework/protocol-base": "^0.1038.2000",
39
+ "@fluidframework/common-utils": "^1.1.1",
40
+ "@fluidframework/container-definitions": "2.0.0-dev.4.2.0.153917",
41
+ "@fluidframework/container-utils": "2.0.0-dev.4.2.0.153917",
42
+ "@fluidframework/core-interfaces": "2.0.0-dev.4.2.0.153917",
43
+ "@fluidframework/driver-definitions": "2.0.0-dev.4.2.0.153917",
44
+ "@fluidframework/driver-utils": "2.0.0-dev.4.2.0.153917",
45
+ "@fluidframework/protocol-base": "^0.1039.1000",
74
46
  "@fluidframework/protocol-definitions": "^1.1.0",
75
- "@fluidframework/telemetry-utils": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
47
+ "@fluidframework/telemetry-utils": "2.0.0-dev.4.2.0.153917",
76
48
  "abort-controller": "^3.0.0",
77
49
  "double-ended-queue": "^2.1.0-0",
78
50
  "events": "^3.1.0",
@@ -81,37 +53,73 @@
81
53
  "uuid": "^8.3.1"
82
54
  },
83
55
  "devDependencies": {
84
- "@fluid-tools/build-cli": "^0.8.0",
56
+ "@fluid-internal/test-loader-utils": "2.0.0-dev.4.2.0.153917",
57
+ "@fluid-tools/build-cli": "^0.15.0",
85
58
  "@fluidframework/build-common": "^1.1.0",
86
- "@fluidframework/build-tools": "^0.8.0",
87
- "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.0.0-internal.3.0.0",
59
+ "@fluidframework/build-tools": "^0.15.0",
60
+ "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.0.0-internal.4.0.0",
88
61
  "@fluidframework/eslint-config-fluid": "^2.0.0",
89
- "@fluidframework/mocha-test-setup": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
90
- "@fluidframework/test-loader-utils": ">=2.0.0-dev.3.1.0.125672 <2.0.0-dev.4.0.0",
91
- "@microsoft/api-extractor": "^7.22.2",
92
- "@rushstack/eslint-config": "^2.5.1",
62
+ "@fluidframework/mocha-test-setup": "2.0.0-dev.4.2.0.153917",
63
+ "@microsoft/api-extractor": "^7.34.4",
93
64
  "@types/double-ended-queue": "^2.1.0",
94
65
  "@types/events": "^3.0.0",
95
66
  "@types/lodash": "^4.14.118",
96
67
  "@types/mocha": "^9.1.1",
97
- "@types/node": "^14.18.36",
68
+ "@types/node": "^14.18.38",
98
69
  "@types/sinon": "^7.0.13",
99
- "concurrently": "^6.2.0",
70
+ "concurrently": "^7.6.0",
100
71
  "copyfiles": "^2.4.1",
101
- "cross-env": "^7.0.2",
72
+ "cross-env": "^7.0.3",
102
73
  "eslint": "~8.6.0",
103
- "mocha": "^10.0.0",
104
- "nyc": "^15.0.0",
74
+ "mocha": "^10.2.0",
75
+ "mocha-json-output-reporter": "^2.0.1",
76
+ "mocha-multi-reporters": "^1.5.1",
77
+ "moment": "^2.21.0",
78
+ "nyc": "^15.1.0",
105
79
  "prettier": "~2.6.2",
106
- "rimraf": "^2.6.2",
80
+ "rimraf": "^4.4.0",
107
81
  "sinon": "^7.4.2",
108
82
  "typescript": "~4.5.5"
109
83
  },
110
84
  "typeValidation": {
111
- "version": "2.0.0-internal.3.1.0",
112
- "previousVersionStyle": "~previousMinor",
113
- "baselineRange": ">=2.0.0-internal.3.0.0 <2.0.0-internal.3.1.0",
114
- "baselineVersion": "2.0.0-internal.3.0.0",
115
- "broken": {}
85
+ "broken": {
86
+ "InterfaceDeclaration_IContainerConfig": {
87
+ "forwardCompat": false,
88
+ "backCompat": false
89
+ },
90
+ "InterfaceDeclaration_IPendingContainerState": {
91
+ "forwardCompat": false,
92
+ "backCompat": false
93
+ }
94
+ }
95
+ },
96
+ "scripts": {
97
+ "build": "npm run build:genver && concurrently npm:build:compile npm:lint && npm run build:docs",
98
+ "build:commonjs": "npm run tsc && npm run typetests:gen && npm run build:test",
99
+ "build:compile": "concurrently npm:build:commonjs npm:build:esnext",
100
+ "build:docs": "api-extractor run --local --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/doc-models/* ../../../_api-extractor-temp/",
101
+ "build:esnext": "tsc --project ./tsconfig.esnext.json",
102
+ "build:full": "npm run build",
103
+ "build:full:compile": "npm run build:compile",
104
+ "build:genver": "gen-version",
105
+ "build:test": "tsc --project ./src/test/tsconfig.json",
106
+ "ci:build:docs": "api-extractor run --typescript-compiler-folder ../../../node_modules/typescript && copyfiles -u 1 ./_api-extractor-temp/* ../../../_api-extractor-temp/",
107
+ "clean": "rimraf dist lib *.tsbuildinfo *.build.log",
108
+ "eslint": "eslint --format stylish src",
109
+ "eslint:fix": "eslint --format stylish src --fix --fix-type problem,suggestion,layout",
110
+ "format": "npm run prettier:fix",
111
+ "lint": "npm run prettier && npm run eslint",
112
+ "lint:fix": "npm run prettier:fix && npm run eslint:fix",
113
+ "prettier": "prettier --check . --ignore-path ../../../.prettierignore",
114
+ "prettier:fix": "prettier --write . --ignore-path ../../../.prettierignore",
115
+ "test": "npm run test:mocha",
116
+ "test:coverage": "nyc npm test -- --reporter xunit --reporter-option output=nyc/junit-report.xml",
117
+ "test:mocha": "mocha --ignore 'dist/test/types/*' --recursive dist/test -r node_modules/@fluidframework/mocha-test-setup --unhandled-rejections=strict",
118
+ "test:mocha:multireport": "cross-env FLUID_TEST_MULTIREPORT=1 npm run test:mocha",
119
+ "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
120
+ "tsc": "tsc",
121
+ "tsc:watch": "tsc --watch",
122
+ "typetests:gen": "fluid-type-test-generator",
123
+ "typetests:prepare": "flub generate typetests --prepare --dir . --pin"
116
124
  }
117
- }
125
+ }
@@ -13,11 +13,12 @@ import { assert, performance, TypedEventEmitter } from "@fluidframework/common-u
13
13
  import {
14
14
  IDeltaQueue,
15
15
  ReadOnlyInfo,
16
- IConnectionDetails,
16
+ IConnectionDetailsInternal,
17
17
  ICriticalContainerError,
18
18
  } from "@fluidframework/container-definitions";
19
19
  import { GenericError, UsageError } from "@fluidframework/container-utils";
20
20
  import {
21
+ DriverErrorType,
21
22
  IAnyDriverError,
22
23
  IDocumentService,
23
24
  IDocumentDeltaConnection,
@@ -28,8 +29,6 @@ import {
28
29
  createWriteError,
29
30
  createGenericNetworkError,
30
31
  getRetryDelayFromError,
31
- waitForConnectedState,
32
- DeltaStreamConnectionForbiddenError,
33
32
  logNetworkFailure,
34
33
  isRuntimeMessage,
35
34
  } from "@fluidframework/driver-utils";
@@ -135,6 +134,19 @@ class NoDeltaStream
135
134
  }
136
135
  }
137
136
 
137
+ const waitForOnline = async (): Promise<void> => {
138
+ // Only wait if we have a strong signal that we're offline - otherwise assume we're online.
139
+ if (globalThis.navigator?.onLine === false && globalThis.addEventListener !== undefined) {
140
+ return new Promise<void>((resolve) => {
141
+ const resolveAndRemoveListener = () => {
142
+ resolve();
143
+ globalThis.removeEventListener("online", resolveAndRemoveListener);
144
+ };
145
+ globalThis.addEventListener("online", resolveAndRemoveListener);
146
+ });
147
+ }
148
+ };
149
+
138
150
  /**
139
151
  * Interface to track the current in-progress connection attempt.
140
152
  */
@@ -266,9 +278,21 @@ export class ConnectionManager implements IConnectionManager {
266
278
 
267
279
  public shouldJoinWrite(): boolean {
268
280
  // We don't have to wait for ack for topmost NoOps. So subtract those.
269
- return (
270
- this.clientSequenceNumberObserved < this.clientSequenceNumber - this.localOpsToIgnore
271
- );
281
+ const outstandingOps =
282
+ this.clientSequenceNumberObserved < this.clientSequenceNumber - this.localOpsToIgnore;
283
+
284
+ // Previous behavior was to force write mode here only when there are outstanding ops (besides
285
+ // no-ops). The dirty signal from runtime should provide the same behavior, but also support
286
+ // stashed ops that weren't submitted to container layer yet. For safety, we want to retain the
287
+ // same behavior whenever dirty is false.
288
+ const isDirty = this.containerDirty();
289
+ if (outstandingOps !== isDirty) {
290
+ this.logger.sendTelemetryEvent({
291
+ eventName: "DesiredConnectionModeMismatch",
292
+ details: JSON.stringify({ outstandingOps, isDirty }),
293
+ });
294
+ }
295
+ return outstandingOps || isDirty;
272
296
  }
273
297
 
274
298
  /**
@@ -281,10 +305,7 @@ export class ConnectionManager implements IConnectionManager {
281
305
  * and do not know if user has write access to a file.
282
306
  */
283
307
  private get readonly(): boolean | undefined {
284
- if (this._forceReadonly) {
285
- return true;
286
- }
287
- return this._readonlyPermissions;
308
+ return this.readOnlyInfo.readonly;
288
309
  }
289
310
 
290
311
  public get readOnlyInfo(): ReadOnlyInfo {
@@ -302,11 +323,12 @@ export class ConnectionManager implements IConnectionManager {
302
323
  return { readonly: this._readonlyPermissions };
303
324
  }
304
325
 
305
- private static detailsFromConnection(connection: IDocumentDeltaConnection): IConnectionDetails {
326
+ private static detailsFromConnection(
327
+ connection: IDocumentDeltaConnection,
328
+ ): IConnectionDetailsInternal {
306
329
  return {
307
330
  claims: connection.claims,
308
331
  clientId: connection.clientId,
309
- existing: connection.existing,
310
332
  checkpointSequenceNumber: connection.checkpointSequenceNumber,
311
333
  get initialClients() {
312
334
  return connection.initialClients;
@@ -319,6 +341,7 @@ export class ConnectionManager implements IConnectionManager {
319
341
 
320
342
  constructor(
321
343
  private readonly serviceProvider: () => IDocumentService | undefined,
344
+ public readonly containerDirty: () => boolean,
322
345
  private client: IClient,
323
346
  reconnectAllowed: boolean,
324
347
  private readonly logger: ITelemetryLogger,
@@ -355,10 +378,7 @@ export class ConnectionManager implements IConnectionManager {
355
378
 
356
379
  this._outbound.clear();
357
380
 
358
- const disconnectReason =
359
- error !== undefined
360
- ? `Closing DeltaManager (${error.message})`
361
- : "Closing DeltaManager";
381
+ const disconnectReason = "Closing DeltaManager";
362
382
 
363
383
  // This raises "disconnect" event if we have active connection.
364
384
  this.disconnectFromDeltaStream(disconnectReason);
@@ -543,7 +563,7 @@ export class ConnectionManager implements IConnectionManager {
543
563
  if (
544
564
  typeof origError === "object" &&
545
565
  origError !== null &&
546
- origError?.errorType === DeltaStreamConnectionForbiddenError.errorType
566
+ origError?.errorType === DriverErrorType.deltaStreamConnectionForbidden
547
567
  ) {
548
568
  connection = new NoDeltaStream();
549
569
  requestedMode = "read";
@@ -572,12 +592,25 @@ export class ConnectionManager implements IConnectionManager {
572
592
  lastError = origError;
573
593
 
574
594
  const retryDelayFromError = getRetryDelayFromError(origError);
575
- delayMs = retryDelayFromError ?? Math.min(delayMs * 2, MaxReconnectDelayInMs);
576
-
577
595
  if (retryDelayFromError !== undefined) {
596
+ // If the error told us to wait, then we wait.
578
597
  this.props.reconnectionDelayHandler(retryDelayFromError, origError);
598
+ await new Promise<void>((resolve) => {
599
+ setTimeout(resolve, retryDelayFromError);
600
+ });
601
+ } else if (globalThis.navigator?.onLine !== false) {
602
+ // If the error didn't tell us to wait, let's still wait a little bit before retrying.
603
+ // We skip this delay if we're confident we're offline, because we probably just need to wait to come back online.
604
+ await new Promise<void>((resolve) => {
605
+ setTimeout(resolve, delayMs);
606
+ delayMs = Math.min(delayMs * 2, MaxReconnectDelayInMs);
607
+ });
579
608
  }
580
- await waitForConnectedState(delayMs);
609
+
610
+ // If we believe we're offline, we assume there's no point in trying until we at least think we're online.
611
+ // NOTE: This isn't strictly true for drivers that don't require network (e.g. local driver). Really this logic
612
+ // should probably live in the driver.
613
+ await waitForOnline();
581
614
  }
582
615
  }
583
616
 
@@ -615,7 +648,7 @@ export class ConnectionManager implements IConnectionManager {
615
648
  * @param args - The connection arguments
616
649
  */
617
650
  private triggerConnect(connectionMode: ConnectionMode) {
618
- // reconnect() has async await of waitForConnectedState(), and that causes potential race conditions
651
+ // reconnect() includes async awaits, and that causes potential race conditions
619
652
  // where we might already have a connection. If it were to happen, it's possible that we will connect
620
653
  // with different mode to `connectionMode`. Glancing through the caller chains, it looks like code should be
621
654
  // fine (if needed, reconnect flow will get triggered again). Places where new mode matters should encode it
@@ -664,10 +697,10 @@ export class ConnectionManager implements IConnectionManager {
664
697
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
665
698
  this._outbound.pause();
666
699
  this._outbound.clear();
667
- this.props.disconnectHandler(reason);
668
-
669
700
  connection.dispose();
670
701
 
702
+ this.props.disconnectHandler(reason);
703
+
671
704
  this._connectionVerboseProps = {};
672
705
 
673
706
  return true;
@@ -888,12 +921,20 @@ export class ConnectionManager implements IConnectionManager {
888
921
  return;
889
922
  }
890
923
 
924
+ // If the error tells us to wait before retrying, then do so.
891
925
  const delayMs = getRetryDelayFromError(error);
892
926
  if (error !== undefined && delayMs !== undefined) {
893
927
  this.props.reconnectionDelayHandler(delayMs, error);
894
- await waitForConnectedState(delayMs);
928
+ await new Promise<void>((resolve) => {
929
+ setTimeout(resolve, delayMs);
930
+ });
895
931
  }
896
932
 
933
+ // If we believe we're offline, we assume there's no point in trying again until we at least think we're online.
934
+ // NOTE: This isn't strictly true for drivers that don't require network (e.g. local driver). Really this logic
935
+ // should probably live in the driver.
936
+ await waitForOnline();
937
+
897
938
  this.triggerConnect(requestedMode);
898
939
  }
899
940
 
@@ -9,7 +9,7 @@ import {
9
9
  TelemetryEventCategory,
10
10
  } from "@fluidframework/common-definitions";
11
11
  import { assert, Timer } from "@fluidframework/common-utils";
12
- import { IConnectionDetails, IDeltaManager } from "@fluidframework/container-definitions";
12
+ import { IConnectionDetailsInternal, IDeltaManager } from "@fluidframework/container-definitions";
13
13
  import { ILocalSequencedClient } from "@fluidframework/protocol-base";
14
14
  import { ISequencedClient, IClient } from "@fluidframework/protocol-definitions";
15
15
  import { PerformanceEvent, loggerToMonitoringContext } from "@fluidframework/telemetry-utils";
@@ -56,7 +56,7 @@ export interface IConnectionStateHandler {
56
56
  containerSaved(): void;
57
57
  dispose(): void;
58
58
  initProtocol(protocol: IProtocolHandler): void;
59
- receivedConnectEvent(details: IConnectionDetails): void;
59
+ receivedConnectEvent(details: IConnectionDetailsInternal): void;
60
60
  receivedDisconnectEvent(reason: string): void;
61
61
  }
62
62
 
@@ -143,7 +143,7 @@ class ConnectionStateHandlerPassThrough
143
143
  return this.pimpl.receivedDisconnectEvent(reason);
144
144
  }
145
145
 
146
- public receivedConnectEvent(details: IConnectionDetails) {
146
+ public receivedConnectEvent(details: IConnectionDetailsInternal) {
147
147
  return this.pimpl.receivedConnectEvent(details);
148
148
  }
149
149
 
@@ -288,7 +288,7 @@ class ConnectionStateHandler implements IConnectionStateHandler {
288
288
  private readonly prevClientLeftTimer: Timer;
289
289
  private readonly joinOpTimer: Timer;
290
290
  private protocol?: IProtocolHandler;
291
- private connection?: IConnectionDetails;
291
+ private connection?: IConnectionDetailsInternal;
292
292
  private _clientId?: string;
293
293
 
294
294
  private waitEvent: PerformanceEvent | undefined;
@@ -398,6 +398,18 @@ class ConnectionStateHandler implements IConnectionStateHandler {
398
398
  });
399
399
  }
400
400
  this.applyForConnectedState("addMemberEvent");
401
+ } else if (clientId === this.clientId) {
402
+ // If we see our clientId and it's not also our pending ID, it's our own join op
403
+ // being replayed, so start the timer in case our previous client is still in quorum
404
+ assert(
405
+ !this.waitingForLeaveOp,
406
+ 0x5d2 /* Unexpected join op with current clientId while waiting */,
407
+ );
408
+ assert(
409
+ this.connectionState !== ConnectionState.Connected,
410
+ 0x5d3 /* Unexpected join op with current clientId while connected */,
411
+ );
412
+ this.prevClientLeftTimer.restart();
401
413
  }
402
414
  }
403
415
 
@@ -472,7 +484,7 @@ class ConnectionStateHandler implements IConnectionStateHandler {
472
484
  * @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.
473
485
  * If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for
474
486
  */
475
- public receivedConnectEvent(details: IConnectionDetails) {
487
+ public receivedConnectEvent(details: IConnectionDetailsInternal) {
476
488
  this.connection = details;
477
489
 
478
490
  const oldState = this._connectionState;