@fluidframework/container-loader 2.0.0-internal.4.0.6 → 2.0.0-internal.4.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 (92) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +38 -0
  3. package/dist/connectionManager.d.ts +2 -1
  4. package/dist/connectionManager.d.ts.map +1 -1
  5. package/dist/connectionManager.js +16 -6
  6. package/dist/connectionManager.js.map +1 -1
  7. package/dist/connectionStateHandler.d.ts.map +1 -1
  8. package/dist/connectionStateHandler.js +7 -0
  9. package/dist/connectionStateHandler.js.map +1 -1
  10. package/dist/container.d.ts +35 -2
  11. package/dist/container.d.ts.map +1 -1
  12. package/dist/container.js +88 -43
  13. package/dist/container.js.map +1 -1
  14. package/dist/containerContext.d.ts +3 -3
  15. package/dist/containerContext.d.ts.map +1 -1
  16. package/dist/containerContext.js +5 -4
  17. package/dist/containerContext.js.map +1 -1
  18. package/dist/containerStorageAdapter.d.ts +27 -2
  19. package/dist/containerStorageAdapter.d.ts.map +1 -1
  20. package/dist/containerStorageAdapter.js +64 -6
  21. package/dist/containerStorageAdapter.js.map +1 -1
  22. package/dist/deltaManager.d.ts +1 -3
  23. package/dist/deltaManager.d.ts.map +1 -1
  24. package/dist/deltaManager.js +3 -8
  25. package/dist/deltaManager.js.map +1 -1
  26. package/dist/index.d.ts +3 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/loader.d.ts.map +1 -1
  31. package/dist/loader.js +4 -1
  32. package/dist/loader.js.map +1 -1
  33. package/dist/packageVersion.d.ts +1 -1
  34. package/dist/packageVersion.js +1 -1
  35. package/dist/packageVersion.js.map +1 -1
  36. package/dist/protocol.d.ts +1 -0
  37. package/dist/protocol.d.ts.map +1 -1
  38. package/dist/protocol.js +4 -2
  39. package/dist/protocol.js.map +1 -1
  40. package/dist/utils.d.ts.map +1 -1
  41. package/dist/utils.js.map +1 -1
  42. package/lib/connectionManager.d.ts +2 -1
  43. package/lib/connectionManager.d.ts.map +1 -1
  44. package/lib/connectionManager.js +16 -6
  45. package/lib/connectionManager.js.map +1 -1
  46. package/lib/connectionStateHandler.d.ts.map +1 -1
  47. package/lib/connectionStateHandler.js +7 -0
  48. package/lib/connectionStateHandler.js.map +1 -1
  49. package/lib/container.d.ts +35 -2
  50. package/lib/container.d.ts.map +1 -1
  51. package/lib/container.js +90 -45
  52. package/lib/container.js.map +1 -1
  53. package/lib/containerContext.d.ts +3 -3
  54. package/lib/containerContext.d.ts.map +1 -1
  55. package/lib/containerContext.js +5 -4
  56. package/lib/containerContext.js.map +1 -1
  57. package/lib/containerStorageAdapter.d.ts +27 -2
  58. package/lib/containerStorageAdapter.d.ts.map +1 -1
  59. package/lib/containerStorageAdapter.js +62 -6
  60. package/lib/containerStorageAdapter.js.map +1 -1
  61. package/lib/deltaManager.d.ts +1 -3
  62. package/lib/deltaManager.d.ts.map +1 -1
  63. package/lib/deltaManager.js +3 -8
  64. package/lib/deltaManager.js.map +1 -1
  65. package/lib/index.d.ts +3 -2
  66. package/lib/index.d.ts.map +1 -1
  67. package/lib/index.js +1 -1
  68. package/lib/index.js.map +1 -1
  69. package/lib/loader.d.ts.map +1 -1
  70. package/lib/loader.js +4 -1
  71. package/lib/loader.js.map +1 -1
  72. package/lib/packageVersion.d.ts +1 -1
  73. package/lib/packageVersion.js +1 -1
  74. package/lib/packageVersion.js.map +1 -1
  75. package/lib/protocol.d.ts +1 -0
  76. package/lib/protocol.d.ts.map +1 -1
  77. package/lib/protocol.js +3 -1
  78. package/lib/protocol.js.map +1 -1
  79. package/lib/utils.d.ts.map +1 -1
  80. package/lib/utils.js.map +1 -1
  81. package/package.json +17 -13
  82. package/src/connectionManager.ts +17 -7
  83. package/src/connectionStateHandler.ts +12 -0
  84. package/src/container.ts +138 -65
  85. package/src/containerContext.ts +6 -6
  86. package/src/containerStorageAdapter.ts +88 -5
  87. package/src/deltaManager.ts +3 -10
  88. package/src/index.ts +3 -1
  89. package/src/loader.ts +4 -1
  90. package/src/packageVersion.ts +1 -1
  91. package/src/protocol.ts +4 -1
  92. package/src/utils.ts +3 -1
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.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 { IAudienceOwner } from \"@fluidframework/container-definitions\";\nimport {\n\tILocalSequencedClient,\n\tIProtocolHandler as IBaseProtocolHandler,\n\tIQuorumSnapshot,\n\tProtocolOpHandler,\n} from \"@fluidframework/protocol-base\";\nimport {\n\tIDocumentAttributes,\n\tIProcessMessageResult,\n\tISequencedDocumentMessage,\n\tISignalClient,\n\tISignalMessage,\n\tMessageType,\n} from \"@fluidframework/protocol-definitions\";\nimport { canBeCoalescedByService } from \"@fluidframework/driver-utils\";\n\n// ADO: #1986: Start using enum from protocol-base.\nexport enum SignalType {\n\tClientJoin = \"join\", // same value as MessageType.ClientJoin,\n\tClientLeave = \"leave\", // same value as MessageType.ClientLeave,\n\tClear = \"clear\", // used only by client for synthetic signals\n}\n\n/**\n * Function to be used for creating a protocol handler.\n */\nexport type ProtocolHandlerBuilder = (\n\tattributes: IDocumentAttributes,\n\tsnapshot: IQuorumSnapshot,\n\tsendProposal: (key: string, value: any) => number,\n) => IProtocolHandler;\n\nexport interface IProtocolHandler extends IBaseProtocolHandler {\n\treadonly audience: IAudienceOwner;\n\tprocessSignal(message: ISignalMessage);\n}\n\nexport class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandler {\n\tconstructor(\n\t\tattributes: IDocumentAttributes,\n\t\tquorumSnapshot: IQuorumSnapshot,\n\t\tsendProposal: (key: string, value: any) => number,\n\t\treadonly audience: IAudienceOwner,\n\t) {\n\t\tsuper(\n\t\t\tattributes.minimumSequenceNumber,\n\t\t\tattributes.sequenceNumber,\n\t\t\tattributes.term,\n\t\t\tquorumSnapshot.members,\n\t\t\tquorumSnapshot.proposals,\n\t\t\tquorumSnapshot.values,\n\t\t\tsendProposal,\n\t\t);\n\n\t\t// Join / leave signals are ignored for \"write\" clients in favor of join / leave ops\n\t\tthis.quorum.on(\"addMember\", (clientId, details) =>\n\t\t\taudience.addMember(clientId, details.client),\n\t\t);\n\t\tthis.quorum.on(\"removeMember\", (clientId) => audience.removeMember(clientId));\n\t\tfor (const [clientId, details] of this.quorum.getMembers()) {\n\t\t\tthis.audience.addMember(clientId, details.client);\n\t\t}\n\t}\n\n\tpublic processMessage(\n\t\tmessage: ISequencedDocumentMessage,\n\t\tlocal: boolean,\n\t): IProcessMessageResult {\n\t\tconst client: ILocalSequencedClient | undefined = this.quorum.getMember(message.clientId);\n\n\t\t// Check and report if we're getting messages from a clientId that we previously\n\t\t// flagged as shouldHaveLeft, or from a client that's not in the quorum but should be\n\t\tif (message.clientId != null) {\n\t\t\tif (client === undefined && message.type !== MessageType.ClientJoin) {\n\t\t\t\t// pre-0.58 error message: messageClientIdMissingFromQuorum\n\t\t\t\tthrow new Error(\"Remote message's clientId is missing from the quorum\");\n\t\t\t}\n\n\t\t\tif (client?.shouldHaveLeft === true && !canBeCoalescedByService(message)) {\n\t\t\t\t// pre-0.58 error message: messageClientIdShouldHaveLeft\n\t\t\t\tthrow new Error(\"Remote message's clientId already should have left\");\n\t\t\t}\n\t\t}\n\n\t\treturn super.processMessage(message, local);\n\t}\n\n\tpublic processSignal(message: ISignalMessage) {\n\t\tconst innerContent = message.content as { content: any; type: string };\n\t\tswitch (innerContent.type) {\n\t\t\tcase SignalType.Clear: {\n\t\t\t\tconst members = this.audience.getMembers();\n\t\t\t\tfor (const [clientId, client] of members) {\n\t\t\t\t\tif (client.mode === \"read\") {\n\t\t\t\t\t\tthis.audience.removeMember(clientId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SignalType.ClientJoin: {\n\t\t\t\tconst newClient = innerContent.content as ISignalClient;\n\t\t\t\t// Ignore write clients - quorum will control such clients.\n\t\t\t\tif (newClient.client.mode === \"read\") {\n\t\t\t\t\tthis.audience.addMember(newClient.clientId, newClient.client);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SignalType.ClientLeave: {\n\t\t\t\tconst leftClientId = innerContent.content as string;\n\t\t\t\t// Ignore write clients - quorum will control such clients.\n\t\t\t\tif (this.audience.getMember(leftClientId)?.mode === \"read\") {\n\t\t\t\t\tthis.audience.removeMember(leftClientId);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.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 { IAudienceOwner } from \"@fluidframework/container-definitions\";\nimport {\n\tILocalSequencedClient,\n\tIProtocolHandler as IBaseProtocolHandler,\n\tIQuorumSnapshot,\n\tProtocolOpHandler,\n} from \"@fluidframework/protocol-base\";\nimport {\n\tIDocumentAttributes,\n\tIProcessMessageResult,\n\tISequencedDocumentMessage,\n\tISignalClient,\n\tISignalMessage,\n\tMessageType,\n} from \"@fluidframework/protocol-definitions\";\nimport { canBeCoalescedByService } from \"@fluidframework/driver-utils\";\n\n// \"term\" was an experimental feature that is being removed. The only safe value to use is 1.\nexport const OnlyValidTermValue = 1 as const;\n\n// ADO: #1986: Start using enum from protocol-base.\nexport enum SignalType {\n\tClientJoin = \"join\", // same value as MessageType.ClientJoin,\n\tClientLeave = \"leave\", // same value as MessageType.ClientLeave,\n\tClear = \"clear\", // used only by client for synthetic signals\n}\n\n/**\n * Function to be used for creating a protocol handler.\n */\nexport type ProtocolHandlerBuilder = (\n\tattributes: IDocumentAttributes,\n\tsnapshot: IQuorumSnapshot,\n\tsendProposal: (key: string, value: any) => number,\n) => IProtocolHandler;\n\nexport interface IProtocolHandler extends IBaseProtocolHandler {\n\treadonly audience: IAudienceOwner;\n\tprocessSignal(message: ISignalMessage);\n}\n\nexport class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandler {\n\tconstructor(\n\t\tattributes: IDocumentAttributes,\n\t\tquorumSnapshot: IQuorumSnapshot,\n\t\tsendProposal: (key: string, value: any) => number,\n\t\treadonly audience: IAudienceOwner,\n\t) {\n\t\tsuper(\n\t\t\tattributes.minimumSequenceNumber,\n\t\t\tattributes.sequenceNumber,\n\t\t\tOnlyValidTermValue,\n\t\t\tquorumSnapshot.members,\n\t\t\tquorumSnapshot.proposals,\n\t\t\tquorumSnapshot.values,\n\t\t\tsendProposal,\n\t\t);\n\n\t\t// Join / leave signals are ignored for \"write\" clients in favor of join / leave ops\n\t\tthis.quorum.on(\"addMember\", (clientId, details) =>\n\t\t\taudience.addMember(clientId, details.client),\n\t\t);\n\t\tthis.quorum.on(\"removeMember\", (clientId) => audience.removeMember(clientId));\n\t\tfor (const [clientId, details] of this.quorum.getMembers()) {\n\t\t\tthis.audience.addMember(clientId, details.client);\n\t\t}\n\t}\n\n\tpublic processMessage(\n\t\tmessage: ISequencedDocumentMessage,\n\t\tlocal: boolean,\n\t): IProcessMessageResult {\n\t\tconst client: ILocalSequencedClient | undefined = this.quorum.getMember(message.clientId);\n\n\t\t// Check and report if we're getting messages from a clientId that we previously\n\t\t// flagged as shouldHaveLeft, or from a client that's not in the quorum but should be\n\t\tif (message.clientId != null) {\n\t\t\tif (client === undefined && message.type !== MessageType.ClientJoin) {\n\t\t\t\t// pre-0.58 error message: messageClientIdMissingFromQuorum\n\t\t\t\tthrow new Error(\"Remote message's clientId is missing from the quorum\");\n\t\t\t}\n\n\t\t\tif (client?.shouldHaveLeft === true && !canBeCoalescedByService(message)) {\n\t\t\t\t// pre-0.58 error message: messageClientIdShouldHaveLeft\n\t\t\t\tthrow new Error(\"Remote message's clientId already should have left\");\n\t\t\t}\n\t\t}\n\n\t\treturn super.processMessage(message, local);\n\t}\n\n\tpublic processSignal(message: ISignalMessage) {\n\t\tconst innerContent = message.content as { content: any; type: string };\n\t\tswitch (innerContent.type) {\n\t\t\tcase SignalType.Clear: {\n\t\t\t\tconst members = this.audience.getMembers();\n\t\t\t\tfor (const [clientId, client] of members) {\n\t\t\t\t\tif (client.mode === \"read\") {\n\t\t\t\t\t\tthis.audience.removeMember(clientId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SignalType.ClientJoin: {\n\t\t\t\tconst newClient = innerContent.content as ISignalClient;\n\t\t\t\t// Ignore write clients - quorum will control such clients.\n\t\t\t\tif (newClient.client.mode === \"read\") {\n\t\t\t\t\tthis.audience.addMember(newClient.clientId, newClient.client);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SignalType.ClientLeave: {\n\t\t\t\tconst leftClientId = innerContent.content as string;\n\t\t\t\t// Ignore write clients - quorum will control such clients.\n\t\t\t\tif (this.audience.getMember(leftClientId)?.mode === \"read\") {\n\t\t\t\t\tthis.audience.removeMember(leftClientId);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\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;AAMhG,MAAM,WAAW,6BAA8B,SAAQ,aAAa;IACnE,aAAa,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAAA;KAAE,CAAC;IACnD,KAAK,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,6BAA6B,CAAA;KAAE,CAAC;CACzD;AAED,MAAM,WAAW,UAAU;IAC1B,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;CACnC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAW5D;AA0DD;;;;GAIG;AACH,wBAAgB,0CAA0C,CACzD,mBAAmB,EAAE,YAAY,EACjC,cAAc,EAAE,YAAY,GAC1B,6BAA6B,CAW/B;AAID,eAAO,MAAM,sCAAsC,8BAA+B,YAAY,kCAY7F,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;AAMhG,MAAM,WAAW,6BAA8B,SAAQ,aAAa;IACnE,aAAa,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAAA;KAAE,CAAC;IACnD,KAAK,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,6BAA6B,CAAA;KAAE,CAAC;CACzD;AAED,MAAM,WAAW,UAAU;IAC1B,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;CACnC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAW5D;AA0DD;;;;GAIG;AACH,wBAAgB,0CAA0C,CACzD,mBAAmB,EAAE,YAAY,EACjC,cAAc,EAAE,YAAY,GAC1B,6BAA6B,CAW/B;AAID,eAAO,MAAM,sCAAsC,8BACvB,YAAY,KACrC,6BAYF,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,aAAa,GAAG,aAAa,CAE9E"}
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\";\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 = (detachedContainerSnapshot: ISummaryTree) => {\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"]}
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-internal.4.0.6",
3
+ "version": "2.0.0-internal.4.1.1",
4
4
  "description": "Fluid container loader",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -37,14 +37,14 @@
37
37
  "dependencies": {
38
38
  "@fluidframework/common-definitions": "^0.20.1",
39
39
  "@fluidframework/common-utils": "^1.1.1",
40
- "@fluidframework/container-definitions": ">=2.0.0-internal.4.0.6 <2.0.0-internal.5.0.0",
41
- "@fluidframework/container-utils": ">=2.0.0-internal.4.0.6 <2.0.0-internal.5.0.0",
42
- "@fluidframework/core-interfaces": ">=2.0.0-internal.4.0.6 <2.0.0-internal.5.0.0",
43
- "@fluidframework/driver-definitions": ">=2.0.0-internal.4.0.6 <2.0.0-internal.5.0.0",
44
- "@fluidframework/driver-utils": ">=2.0.0-internal.4.0.6 <2.0.0-internal.5.0.0",
45
- "@fluidframework/protocol-base": "^0.1038.4000",
40
+ "@fluidframework/container-definitions": ">=2.0.0-internal.4.1.1 <2.0.0-internal.4.2.0",
41
+ "@fluidframework/container-utils": ">=2.0.0-internal.4.1.1 <2.0.0-internal.4.2.0",
42
+ "@fluidframework/core-interfaces": ">=2.0.0-internal.4.1.1 <2.0.0-internal.4.2.0",
43
+ "@fluidframework/driver-definitions": ">=2.0.0-internal.4.1.1 <2.0.0-internal.4.2.0",
44
+ "@fluidframework/driver-utils": ">=2.0.0-internal.4.1.1 <2.0.0-internal.4.2.0",
45
+ "@fluidframework/protocol-base": "^0.1039.1000",
46
46
  "@fluidframework/protocol-definitions": "^1.1.0",
47
- "@fluidframework/telemetry-utils": ">=2.0.0-internal.4.0.6 <2.0.0-internal.5.0.0",
47
+ "@fluidframework/telemetry-utils": ">=2.0.0-internal.4.1.1 <2.0.0-internal.4.2.0",
48
48
  "abort-controller": "^3.0.0",
49
49
  "double-ended-queue": "^2.1.0-0",
50
50
  "events": "^3.1.0",
@@ -53,13 +53,13 @@
53
53
  "uuid": "^8.3.1"
54
54
  },
55
55
  "devDependencies": {
56
- "@fluid-internal/test-loader-utils": ">=2.0.0-internal.4.0.6 <2.0.0-internal.5.0.0",
57
- "@fluid-tools/build-cli": "^0.13.0",
56
+ "@fluid-internal/test-loader-utils": ">=2.0.0-internal.4.1.1 <2.0.0-internal.4.2.0",
57
+ "@fluid-tools/build-cli": "^0.15.0",
58
58
  "@fluidframework/build-common": "^1.1.0",
59
- "@fluidframework/build-tools": "^0.13.0",
60
- "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.0.0-internal.4.0.0",
59
+ "@fluidframework/build-tools": "^0.15.0",
60
+ "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.0.0-internal.4.1.0",
61
61
  "@fluidframework/eslint-config-fluid": "^2.0.0",
62
- "@fluidframework/mocha-test-setup": ">=2.0.0-internal.4.0.6 <2.0.0-internal.5.0.0",
62
+ "@fluidframework/mocha-test-setup": ">=2.0.0-internal.4.1.1 <2.0.0-internal.4.2.0",
63
63
  "@microsoft/api-extractor": "^7.34.4",
64
64
  "@types/double-ended-queue": "^2.1.0",
65
65
  "@types/events": "^3.0.0",
@@ -72,6 +72,9 @@
72
72
  "cross-env": "^7.0.3",
73
73
  "eslint": "~8.6.0",
74
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",
75
78
  "nyc": "^15.1.0",
76
79
  "prettier": "~2.6.2",
77
80
  "rimraf": "^4.4.0",
@@ -103,6 +106,7 @@
103
106
  "test": "npm run test:mocha",
104
107
  "test:coverage": "nyc npm test -- --reporter xunit --reporter-option output=nyc/junit-report.xml",
105
108
  "test:mocha": "mocha --ignore 'dist/test/types/*' --recursive dist/test -r node_modules/@fluidframework/mocha-test-setup --unhandled-rejections=strict",
109
+ "test:mocha:multireport": "cross-env FLUID_TEST_MULTIREPORT=1 npm run test:mocha",
106
110
  "test:mocha:verbose": "cross-env FLUID_TEST_VERBOSE=1 npm run test:mocha",
107
111
  "tsc": "tsc",
108
112
  "tsc:watch": "tsc --watch",
@@ -278,9 +278,21 @@ export class ConnectionManager implements IConnectionManager {
278
278
 
279
279
  public shouldJoinWrite(): boolean {
280
280
  // We don't have to wait for ack for topmost NoOps. So subtract those.
281
- return (
282
- this.clientSequenceNumberObserved < this.clientSequenceNumber - this.localOpsToIgnore
283
- );
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;
284
296
  }
285
297
 
286
298
  /**
@@ -293,10 +305,7 @@ export class ConnectionManager implements IConnectionManager {
293
305
  * and do not know if user has write access to a file.
294
306
  */
295
307
  private get readonly(): boolean | undefined {
296
- if (this._forceReadonly) {
297
- return true;
298
- }
299
- return this._readonlyPermissions;
308
+ return this.readOnlyInfo.readonly;
300
309
  }
301
310
 
302
311
  public get readOnlyInfo(): ReadOnlyInfo {
@@ -332,6 +341,7 @@ export class ConnectionManager implements IConnectionManager {
332
341
 
333
342
  constructor(
334
343
  private readonly serviceProvider: () => IDocumentService | undefined,
344
+ public readonly containerDirty: () => boolean,
335
345
  private client: IClient,
336
346
  reconnectAllowed: boolean,
337
347
  private readonly logger: ITelemetryLogger,
@@ -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
 
package/src/container.ts CHANGED
@@ -5,6 +5,9 @@
5
5
 
6
6
  // eslint-disable-next-line import/no-internal-modules
7
7
  import merge from "lodash/merge";
8
+ // eslint-disable-next-line import/no-internal-modules
9
+ import cloneDeep from "lodash/cloneDeep";
10
+
8
11
  import { v4 as uuid } from "uuid";
9
12
  import {
10
13
  ITelemetryLogger,
@@ -54,7 +57,6 @@ import {
54
57
  ICommittedProposal,
55
58
  IDocumentAttributes,
56
59
  IDocumentMessage,
57
- IProtocolState,
58
60
  IQuorumClients,
59
61
  IQuorumProposals,
60
62
  ISequencedClient,
@@ -87,7 +89,12 @@ import { DeltaManager, IConnectionArgs } from "./deltaManager";
87
89
  import { DeltaManagerProxy } from "./deltaManagerProxy";
88
90
  import { ILoaderOptions, Loader, RelativeLoader } from "./loader";
89
91
  import { pkgVersion } from "./packageVersion";
90
- import { ContainerStorageAdapter } from "./containerStorageAdapter";
92
+ import {
93
+ ContainerStorageAdapter,
94
+ getBlobContentsFromTree,
95
+ getBlobContentsFromTreeWithBlobContents,
96
+ ISerializableBlobContents,
97
+ } from "./containerStorageAdapter";
91
98
  import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler";
92
99
  import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
93
100
  import {
@@ -98,13 +105,22 @@ import {
98
105
  import { CollabWindowTracker } from "./collabWindowTracker";
99
106
  import { ConnectionManager } from "./connectionManager";
100
107
  import { ConnectionState } from "./connectionState";
101
- import { IProtocolHandler, ProtocolHandler, ProtocolHandlerBuilder } from "./protocol";
108
+ import {
109
+ OnlyValidTermValue,
110
+ IProtocolHandler,
111
+ ProtocolHandler,
112
+ ProtocolHandlerBuilder,
113
+ } from "./protocol";
102
114
 
103
115
  const detachedContainerRefSeqNumber = 0;
104
116
 
105
117
  const dirtyContainerEvent = "dirty";
106
118
  const savedContainerEvent = "saved";
107
119
 
120
+ /**
121
+ * @deprecated this is an internal interface and will not longer be exported in future versions
122
+ * @internal
123
+ */
108
124
  export interface IContainerLoadOptions {
109
125
  /**
110
126
  * Disables the Container from reconnecting if false, allows reconnect otherwise.
@@ -125,6 +141,10 @@ export interface IContainerLoadOptions {
125
141
  loadMode?: IContainerLoadMode;
126
142
  }
127
143
 
144
+ /**
145
+ * @deprecated this is an internal interface and will not longer be exported in future versions
146
+ * @internal
147
+ */
128
148
  export interface IContainerConfig {
129
149
  resolvedUrl?: IFluidResolvedUrl;
130
150
  canReconnect?: boolean;
@@ -255,11 +275,27 @@ export async function ReportIfTooLong(
255
275
  /**
256
276
  * State saved by a container at close time, to be used to load a new instance
257
277
  * of the container to the same state
278
+ * @deprecated this is an internal interface and will not longer be exported in future versions
279
+ * @internal
258
280
  */
259
281
  export interface IPendingContainerState {
260
282
  pendingRuntimeState: unknown;
283
+ /**
284
+ * Snapshot from which container initially loaded.
285
+ */
286
+ baseSnapshot: ISnapshotTree;
287
+ /**
288
+ * Serializable blobs from the base snapshot. Used to load offline since
289
+ * storage is not available.
290
+ */
291
+ snapshotBlobs: ISerializableBlobContents;
292
+ /**
293
+ * All ops since base snapshot sequence number up to the latest op
294
+ * seen when the container was closed. Used to apply stashed (saved pending)
295
+ * ops at the same sequence number at which they were made.
296
+ */
297
+ savedOps: ISequencedDocumentMessage[];
261
298
  url: string;
262
- protocol: IProtocolState;
263
299
  term: number;
264
300
  clientId?: string;
265
301
  }
@@ -277,6 +313,7 @@ export class Container
277
313
 
278
314
  /**
279
315
  * Load an existing container.
316
+ * @internal
280
317
  */
281
318
  public static async load(
282
319
  loader: Loader,
@@ -470,6 +507,9 @@ export class Container
470
507
  private _resolvedUrl: IFluidResolvedUrl | undefined;
471
508
  private attachStarted = false;
472
509
  private _dirtyContainer = false;
510
+ private readonly savedOps: ISequencedDocumentMessage[] = [];
511
+ private baseSnapshot?: ISnapshotTree;
512
+ private baseSnapshotBlobs?: ISerializableBlobContents;
473
513
 
474
514
  private lastVisible: number | undefined;
475
515
  private readonly visibilityEventHandler: (() => void) | undefined;
@@ -552,6 +592,14 @@ export class Container
552
592
  return this._deltaManager.clientDetails;
553
593
  }
554
594
 
595
+ private get offlineLoadEnabled(): boolean {
596
+ // summarizer will not have any pending state we want to save
597
+ return (
598
+ (this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad") ?? false) &&
599
+ this.clientDetails.capabilities.interactive
600
+ );
601
+ }
602
+
555
603
  /**
556
604
  * Get the code details that are currently specified for the container.
557
605
  * @returns The current code details if any are specified, undefined if none are specified.
@@ -634,6 +682,9 @@ export class Container
634
682
  return await this._context.getEntryPoint?.();
635
683
  }
636
684
 
685
+ /**
686
+ * @internal
687
+ */
637
688
  constructor(
638
689
  private readonly loader: Loader,
639
690
  config: IContainerConfig,
@@ -697,13 +748,10 @@ export class Container
697
748
  // Prefix all events in this file with container-loader
698
749
  this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
699
750
 
700
- this.options = {
701
- ...this.loader.services.options,
702
- };
751
+ this.options = cloneDeep(this.loader.services.options);
703
752
 
704
753
  this._deltaManager = this.createDeltaManager();
705
754
 
706
- this._clientId = config.serializedContainerState?.clientId;
707
755
  this.connectionStateHandler = createConnectionStateHandler(
708
756
  {
709
757
  logger: this.mc.logger,
@@ -759,7 +807,7 @@ export class Container
759
807
  },
760
808
  },
761
809
  this.deltaManager,
762
- this._clientId,
810
+ config.serializedContainerState?.clientId,
763
811
  );
764
812
 
765
813
  this.on(savedContainerEvent, () => {
@@ -783,6 +831,7 @@ export class Container
783
831
  this.storageAdapter = new ContainerStorageAdapter(
784
832
  this.loader.services.detachedBlobStorage,
785
833
  this.mc.logger,
834
+ config.serializedContainerState?.snapshotBlobs,
786
835
  addProtocolSummaryIfMissing,
787
836
  forceEnableSummarizeProtocolTree,
788
837
  );
@@ -948,6 +997,9 @@ export class Container
948
997
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
949
998
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
950
999
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
1000
+ if (!this.offlineLoadEnabled) {
1001
+ throw new UsageError("Can't get pending local state unless offline load is enabled");
1002
+ }
951
1003
  assert(
952
1004
  this.attachState === AttachState.Attached,
953
1005
  0x0d1 /* "Container should be attached before close" */,
@@ -957,15 +1009,15 @@ export class Container
957
1009
  0x0d2 /* "resolved url should be valid Fluid url" */,
958
1010
  );
959
1011
  assert(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
960
- assert(
961
- this._protocolHandler.attributes.term !== undefined,
962
- 0x37e /* Must have a valid protocol handler instance */,
963
- );
1012
+ assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
1013
+ assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
964
1014
  const pendingState: IPendingContainerState = {
965
1015
  pendingRuntimeState: this.context.getPendingLocalState(),
1016
+ baseSnapshot: this.baseSnapshot,
1017
+ snapshotBlobs: this.baseSnapshotBlobs,
1018
+ savedOps: this.savedOps,
966
1019
  url: this.resolvedUrl.url,
967
- protocol: this.protocolHandler.getProtocolState(),
968
- term: this._protocolHandler.attributes.term,
1020
+ term: OnlyValidTermValue,
969
1021
  clientId: this.clientId,
970
1022
  };
971
1023
 
@@ -1046,9 +1098,13 @@ export class Container
1046
1098
  // starting to attach the container to storage.
1047
1099
  // Also, this should only be fired in detached container.
1048
1100
  this._attachState = AttachState.Attaching;
1049
- this.context.notifyAttaching(
1050
- getSnapshotTreeFromSerializedContainer(summary),
1051
- );
1101
+ this.emit("attaching");
1102
+ if (this.offlineLoadEnabled) {
1103
+ const snapshot = getSnapshotTreeFromSerializedContainer(summary);
1104
+ this.baseSnapshot = snapshot;
1105
+ this.baseSnapshotBlobs =
1106
+ getBlobContentsFromTreeWithBlobContents(snapshot);
1107
+ }
1052
1108
  }
1053
1109
 
1054
1110
  // Actually go and create the resolved document
@@ -1108,9 +1164,13 @@ export class Container
1108
1164
  summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
1109
1165
 
1110
1166
  this._attachState = AttachState.Attaching;
1111
- this.context.notifyAttaching(
1112
- getSnapshotTreeFromSerializedContainer(summary),
1113
- );
1167
+ this.emit("attaching");
1168
+ if (this.offlineLoadEnabled) {
1169
+ const snapshot = getSnapshotTreeFromSerializedContainer(summary);
1170
+ this.baseSnapshot = snapshot;
1171
+ this.baseSnapshotBlobs =
1172
+ getBlobContentsFromTreeWithBlobContents(snapshot);
1173
+ }
1114
1174
 
1115
1175
  await this.storageAdapter.uploadSummaryWithContext(summary, {
1116
1176
  referenceSequenceNumber: 0,
@@ -1344,7 +1404,7 @@ export class Container
1344
1404
 
1345
1405
  // Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
1346
1406
  // DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
1347
- if (loadMode.deltaConnection === undefined) {
1407
+ if (loadMode.deltaConnection === undefined && !pendingLocalState) {
1348
1408
  this.connectToDeltaStream(connectionArgs);
1349
1409
  }
1350
1410
 
@@ -1364,20 +1424,30 @@ export class Container
1364
1424
  const { snapshot, versionId } =
1365
1425
  pendingLocalState === undefined
1366
1426
  ? await this.fetchSnapshotTree(specifiedVersion)
1367
- : { snapshot: undefined, versionId: undefined };
1368
- assert(
1369
- snapshot !== undefined || pendingLocalState !== undefined,
1370
- 0x237 /* "Snapshot should exist" */,
1427
+ : { snapshot: pendingLocalState.baseSnapshot, versionId: undefined };
1428
+
1429
+ if (pendingLocalState) {
1430
+ this.baseSnapshot = pendingLocalState.baseSnapshot;
1431
+ this.baseSnapshotBlobs = pendingLocalState.snapshotBlobs;
1432
+ } else {
1433
+ assert(snapshot !== undefined, 0x237 /* "Snapshot should exist" */);
1434
+ if (this.offlineLoadEnabled) {
1435
+ this.baseSnapshot = snapshot;
1436
+ // Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
1437
+ this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.storage);
1438
+ }
1439
+ }
1440
+
1441
+ const attributes: IDocumentAttributes = await this.getDocumentAttributes(
1442
+ this.storageAdapter,
1443
+ snapshot,
1371
1444
  );
1372
1445
 
1373
- const attributes: IDocumentAttributes =
1374
- pendingLocalState === undefined
1375
- ? await this.getDocumentAttributes(this.storageAdapter, snapshot)
1376
- : {
1377
- sequenceNumber: pendingLocalState.protocol.sequenceNumber,
1378
- minimumSequenceNumber: pendingLocalState.protocol.minimumSequenceNumber,
1379
- term: pendingLocalState.term,
1380
- };
1446
+ // If we saved ops, we will replay them and don't need DeltaManager to fetch them
1447
+ const sequenceNumber =
1448
+ pendingLocalState?.savedOps[pendingLocalState.savedOps.length - 1]?.sequenceNumber;
1449
+ const dmAttributes =
1450
+ sequenceNumber !== undefined ? { ...attributes, sequenceNumber } : attributes;
1381
1451
 
1382
1452
  let opsBeforeReturnP: Promise<void> | undefined;
1383
1453
 
@@ -1388,15 +1458,15 @@ export class Container
1388
1458
  // Start prefetch, but not set opsBeforeReturnP - boot is not blocked by it!
1389
1459
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1390
1460
  this.attachDeltaManagerOpHandler(
1391
- attributes,
1461
+ dmAttributes,
1392
1462
  loadMode.deltaConnection !== "none" ? "all" : "none",
1393
1463
  );
1394
1464
  break;
1395
1465
  case "cached":
1396
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(attributes, "cached");
1466
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
1397
1467
  break;
1398
1468
  case "all":
1399
- opsBeforeReturnP = this.attachDeltaManagerOpHandler(attributes, "all");
1469
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "all");
1400
1470
  break;
1401
1471
  default:
1402
1472
  unreachableCase(loadMode.opsBeforeReturn);
@@ -1404,22 +1474,7 @@ export class Container
1404
1474
 
1405
1475
  // ...load in the existing quorum
1406
1476
  // Initialize the protocol handler
1407
- if (pendingLocalState === undefined) {
1408
- await this.initializeProtocolStateFromSnapshot(
1409
- attributes,
1410
- this.storageAdapter,
1411
- snapshot,
1412
- );
1413
- } else {
1414
- this.initializeProtocolState(
1415
- attributes,
1416
- {
1417
- members: pendingLocalState.protocol.members,
1418
- proposals: pendingLocalState.protocol.proposals,
1419
- values: pendingLocalState.protocol.values,
1420
- }, // pending IQuorumSnapshot
1421
- );
1422
- }
1477
+ await this.initializeProtocolStateFromSnapshot(attributes, this.storageAdapter, snapshot);
1423
1478
 
1424
1479
  const codeDetails = this.getCodeDetailsFromQuorum();
1425
1480
  await this.instantiateContext(
@@ -1429,6 +1484,24 @@ export class Container
1429
1484
  pendingLocalState?.pendingRuntimeState,
1430
1485
  );
1431
1486
 
1487
+ // replay saved ops
1488
+ if (pendingLocalState) {
1489
+ for (const message of pendingLocalState.savedOps) {
1490
+ this.processRemoteMessage(message);
1491
+
1492
+ // allow runtime to apply stashed ops at this op's sequence number
1493
+ await this.context.notifyOpReplay(message);
1494
+ }
1495
+ pendingLocalState.savedOps = [];
1496
+
1497
+ // now set clientId to stashed clientId so live ops are correctly processed as local
1498
+ assert(
1499
+ this.clientId === undefined,
1500
+ 0x5d6 /* Unexpected clientId when setting stashed clientId */,
1501
+ );
1502
+ this._clientId = pendingLocalState?.clientId;
1503
+ }
1504
+
1432
1505
  // We might have hit some failure that did not manifest itself in exception in this flow,
1433
1506
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
1434
1507
  if (!this.closed) {
@@ -1452,6 +1525,11 @@ export class Container
1452
1525
 
1453
1526
  switch (loadMode.deltaConnection) {
1454
1527
  case undefined:
1528
+ if (pendingLocalState) {
1529
+ // connect to delta stream now since we did not before
1530
+ this.connectToDeltaStream(connectionArgs);
1531
+ }
1532
+ // intentional fallthrough
1455
1533
  case "delayed":
1456
1534
  assert(
1457
1535
  this.inboundQueuePausedFromInit,
@@ -1491,7 +1569,7 @@ export class Container
1491
1569
  private async createDetached(source: IFluidCodeDetails) {
1492
1570
  const attributes: IDocumentAttributes = {
1493
1571
  sequenceNumber: detachedContainerRefSeqNumber,
1494
- term: 1,
1572
+ term: OnlyValidTermValue,
1495
1573
  minimumSequenceNumber: 0,
1496
1574
  };
1497
1575
 
@@ -1565,7 +1643,7 @@ export class Container
1565
1643
  return {
1566
1644
  minimumSequenceNumber: 0,
1567
1645
  sequenceNumber: 0,
1568
- term: 1,
1646
+ term: OnlyValidTermValue,
1569
1647
  };
1570
1648
  }
1571
1649
 
@@ -1577,11 +1655,6 @@ export class Container
1577
1655
 
1578
1656
  const attributes = await readAndParse<IDocumentAttributes>(storage, attributesHash);
1579
1657
 
1580
- // Backward compatibility for older summaries with no term
1581
- if (attributes.term === undefined) {
1582
- attributes.term = 1;
1583
- }
1584
-
1585
1658
  return attributes;
1586
1659
  }
1587
1660
 
@@ -1746,6 +1819,7 @@ export class Container
1746
1819
  (props: IConnectionManagerFactoryArgs) =>
1747
1820
  new ConnectionManager(
1748
1821
  serviceProvider,
1822
+ () => this.isDirty,
1749
1823
  this.client,
1750
1824
  this._canReconnect,
1751
1825
  ChildLogger.create(this.subLogger, "ConnectionManager"),
@@ -1807,7 +1881,6 @@ export class Container
1807
1881
  return this._deltaManager.attachOpHandler(
1808
1882
  attributes.minimumSequenceNumber,
1809
1883
  attributes.sequenceNumber,
1810
- attributes.term ?? 1,
1811
1884
  {
1812
1885
  process: (message) => this.processRemoteMessage(message),
1813
1886
  processSignal: (message) => {
@@ -1897,10 +1970,7 @@ export class Container
1897
1970
 
1898
1971
  // Both protocol and context should not be undefined if we got so far.
1899
1972
 
1900
- this.setContextConnectedState(
1901
- state,
1902
- this._deltaManager.connectionManager.readOnlyInfo.readonly ?? false,
1903
- );
1973
+ this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
1904
1974
  this.protocolHandler.setConnectionState(state, this.clientId);
1905
1975
  raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
1906
1976
 
@@ -1999,6 +2069,9 @@ export class Container
1999
2069
  }
2000
2070
 
2001
2071
  private processRemoteMessage(message: ISequencedDocumentMessage) {
2072
+ if (this.offlineLoadEnabled) {
2073
+ this.savedOps.push(message);
2074
+ }
2002
2075
  const local = this.clientId === message.clientId;
2003
2076
 
2004
2077
  // Allow the protocol handler to process the message