@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.
- package/CHANGELOG.md +14 -0
- package/README.md +38 -0
- package/dist/connectionManager.d.ts +2 -1
- package/dist/connectionManager.d.ts.map +1 -1
- package/dist/connectionManager.js +16 -6
- package/dist/connectionManager.js.map +1 -1
- package/dist/connectionStateHandler.d.ts.map +1 -1
- package/dist/connectionStateHandler.js +7 -0
- package/dist/connectionStateHandler.js.map +1 -1
- package/dist/container.d.ts +35 -2
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js +88 -43
- package/dist/container.js.map +1 -1
- package/dist/containerContext.d.ts +3 -3
- package/dist/containerContext.d.ts.map +1 -1
- package/dist/containerContext.js +5 -4
- package/dist/containerContext.js.map +1 -1
- package/dist/containerStorageAdapter.d.ts +27 -2
- package/dist/containerStorageAdapter.d.ts.map +1 -1
- package/dist/containerStorageAdapter.js +64 -6
- package/dist/containerStorageAdapter.js.map +1 -1
- package/dist/deltaManager.d.ts +1 -3
- package/dist/deltaManager.d.ts.map +1 -1
- package/dist/deltaManager.js +3 -8
- package/dist/deltaManager.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +4 -1
- package/dist/loader.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/protocol.d.ts +1 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +4 -2
- package/dist/protocol.js.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/lib/connectionManager.d.ts +2 -1
- package/lib/connectionManager.d.ts.map +1 -1
- package/lib/connectionManager.js +16 -6
- package/lib/connectionManager.js.map +1 -1
- package/lib/connectionStateHandler.d.ts.map +1 -1
- package/lib/connectionStateHandler.js +7 -0
- package/lib/connectionStateHandler.js.map +1 -1
- package/lib/container.d.ts +35 -2
- package/lib/container.d.ts.map +1 -1
- package/lib/container.js +90 -45
- package/lib/container.js.map +1 -1
- package/lib/containerContext.d.ts +3 -3
- package/lib/containerContext.d.ts.map +1 -1
- package/lib/containerContext.js +5 -4
- package/lib/containerContext.js.map +1 -1
- package/lib/containerStorageAdapter.d.ts +27 -2
- package/lib/containerStorageAdapter.d.ts.map +1 -1
- package/lib/containerStorageAdapter.js +62 -6
- package/lib/containerStorageAdapter.js.map +1 -1
- package/lib/deltaManager.d.ts +1 -3
- package/lib/deltaManager.d.ts.map +1 -1
- package/lib/deltaManager.js +3 -8
- package/lib/deltaManager.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/loader.d.ts.map +1 -1
- package/lib/loader.js +4 -1
- package/lib/loader.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/protocol.d.ts +1 -0
- package/lib/protocol.d.ts.map +1 -1
- package/lib/protocol.js +3 -1
- package/lib/protocol.js.map +1 -1
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js.map +1 -1
- package/package.json +17 -13
- package/src/connectionManager.ts +17 -7
- package/src/connectionStateHandler.ts +12 -0
- package/src/container.ts +138 -65
- package/src/containerContext.ts +6 -6
- package/src/containerStorageAdapter.ts +88 -5
- package/src/deltaManager.ts +3 -10
- package/src/index.ts +3 -1
- package/src/loader.ts +4 -1
- package/src/packageVersion.ts +1 -1
- package/src/protocol.ts +4 -1
- package/src/utils.ts +3 -1
package/lib/protocol.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAIN,iBAAiB,GACjB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAMN,WAAW,GACX,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,mDAAmD;AACnD,MAAM,CAAN,IAAY,UAIX;AAJD,WAAY,UAAU;IACrB,iCAAmB,CAAA;IACnB,mCAAqB,CAAA;IACrB,6BAAe,CAAA;AAChB,CAAC,EAJW,UAAU,KAAV,UAAU,QAIrB;AAgBD,MAAM,OAAO,eAAgB,SAAQ,iBAAiB;IACrD,YACC,UAA+B,EAC/B,cAA+B,EAC/B,YAAiD,EACxC,QAAwB;QAEjC,KAAK,CACJ,UAAU,CAAC,qBAAqB,EAChC,UAAU,CAAC,cAAc,EACzB,
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAIN,iBAAiB,GACjB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAMN,WAAW,GACX,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,8FAA8F;AAC9F,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAU,CAAC;AAE7C,mDAAmD;AACnD,MAAM,CAAN,IAAY,UAIX;AAJD,WAAY,UAAU;IACrB,iCAAmB,CAAA;IACnB,mCAAqB,CAAA;IACrB,6BAAe,CAAA;AAChB,CAAC,EAJW,UAAU,KAAV,UAAU,QAIrB;AAgBD,MAAM,OAAO,eAAgB,SAAQ,iBAAiB;IACrD,YACC,UAA+B,EAC/B,cAA+B,EAC/B,YAAiD,EACxC,QAAwB;QAEjC,KAAK,CACJ,UAAU,CAAC,qBAAqB,EAChC,UAAU,CAAC,cAAc,EACzB,kBAAkB,EAClB,cAAc,CAAC,OAAO,EACtB,cAAc,CAAC,SAAS,EACxB,cAAc,CAAC,MAAM,EACrB,YAAY,CACZ,CAAC;QAVO,aAAQ,GAAR,QAAQ,CAAgB;QAYjC,oFAAoF;QACpF,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CACjD,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAC5C,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC9E,KAAK,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE;YAC3D,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;SAClD;IACF,CAAC;IAEM,cAAc,CACpB,OAAkC,EAClC,KAAc;QAEd,MAAM,MAAM,GAAsC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAE1F,gFAAgF;QAChF,qFAAqF;QACrF,IAAI,OAAO,CAAC,QAAQ,IAAI,IAAI,EAAE;YAC7B,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,UAAU,EAAE;gBACpE,2DAA2D;gBAC3D,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;aACxE;YAED,IAAI,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,cAAc,MAAK,IAAI,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC,EAAE;gBACzE,wDAAwD;gBACxD,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;aACtE;SACD;QAED,OAAO,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC7C,CAAC;IAEM,aAAa,CAAC,OAAuB;;QAC3C,MAAM,YAAY,GAAG,OAAO,CAAC,OAAyC,CAAC;QACvE,QAAQ,YAAY,CAAC,IAAI,EAAE;YAC1B,KAAK,UAAU,CAAC,KAAK,CAAC,CAAC;gBACtB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;gBAC3C,KAAK,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,OAAO,EAAE;oBACzC,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE;wBAC3B,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;qBACrC;iBACD;gBACD,MAAM;aACN;YACD,KAAK,UAAU,CAAC,UAAU,CAAC,CAAC;gBAC3B,MAAM,SAAS,GAAG,YAAY,CAAC,OAAwB,CAAC;gBACxD,2DAA2D;gBAC3D,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE;oBACrC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;iBAC9D;gBACD,MAAM;aACN;YACD,KAAK,UAAU,CAAC,WAAW,CAAC,CAAC;gBAC5B,MAAM,YAAY,GAAG,YAAY,CAAC,OAAiB,CAAC;gBACpD,2DAA2D;gBAC3D,IAAI,CAAA,MAAA,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,YAAY,CAAC,0CAAE,IAAI,MAAK,MAAM,EAAE;oBAC3D,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;iBACzC;gBACD,MAAM;aACN;YACD;gBACC,MAAM;SACP;IACF,CAAC;CACD","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"]}
|
package/lib/utils.d.ts.map
CHANGED
|
@@ -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,
|
|
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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
|
|
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.
|
|
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.
|
|
41
|
-
"@fluidframework/container-utils": ">=2.0.0-internal.4.
|
|
42
|
-
"@fluidframework/core-interfaces": ">=2.0.0-internal.4.
|
|
43
|
-
"@fluidframework/driver-definitions": ">=2.0.0-internal.4.
|
|
44
|
-
"@fluidframework/driver-utils": ">=2.0.0-internal.4.
|
|
45
|
-
"@fluidframework/protocol-base": "^0.
|
|
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.
|
|
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.
|
|
57
|
-
"@fluid-tools/build-cli": "^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.
|
|
60
|
-
"@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.0.0-internal.4.
|
|
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.
|
|
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",
|
package/src/connectionManager.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1050
|
-
|
|
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.
|
|
1112
|
-
|
|
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:
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
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
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
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
|
-
|
|
1461
|
+
dmAttributes,
|
|
1392
1462
|
loadMode.deltaConnection !== "none" ? "all" : "none",
|
|
1393
1463
|
);
|
|
1394
1464
|
break;
|
|
1395
1465
|
case "cached":
|
|
1396
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
1466
|
+
opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
|
|
1397
1467
|
break;
|
|
1398
1468
|
case "all":
|
|
1399
|
-
opsBeforeReturnP = this.attachDeltaManagerOpHandler(
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|