@fluidframework/container-runtime 2.40.0-336023 → 2.40.0

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/api-report/container-runtime.legacy.alpha.api.md +4 -0
  3. package/container-runtime.test-files.tar +0 -0
  4. package/dist/blobManager/blobManager.d.ts +31 -8
  5. package/dist/blobManager/blobManager.d.ts.map +1 -1
  6. package/dist/blobManager/blobManager.js +90 -17
  7. package/dist/blobManager/blobManager.js.map +1 -1
  8. package/dist/channelCollection.d.ts +8 -2
  9. package/dist/channelCollection.d.ts.map +1 -1
  10. package/dist/channelCollection.js +29 -6
  11. package/dist/channelCollection.js.map +1 -1
  12. package/dist/compatUtils.d.ts +19 -10
  13. package/dist/compatUtils.d.ts.map +1 -1
  14. package/dist/compatUtils.js +39 -32
  15. package/dist/compatUtils.js.map +1 -1
  16. package/dist/containerRuntime.d.ts +29 -13
  17. package/dist/containerRuntime.d.ts.map +1 -1
  18. package/dist/containerRuntime.js +139 -149
  19. package/dist/containerRuntime.js.map +1 -1
  20. package/dist/dataStoreContext.d.ts +12 -4
  21. package/dist/dataStoreContext.d.ts.map +1 -1
  22. package/dist/dataStoreContext.js +37 -18
  23. package/dist/dataStoreContext.js.map +1 -1
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/legacy.d.ts +1 -0
  28. package/dist/opLifecycle/index.d.ts +1 -1
  29. package/dist/opLifecycle/index.d.ts.map +1 -1
  30. package/dist/opLifecycle/index.js.map +1 -1
  31. package/dist/opLifecycle/outbox.d.ts +20 -7
  32. package/dist/opLifecycle/outbox.d.ts.map +1 -1
  33. package/dist/opLifecycle/outbox.js +16 -20
  34. package/dist/opLifecycle/outbox.js.map +1 -1
  35. package/dist/packageVersion.d.ts +1 -1
  36. package/dist/packageVersion.d.ts.map +1 -1
  37. package/dist/packageVersion.js +1 -1
  38. package/dist/packageVersion.js.map +1 -1
  39. package/dist/pendingStateManager.d.ts +22 -8
  40. package/dist/pendingStateManager.d.ts.map +1 -1
  41. package/dist/pendingStateManager.js +11 -16
  42. package/dist/pendingStateManager.js.map +1 -1
  43. package/lib/blobManager/blobManager.d.ts +31 -8
  44. package/lib/blobManager/blobManager.d.ts.map +1 -1
  45. package/lib/blobManager/blobManager.js +91 -18
  46. package/lib/blobManager/blobManager.js.map +1 -1
  47. package/lib/channelCollection.d.ts +8 -2
  48. package/lib/channelCollection.d.ts.map +1 -1
  49. package/lib/channelCollection.js +29 -6
  50. package/lib/channelCollection.js.map +1 -1
  51. package/lib/compatUtils.d.ts +19 -10
  52. package/lib/compatUtils.d.ts.map +1 -1
  53. package/lib/compatUtils.js +36 -29
  54. package/lib/compatUtils.js.map +1 -1
  55. package/lib/containerRuntime.d.ts +29 -13
  56. package/lib/containerRuntime.d.ts.map +1 -1
  57. package/lib/containerRuntime.js +60 -70
  58. package/lib/containerRuntime.js.map +1 -1
  59. package/lib/dataStoreContext.d.ts +12 -4
  60. package/lib/dataStoreContext.d.ts.map +1 -1
  61. package/lib/dataStoreContext.js +38 -19
  62. package/lib/dataStoreContext.js.map +1 -1
  63. package/lib/index.d.ts +1 -0
  64. package/lib/index.d.ts.map +1 -1
  65. package/lib/index.js.map +1 -1
  66. package/lib/legacy.d.ts +1 -0
  67. package/lib/opLifecycle/index.d.ts +1 -1
  68. package/lib/opLifecycle/index.d.ts.map +1 -1
  69. package/lib/opLifecycle/index.js.map +1 -1
  70. package/lib/opLifecycle/outbox.d.ts +20 -7
  71. package/lib/opLifecycle/outbox.d.ts.map +1 -1
  72. package/lib/opLifecycle/outbox.js +16 -20
  73. package/lib/opLifecycle/outbox.js.map +1 -1
  74. package/lib/packageVersion.d.ts +1 -1
  75. package/lib/packageVersion.d.ts.map +1 -1
  76. package/lib/packageVersion.js +1 -1
  77. package/lib/packageVersion.js.map +1 -1
  78. package/lib/pendingStateManager.d.ts +22 -8
  79. package/lib/pendingStateManager.d.ts.map +1 -1
  80. package/lib/pendingStateManager.js +11 -16
  81. package/lib/pendingStateManager.js.map +1 -1
  82. package/package.json +18 -18
  83. package/src/blobManager/blobManager.ts +141 -33
  84. package/src/channelCollection.ts +42 -6
  85. package/src/compatUtils.ts +53 -30
  86. package/src/containerRuntime.ts +102 -81
  87. package/src/dataStoreContext.ts +44 -25
  88. package/src/index.ts +1 -0
  89. package/src/opLifecycle/index.ts +1 -0
  90. package/src/opLifecycle/outbox.ts +42 -33
  91. package/src/packageVersion.ts +1 -1
  92. package/src/pendingStateManager.ts +37 -20
@@ -1 +1 @@
1
- {"version":3,"file":"pendingStateManager.js","sourceRoot":"","sources":["../src/pendingStateManager.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;AAGH,kEAAmE;AACnE,uEAMkD;AAClD,4EAAuC;AACvC,+BAAkC;AAOlC,+CAA6E;AAC7E,qDAQgC;AA8FhC,SAAS,0BAA0B,CAAC,OAAiC;IACpE,mEAAmE;IACnE,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,sEAAsE;IACtE,OAAO,OAAO,CAAC,IAAI,KAAK,cAAc,IAAI,OAAO,CAAC,QAAQ,EAAE,MAAM,KAAK,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,0BAA0B,CAAC,OAAgD;IACnF,kFAAkF;IAClF,+BAA+B;IAC/B,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAmC,OAAO,CAAC;IACnE,sEAAsE;IACtE,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,WAAW,CAAmB,GAAM;IAC5C,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC3C,GAAG,CAAC,GAAG,CAAC,GAAG,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3B,OAAO,GAAG,CAAC;IACZ,CAAC,EAAE,EAAE,CAA4B,CAAC;AACnC,CAAC;AAED,SAAS,iBAAiB,CACzB,OAAsE;IAEtE,2DAA2D;IAC3D,MAAM,QAAQ,GAA4B,WAAW,CAAC,OAAO,CAAC,CAAC;IAE/D,oFAAoF;IACpF,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACtE,QAAQ,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAE7B,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;AACjC,CAAC;AAED;;GAEG;AACH,SAAS,+BAA+B,CACvC,CAAS,EACT,CAAS;IAET,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACnB,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACxB,CAAC;IACF,CAAC;IAED,wFAAwF;IACxF,uEAAuE;IACvE,+GAA+G;IAC/G,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAC3B,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC;QAC5B,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED;;;GAGG;AACH,SAAgB,4BAA4B,CAC3C,CAAS,EACT,CAAS;IAET,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,GAAG,+BAA+B,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAE1E,MAAM,KAAK,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC;IACjF,MAAM,KAAK,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC;IAEjF,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAC9B,CAAC;AAVD,oEAUC;AAED;;;GAGG;AACH,SAAS,kBAAkB,CAC1B,OAAwB;IAExB,OAAO;QACN,GAAG,OAAO;QACV,eAAe,EAAE,SAAS;QAC1B,SAAS,EAAE,SAAS;KACpB,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAa,mBAAmB;IA0B/B;;;OAGG;IACH,IAAW,oBAAoB;QAC9B,OAAO,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;IAClE,CAAC;IAED;;;;OAIG;IACH,IAAW,mCAAmC;QAC7C,OAAO,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,EAAE,uBAAuB,CAAC;IAClE,CAAC;IAED;;;OAGG;IACI,kBAAkB;QACxB,OAAO,IAAI,CAAC,oBAAoB,KAAK,CAAC,CAAC;IACxC,CAAC;IAEM,aAAa,CAAC,sBAA+B;QACnD,IAAA,iBAAM,EACL,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAC9B,KAAK,CAAC,+DAA+D,CACrE,CAAC;QACF,+EAA+E;QAC/E,sFAAsF;QACtF,sFAAsF;QACtF,MAAM,WAAW,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE;YACzD,IAAA,iBAAM,EACL,OAAO,CAAC,cAAc,KAAK,SAAS,EACpC,KAAK,CAAC,oDAAoD,CAC1D,CAAC;YACF,OAAO,OAAO,CAAC,cAAc,GAAG,CAAC,sBAAsB,IAAI,CAAC,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QACH,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAAE,CAAC;YACtD,IACC,sBAAsB,KAAK,SAAS;gBACpC,OAAO,CAAC,uBAAuB,GAAG,sBAAsB,EACvD,CAAC;gBACF,MAAM,IAAI,uBAAY,CAAC,oDAAoD,CAAC,CAAC;YAC9E,CAAC;QACF,CAAC;QACD,OAAO;YACN,aAAa,EAAE;gBACd,GAAG,WAAW;gBACd,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;aAC/E;SACD,CAAC;IACH,CAAC;IAID,YACkB,YAAkC,EACnD,iBAAiD,EACjD,MAA4B;QAFX,iBAAY,GAAZ,YAAY,CAAsB;QApFpD;;WAEG;QACc,oBAAe,GAAG,IAAI,4BAAK,EAAmB,CAAC;QAChE;;WAEG;QACc,oBAAe,GAAG,IAAI,4BAAK,EAA4B,CAAC;QAEzE;;WAEG;QACK,aAAQ,GAAsB,EAAE,CAAC;QAEzC,4HAA4H;QAC3G,gBAAW,GAAG,IAAI,eAAI,CAAO,GAAG,EAAE;YAClD,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;YAC7B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC9B,CAAC,CAAC,CAAC;QA+Ea,YAAO,GAAG,GAAS,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;QAT5D,IAAI,CAAC,MAAM,GAAG,IAAA,4BAAiB,EAAC,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5C,IAAI,iBAAiB,EAAE,aAAa,EAAE,CAAC;YACtC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;QAC/D,CAAC;IACF,CAAC;IAED,IAAW,QAAQ;QAClB,OAAO,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC;IACnC,CAAC;IAGD;;;OAGG;IACI,iBAAiB,CACvB,WAAuC,EACvC,oBAAwC,EACxC,MAAe;QAEf,iHAAiH;QACjH,IAAI,CAAC,YAAY,CAChB,CAAC,WAA+E,CAAC,EACjF,oBAAoB,EACpB,MAAM,CACN,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACI,YAAY,CAClB,KAA0B,EAC1B,oBAAwC,EACxC,MAAe,EACf,aAAuB;QAEvB,kGAAkG;QAClG,iGAAiG;QACjG,oGAAoG;QACpG,mIAAmI;QACnI,wJAAwJ;QACxJ,MAAM,YAAY,GAAG,oBAAoB,KAAK,SAAS,CAAC;QACxD,IAAI,YAAY,EAAE,CAAC;YAClB,IAAA,iBAAM,EAAC,CAAC,MAAM,EAAE,KAAK,CAAC,mDAAmD,CAAC,CAAC;QAC5E,CAAC;QACD,MAAM,CAAC,QAAQ,EAAE,aAAa,CAAC,GAAG,YAAY;YAC7C,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,EAAE,oBAAoB,CAAC;YACtD,CAAC,CAAC,CAAC,IAAA,SAAI,GAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,gDAAgD;QACjE,IAAA,iBAAM,EACL,QAAQ,KAAK,SAAS,EACtB,KAAK,CAAC,qIAAqI,CAC3I,CAAC;QAEF,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;YAC7B,MAAM,EACL,SAAS,EACT,uBAAuB,EACvB,eAAe,EACf,QAAQ,EAAE,UAAU,GACpB,GAAG,OAAO,CAAC;YACZ,MAAM,cAAc,GAAoB;gBACvC,IAAI,EAAE,SAAS;gBACf,uBAAuB;gBACvB,OAAO,EAAE,IAAA,sBAAW,EAAC,SAAS,CAAC;gBAC/B,SAAS;gBACT,eAAe;gBACf,UAAU;gBACV,uFAAuF;gBACvF,SAAS,EAAE,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE;aACnF,CAAC;YACF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC3C,CAAC;IACF,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,iBAAiB,CAAC,MAAe;QAC7C,uCAAuC;QACvC,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAAE,CAAC;YACxC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC1B,oEAAoE;gBACpE,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,SAAS,EAAG,CAAC;gBACtD,IAAI,WAAW,CAAC,uBAAuB,GAAG,MAAM,EAAE,CAAC;oBAClD,MAAM,CAAC,6CAA6C;gBACrD,CAAC;gBACD,IAAI,WAAW,CAAC,uBAAuB,GAAG,MAAM,EAAE,CAAC;oBAClD,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;gBACzE,CAAC;YACF,CAAC;YACD,oEAAoE;YACpE,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,EAAG,CAAC;YAClD,qDAAqD;YACrD,8CAA8C;YAC9C,IAAI,CAAC;gBACJ,IAAI,0BAA0B,CAAC,WAAW,CAAC,EAAE,CAAC;oBAC7C,WAAW,CAAC,eAAe,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,+CAA+C;oBACnG,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,cAAc;oBAC3C,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;oBACvC,SAAS;gBACV,CAAC;gBACD,gGAAgG;gBAChG,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;gBACpF,IAAI,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,EAAE,CAAC;oBACpC,WAAW,CAAC,eAAe,GAAG,eAAe,CAAC;oBAC9C,yFAAyF;oBACzF,yDAAyD;oBACzD,WAAW,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CACjC,WAAW,CAAC,OAAO,CACa,CAAC;oBAClC,qGAAqG;oBACrG,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,cAAc;oBAC3C,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;gBACxC,CAAC;qBAAM,CAAC;oBACP,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;wBACnC,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;oBAC1E,CAAC;gBACF,CAAC;YACF,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,MAAM,8BAAmB,CAAC,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,EAAE,WAAW,CAAC,CAAC;YACpF,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACK,8BAA8B,CAAC,gBAAgC;QACtE,8FAA8F;QAC9F,2GAA2G;QAC3G,MAAM,sBAAsB,GAAG,KAAK,CAAC,IAAI,CAAC;YACzC,MAAM,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM;SACnC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,aAAa,KAAK,IAAI,CAAC,CAAC;QACtF,MAAM,0BAA0B,GAC/B,sBAAsB,KAAK,CAAC,CAAC;YAC5B,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;QAErD,IAAI,0BAA0B,KAAK,SAAS,EAAE,CAAC;YAC9C,OAAO,KAAK,CAAC;QACd,CAAC;QAED,kEAAkE;QAClE,0FAA0F;QAC1F,MAAM,cAAc,GAAG,IAAA,8BAAmB,EAAC,0BAA0B,CAAC,CAAC;QACvE,MAAM,cAAc,GAAG,IAAA,8BAAmB,EAAC,gBAAgB,CAAC,CAAC;QAE7D,OAAO,cAAc,KAAK,cAAc,CAAC;IAC1C,CAAC;IAED;;;;;;;;;;OAUG;IACI,sBAAsB,CAC5B,OAA6B,EAC7B,KAAc;QAKd,IAAI,KAAK,EAAE,CAAC;YACX,OAAO,IAAI,CAAC,2BAA2B,CAAC,OAAO,CAAC,CAAC;QAClD,CAAC;QAED,iFAAiF;QACjF,iGAAiG;QACjG,IAAI,YAAY,IAAI,OAAO,IAAI,IAAI,CAAC,8BAA8B,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YACxF,MAAM,8BAAmB,CAAC,MAAM,CAC/B,mEAAmE,EACnE,4CAA4C,EAC5C,OAAO,CAAC,UAAU,CAAC,UAAU,CAC7B,CAAC;QACH,CAAC;QAED,yCAAyC;QACzC,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACzF,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IACjD,CAAC;IAED;;;;;;OAMG;IACK,2BAA2B,CAAC,OAA6B;QAIhE,IAAI,YAAY,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAC5D,CAAC;QAED,cAAc;QACd,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,eAAe,GAAG,IAAI,CAAC,yBAAyB,CACrD,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,cAAc,CAC5C,CAAC;YACF,IAAA,iBAAM,EACL,IAAA,yCAA2B,EAAC,eAAe,CAAC,EAAE,UAAU,KAAK,IAAI,EACjE,KAAK,CAAC,iCAAiC,CACvC,CAAC;YACF,OAAO,EAAE,CAAC;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAEzF,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACjC,OAAO;YACP,eAAe,EAAE,IAAI,CAAC,yBAAyB,CAAC,OAAO,CAAC,cAAc,EAAE,OAAO,CAAC;SAChF,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;OAMG;IACK,yBAAyB,CAChC,cAAsB,EACtB,OAAiD;QAEjD,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,CAAC;QACxD,IAAA,iBAAM,EACL,cAAc,KAAK,SAAS,EAC5B,KAAK,CAAC,wDAAwD,CAC9D,CAAC;QAEF,cAAc,CAAC,cAAc,GAAG,cAAc,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAC,CAAC;QAEvD,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAE7B,gDAAgD;QAChD,wGAAwG;QACxG,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,cAAc,GAAG,0BAA0B,CAAC,OAAO,CAAC,CAAC;YAE3D,mCAAmC;YACnC,iGAAiG;YACjG,IAAI,cAAc,CAAC,OAAO,KAAK,cAAc,EAAE,CAAC;gBAC/C,MAAM,CAAC,aAAa,EAAE,cAAc,CAAC,GAAG;oBACvC,cAAc,CAAC,OAAO,CAAC,MAAM;oBAC7B,cAAc,CAAC,MAAM;iBACrB,CAAC;gBACF,MAAM,CAAC,kBAAkB,EAAE,WAAW,EAAE,YAAY,CAAC,GAAG,4BAA4B,CACnF,cAAc,CAAC,OAAO,EACtB,cAAc,CACd,CAAC;gBAEF,MAAM,iBAAiB,GAAG,IAAI,CAAC,KAAK,CACnC,cAAc,CAAC,OAAO,CACU,CAAC;gBAClC,MAAM,kBAAkB,GAAG,IAAI,CAAC,KAAK,CACpC,cAAc,CACoB,CAAC;gBAEpC,8EAA8E;gBAC9E,MAAM,aAAa,GAClB,iBAAiB,CAAC,QAAQ,KAAK,kBAAkB,CAAC,QAAQ;oBAC1D,CAAC,iBAAiB,CAAC,QAAQ,KAAK,SAAS;wBACxC,kBAAkB,CAAC,QAAQ,KAAK,SAAS;wBACzC,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAC,QAAQ,CAAC;4BACzC,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAEhD,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC;oBAC1B,SAAS,EAAE,uBAAuB;oBAClC,OAAO,EAAE;wBACR,sBAAsB,EAAE,iBAAiB,CAAC,iBAAiB,CAAC;wBAC5D,uBAAuB,EAAE,iBAAiB,CAAC,kBAAkB,CAAC;wBAC9D,aAAa;wBACb,aAAa;wBACb,cAAc;wBACd,kBAAkB;wBAClB,WAAW;wBACX,YAAY;qBACZ;iBACD,CAAC,CAAC;gBAEH,MAAM,8BAAmB,CAAC,MAAM,CAC/B,wCAAwC,EACxC,uBAAuB,EACvB,OAAO,CACP,CAAC;YACH,CAAC;QACF,CAAC;QAED,OAAO,cAAc,CAAC,eAAe,CAAC;IACvC,CAAC;IAED;;OAEG;IACK,iBAAiB,CAAC,UAA0B,EAAE,WAAoB;QACzE,wEAAwE;QACxE,MAAM,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,CAAC;QACxD,IAAA,iBAAM,EACL,cAAc,KAAK,SAAS,EAC5B,KAAK,CAAC,uEAAuE,CAC7E,CAAC;QACF,IAAA,iBAAM,EAAC,CAAC,cAAc,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAE3F,sGAAsG;QACtG,gEAAgE;QAChE,kFAAkF;QAClF,2FAA2F;QAC3F,MAAM,YAAY,GAAG,UAAU,CAAC,UAAU,CAAC;QAC3C,iFAAiF;QACjF,MAAM,eAAe,GACpB,cAAc,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,IAAI,WAAW,KAAK,SAAS,CAAC;QACrE,MAAM,0BAA0B,GAC/B,WAAW,KAAK,CAAC;YAChB,CAAC,CAAC,CAAC,CAAC,2EAA2E;YAC/E,CAAC,CAAC,WAAW,CAAC,CAAC,+CAA+C;QAEhE,+FAA+F;QAC/F,6EAA6E;QAC7E,+GAA+G;QAC/G,MAAM,cAAc,GAAG,IAAA,6BAAe,EAAC,cAAc,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;QAC3E,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,CAAC;QAE1C,oGAAoG;QACpG,6GAA6G;QAC7G,6GAA6G;QAC7G,8GAA8G;QAC9G,IACC,cAAc,CAAC,SAAS,CAAC,aAAa,KAAK,UAAU,CAAC,aAAa;YACnE,CAAC,CAAC,eAAe,IAAI,cAAc,CAAC,SAAS,CAAC,MAAM,KAAK,0BAA0B,CAAC;YACpF,cAAc,KAAK,cAAc,EAChC,CAAC;YACF,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC;gBAC3B,SAAS,EAAE,mBAAmB;gBAC9B,OAAO,EAAE;oBACR,eAAe,EAAE,cAAc,CAAC,SAAS,CAAC,aAAa;oBACvD,aAAa,EAAE,UAAU,CAAC,aAAa;oBACvC,kBAAkB,EAAE,cAAc,CAAC,SAAS,CAAC,MAAM;oBACnD,0BAA0B;oBAC1B,WAAW;oBACX,cAAc;oBACd,cAAc;oBACd,2BAA2B,EAAE,IAAA,6BAAe,EAAC,cAAc,CAAC,UAAU,CAAC,EAAE,KAAK;oBAC9E,oBAAoB,EAAE,IAAA,6BAAe,EAAC,YAAY,EAAE,QAAQ,CAAC,EAAE,KAAK;iBACpE;gBACD,cAAc,EAAE,YAAY,IAAI,IAAA,2CAAgC,EAAC,YAAY,CAAC;aAC9E,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED;;;;;OAKG;IACI,mBAAmB,CAAC,iBAA2B;QACrD,IAAA,iBAAM,EACL,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,EAC7B,KAAK,CAAC,+DAA+D,CACrE,CAAC;QAEF,oEAAoE;QACpE,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACxB,4FAA4F;YAC5F,IAAA,iBAAM,EACL,IAAI,CAAC,sBAAsB,KAAK,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,EAC5D,KAAK,CAAC,2DAA2D,CACjE,CAAC;QACH,CAAC;QACD,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;QAE3D,IAAA,iBAAM,EACL,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAC9B,KAAK,CAAC,+DAA+D,CACrE,CAAC;QAEF,MAAM,2BAA2B,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;QAChE,IAAI,6BAA6B,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;QAEhE,IAAI,eAAe,GAAG,KAAK,CAAC;QAE5B,8GAA8G;QAC9G,0GAA0G;QAC1G,8BAA8B;QAC9B,OAAO,6BAA6B,GAAG,CAAC,EAAE,CAAC;YAC1C,oEAAoE;YACpE,IAAI,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,EAAG,CAAC;YACnD,6BAA6B,EAAE,CAAC;YAEhC,yEAAyE;YACzE,IAAI,iBAAiB,EAAE,CAAC;gBACvB,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;oBACtC,IAAA,iBAAM,EAAC,CAAC,eAAe,EAAE,KAAK,CAAC,mDAAmD,CAAC,CAAC;oBACpF,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;oBAC1C,SAAS;gBACV,CAAC;gBAED,eAAe,GAAG,IAAI,CAAC;gBACvB,cAAc,CAAC,SAAS,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,qCAAqC;YAC/E,CAAC;YAED,MAAM,iBAAiB,GAAG,IAAA,6BAAe,EAAC,cAAc,CAAC,UAAU,CAAC,EAAE,KAAK,CAAC;YAC5E,IAAA,iBAAM,EAAC,iBAAiB,KAAK,KAAK,EAAE,KAAK,CAAC,yCAAyC,CAAC,CAAC;YAErF,yFAAyF;YACzF,MAAM,OAAO,GAAG,IAAA,8BAAmB,EAAC,cAAc,CAAC,CAAC;YAEpD,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,CAAC,MAAM,CAAC;YAE/C,IAAI,IAAA,yCAA2B,EAAC,cAAc,CAAC,eAAe,CAAC,EAAE,UAAU,KAAK,IAAI,EAAE,CAAC;gBACtF,qFAAqF;gBACrF,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;gBACrD,SAAS;YACV,CAAC;YAED,IAAA,iBAAM,EACL,cAAc,CAAC,SAAS,KAAK,SAAS,EACtC,KAAK,CAAC,kDAAkD,CACxD,CAAC;YAEF;;;;eAIG;YACH,IAAI,iBAAiB,KAAK,SAAS,EAAE,CAAC;gBACrC,uBAAuB;gBAEvB,IAAI,CAAC,YAAY,CAAC,aAAa,CAC9B;oBACC;wBACC,SAAS,EAAE,cAAc,CAAC,SAAS;wBACnC,eAAe,EAAE,cAAc,CAAC,eAAe;wBAC/C,UAAU,EAAE,cAAc,CAAC,UAAU;qBACrC;iBACD,EACD,OAAO,EACP,MAAM,CACN,CAAC;gBACF,SAAS;YACV,CAAC;YACD,yEAAyE;YAEzE,IAAA,iBAAM,EACL,6BAA6B,GAAG,CAAC,EACjC,KAAK,CAAC,kDAAkD,CACxD,CAAC;YAEF,MAAM,KAAK,GAAiC,EAAE,CAAC;YAE/C,4DAA4D;YAC5D,OAAO,6BAA6B,IAAI,CAAC,EAAE,CAAC;gBAC3C,IAAA,iBAAM,EACL,cAAc,CAAC,SAAS,KAAK,SAAS,EACtC,KAAK,CAAC,kDAAkD,CACxD,CAAC;gBACF,KAAK,CAAC,IAAI,CAAC;oBACV,SAAS,EAAE,cAAc,CAAC,SAAS;oBACnC,eAAe,EAAE,cAAc,CAAC,eAAe;oBAC/C,UAAU,EAAE,cAAc,CAAC,UAAU;iBACrC,CAAC,CAAC;gBAEH,mBAAmB;gBACnB,IAAI,cAAc,CAAC,UAAU,EAAE,KAAK,KAAK,KAAK,EAAE,CAAC;oBAChD,MAAM;gBACP,CAAC;gBACD,IAAA,iBAAM,EAAC,6BAA6B,GAAG,CAAC,EAAE,KAAK,CAAC,wBAAwB,CAAC,CAAC;gBAE1E,oEAAoE;gBACpE,cAAc,GAAG,IAAI,CAAC,eAAe,CAAC,KAAK,EAAG,CAAC;gBAC/C,6BAA6B,EAAE,CAAC;gBAChC,IAAA,iBAAM,EACL,cAAc,CAAC,UAAU,EAAE,KAAK,KAAK,IAAI,EACzC,KAAK,CAAC,iDAAiD,CACvD,CAAC;YACH,CAAC;YAED,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACzD,CAAC;QAED,qFAAqF;QACrF,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;QAEnB,+GAA+G;QAC/G,6GAA6G;QAC7G,mFAAmF;QACnF,IAAI,IAAI,CAAC,YAAY,CAAC,kBAAkB,EAAE,EAAE,CAAC;YAC5C,IAAI,CAAC,MAAM,EAAE,kBAAkB,CAAC;gBAC/B,SAAS,EAAE,uBAAuB;gBAClC,KAAK,EAAE,2BAA2B;gBAClC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,QAAQ,EAAE;aACtC,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED;;OAEG;IACI,iBAAiB;QACvB,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAAE,CAAC;YACtD,IAAI,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;gBAC9B,OAAO,CAAC,SAAS,CAAC,MAAM,GAAG,KAAK,CAAC;YAClC,CAAC;QACF,CAAC;IACF,CAAC;IAED;;OAEG;IACI,gBAAgB,CAAC,QAAkD;QACzE,OAAO,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,EAAE,CAAC;YACxC,MAAM,aAAa,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,CAAC;YACtD,IAAI,aAAa,EAAE,SAAS,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;gBAC9C,QAAQ,CAAC,aAAa,CAAC,CAAC;gBACxB,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,CAAC;YAC5B,CAAC;iBAAM,CAAC;gBACP,MAAM,CAAC,0BAA0B;YAClC,CAAC;QACF,CAAC;QACD,IAAA,iBAAM,EACL,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,KAAK,IAAI,CAAC,EACxE,KAAK,CAAC,2CAA2C,CACjD,CAAC;IACH,CAAC;CACD;AA1nBD,kDA0nBC;AAED;;GAEG;AACH,SAAS,cAAc,CACtB,OAAiC;IAEjC,MAAM,SAAS,GAA0C,OAAO,CAAC,SAAS,CAAC;IAC3E,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC7B,gEAAgE;QAChE,OAAO,CAAC,SAAS,GAAG,EAAE,QAAQ,EAAE,IAAA,SAAI,GAAE,EAAE,aAAa,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACxF,CAAC;AACF,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IDisposable, type ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { assert, Lazy } from \"@fluidframework/core-utils/internal\";\nimport {\n\tITelemetryLoggerExt,\n\tDataProcessingError,\n\tLoggingError,\n\textractSafePropertiesFromMessage,\n\tcreateChildLogger,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport Deque from \"double-ended-queue\";\nimport { v4 as uuid } from \"uuid\";\n\nimport {\n\ttype InboundContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessage,\n\ttype LocalContainerRuntimeMessage,\n} from \"./messageTypes.js\";\nimport { asBatchMetadata, asEmptyBatchLocalOpMetadata } from \"./metadata.js\";\nimport {\n\tBatchId,\n\tLocalBatchMessage,\n\tgetEffectiveBatchId,\n\tBatchStartInfo,\n\tInboundMessageResult,\n\tserializeOp,\n\ttype LocalEmptyBatchPlaceholder,\n} from \"./opLifecycle/index.js\";\n\n/**\n * This represents a message that has been submitted and is added to the pending queue when `submit` is called on the\n * ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.\n *\n * @remarks This is the current serialization format for pending local state when a Container is serialized.\n */\nexport interface IPendingMessage {\n\ttype: \"message\";\n\treferenceSequenceNumber: number;\n\t/**\n\t * Serialized copy of runtimeOp\n\t */\n\tcontent: string;\n\t/**\n\t * The original runtime op that was submitted to the ContainerRuntime\n\t * Unless this pending message came from stashed content, in which case this was roundtripped through string\n\t */\n\truntimeOp?: LocalContainerRuntimeMessage | undefined; // Undefined for empty batches and initial messages before parsing\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n\tsequenceNumber?: number;\n\t/**\n\t * Info about the batch this pending message belongs to, for validation and for computing the batchId on reconnect\n\t * We don't include batchId itself to avoid redundancy, because that's stamped on opMetadata above\n\t */\n\tbatchInfo: {\n\t\t/**\n\t\t * The Batch's original clientId, from when it was first flushed to be submitted.\n\t\t * Or, a random uuid if it was never submitted (and batchStartCsn will be -1)\n\t\t */\n\t\tclientId: string;\n\t\t/**\n\t\t * The Batch's original clientSequenceNumber, from when it was first flushed to be submitted\n\t\t * Or, -1 if it was never submitted (and clientId will be a random uuid)\n\t\t */\n\t\tbatchStartCsn: number;\n\t\t/**\n\t\t * length of the batch (how many runtime messages here)\n\t\t */\n\t\tlength: number;\n\t\t/**\n\t\t * If true, don't compare batchID of incoming batches to this. e.g. ID Allocation Batch IDs should be ignored\n\t\t */\n\t\tignoreBatchId?: boolean;\n\t\t/**\n\t\t * If true, this batch is staged and should not actually be submitted on replayPendingStates.\n\t\t */\n\t\tstaged: boolean;\n\t};\n}\n\ntype Patch<T, U> = U & Omit<T, keyof U>;\n\n/**\n * First version of the type (pre-dates batchInfo)\n */\ntype IPendingMessageV0 = Patch<IPendingMessage, { batchInfo?: undefined }>;\n\n/**\n * Union of all supported schemas for when applying stashed ops\n *\n * @remarks When the format changes, this type should update to reflect all possible schemas.\n */\ntype IPendingMessageFromStash = IPendingMessageV0 | IPendingMessage;\n\nexport interface IPendingLocalState {\n\t/**\n\t * list of pending states, including ops and batch information\n\t */\n\tpendingStates: IPendingMessage[];\n}\n\n/**\n * Info needed to replay/resubmit a pending message\n */\nexport type PendingMessageResubmitData = Pick<\n\tIPendingMessage,\n\t\"runtimeOp\" | \"localOpMetadata\" | \"opMetadata\"\n> & {\n\t// Required (it's only missing on IPendingMessage for empty batch, which will be resubmitted as an empty array)\n\truntimeOp: LocalContainerRuntimeMessage;\n};\n\nexport interface IRuntimeStateHandler {\n\tconnected(): boolean;\n\tclientId(): string | undefined;\n\tapplyStashedOp(serializedOp: string): Promise<unknown>;\n\treSubmitBatch(batch: PendingMessageResubmitData[], batchId: BatchId, staged: boolean): void;\n\tisActiveConnection: () => boolean;\n\tisAttached: () => boolean;\n}\n\nfunction isEmptyBatchPendingMessage(message: IPendingMessageFromStash): boolean {\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\tconst content = JSON.parse(message.content);\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n\treturn content.type === \"groupedBatch\" && content.contents?.length === 0;\n}\n\nfunction buildPendingMessageContent(message: InboundSequencedContainerRuntimeMessage): string {\n\t// IMPORTANT: Order matters here, this must match the order of the properties used\n\t// when submitting the message.\n\tconst { type, contents }: InboundContainerRuntimeMessage = message;\n\t// Any properties that are not defined, won't be emitted by stringify.\n\treturn JSON.stringify({ type, contents });\n}\n\nfunction typesOfKeys<T extends object>(obj: T): Record<keyof T, string> {\n\treturn Object.keys(obj).reduce((acc, key) => {\n\t\tacc[key] = typeof obj[key];\n\t\treturn acc;\n\t}, {}) as Record<keyof T, string>;\n}\n\nfunction scrubAndStringify(\n\tmessage: InboundContainerRuntimeMessage | LocalContainerRuntimeMessage,\n): string {\n\t// Scrub the whole object in case there are unexpected keys\n\tconst scrubbed: Record<string, unknown> = typesOfKeys(message);\n\n\t// For these known/expected keys, we can either drill into the object (for contents)\n\t// or just use the value as-is (since it's not personal info)\n\tscrubbed.contents = message.contents && typesOfKeys(message.contents);\n\tscrubbed.type = message.type;\n\n\treturn JSON.stringify(scrubbed);\n}\n\n/**\n * Finds and returns the index where the strings diverge, and the character at that index in each string (or undefined if not applicable)\n */\nfunction findFirstRawCharacterMismatched(\n\ta: string,\n\tb: string,\n): [index: number, charA?: string, charB?: string] {\n\tconst minLength = Math.min(a.length, b.length);\n\tfor (let i = 0; i < minLength; i++) {\n\t\tif (a[i] !== b[i]) {\n\t\t\treturn [i, a[i], b[i]];\n\t\t}\n\t}\n\n\t// Since we didn't return in the loop, the shorter string must be a prefix of the other.\n\t// If they're the same length, return -1 to indicate they're identical.\n\t// Otherwise, the next character of the longer one is where they differ. No need to return that next character.\n\treturn a.length === b.length\n\t\t? [-1, undefined, undefined]\n\t\t: [minLength, a[minLength], b[minLength]];\n}\n\n/**\n * Finds and returns the index where the strings diverge, and the character at that index in each string (or undefined if not applicable)\n * It scrubs non-ASCII characters since they convey more meaning (privacy consideration)\n */\nexport function findFirstCharacterMismatched(\n\ta: string,\n\tb: string,\n): [index: number, charA?: string, charB?: string] {\n\tconst [index, rawCharA, rawCharB] = findFirstRawCharacterMismatched(a, b);\n\n\tconst charA = (rawCharA?.codePointAt(0) ?? 0) <= 0x7f ? rawCharA : \"[non-ASCII]\";\n\tconst charB = (rawCharB?.codePointAt(0) ?? 0) <= 0x7f ? rawCharB : \"[non-ASCII]\";\n\n\treturn [index, charA, charB];\n}\n\n/**\n * Returns a shallow copy of the given message with the non-serializable properties removed.\n * Note that the runtimeOp's data has already been serialized in the content property.\n */\nfunction toSerializableForm(\n\tmessage: IPendingMessage,\n): IPendingMessage & { runtimeOp: undefined; localOpMetadata: undefined } {\n\treturn {\n\t\t...message,\n\t\tlocalOpMetadata: undefined,\n\t\truntimeOp: undefined,\n\t};\n}\n\n/**\n * PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been\n * acknowledged by the server. It also maintains the batch information for both automatically and manually flushed\n * batches along with the messages.\n * When the Container reconnects, it replays the pending states, which includes manual flushing\n * of messages and triggering resubmission of unacked ops.\n *\n * It verifies that all the ops are acked, are received in the right order and batch information is correct.\n */\nexport class PendingStateManager implements IDisposable {\n\t/**\n\t * Messages that will need to be resubmitted if not ack'd before the next reconnection\n\t */\n\tprivate readonly pendingMessages = new Deque<IPendingMessage>();\n\t/**\n\t * Messages stashed from a previous container, now being rehydrated. Need to be resubmitted.\n\t */\n\tprivate readonly initialMessages = new Deque<IPendingMessageFromStash>();\n\n\t/**\n\t * Sequenced local ops that are saved when stashing since pending ops may depend on them\n\t */\n\tprivate savedOps: IPendingMessage[] = [];\n\n\t// eslint-disable-next-line unicorn/consistent-function-scoping -- Property is defined once; no need to extract inner lambda\n\tprivate readonly disposeOnce = new Lazy<void>(() => {\n\t\tthis.initialMessages.clear();\n\t\tthis.pendingMessages.clear();\n\t});\n\n\t/**\n\t * Used to ensure we don't replay ops on the same connection twice\n\t */\n\tprivate clientIdFromLastReplay: string | undefined;\n\n\t/**\n\t * The pending messages count. Includes `pendingMessages` and `initialMessages` to keep in sync with\n\t * 'hasPendingMessages'.\n\t */\n\tpublic get pendingMessagesCount(): number {\n\t\treturn this.pendingMessages.length + this.initialMessages.length;\n\t}\n\n\t/**\n\t * The minimumPendingMessageSequenceNumber is the minimum of the first pending message and the first initial message.\n\t *\n\t * We need this so that we can properly keep local data and maintain the correct sequence window.\n\t */\n\tpublic get minimumPendingMessageSequenceNumber(): number | undefined {\n\t\treturn this.pendingMessages.peekFront()?.referenceSequenceNumber;\n\t}\n\n\t/**\n\t * Called to check if there are any pending messages in the pending message queue.\n\t * @returns A boolean indicating whether there are messages or not.\n\t */\n\tpublic hasPendingMessages(): boolean {\n\t\treturn this.pendingMessagesCount !== 0;\n\t}\n\n\tpublic getLocalState(snapshotSequenceNumber?: number): IPendingLocalState {\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x2e9 /* \"Must call getLocalState() after applying initial states\" */,\n\t\t);\n\t\t// Using snapshot sequence number to filter ops older than our latest snapshot.\n\t\t// Such ops should not be declared in pending/stashed state. Snapshot seq num will not\n\t\t// be available when the container is not attached. Therefore, no filtering is needed.\n\t\tconst newSavedOps = [...this.savedOps].filter((message) => {\n\t\t\tassert(\n\t\t\t\tmessage.sequenceNumber !== undefined,\n\t\t\t\t0x97c /* saved op should already have a sequence number */,\n\t\t\t);\n\t\t\treturn message.sequenceNumber > (snapshotSequenceNumber ?? 0);\n\t\t});\n\t\tfor (const message of this.pendingMessages.toArray()) {\n\t\t\tif (\n\t\t\t\tsnapshotSequenceNumber !== undefined &&\n\t\t\t\tmessage.referenceSequenceNumber < snapshotSequenceNumber\n\t\t\t) {\n\t\t\t\tthrow new LoggingError(\"trying to stash ops older than our latest snapshot\");\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tpendingStates: [\n\t\t\t\t...newSavedOps,\n\t\t\t\t...this.pendingMessages.toArray().map((message) => toSerializableForm(message)),\n\t\t\t],\n\t\t};\n\t}\n\n\tprivate readonly logger: ITelemetryLoggerExt;\n\n\tconstructor(\n\t\tprivate readonly stateHandler: IRuntimeStateHandler,\n\t\tstashedLocalState: IPendingLocalState | undefined,\n\t\tlogger: ITelemetryBaseLogger,\n\t) {\n\t\tthis.logger = createChildLogger({ logger });\n\t\tif (stashedLocalState?.pendingStates) {\n\t\t\tthis.initialMessages.push(...stashedLocalState.pendingStates);\n\t\t}\n\t}\n\n\tpublic get disposed(): boolean {\n\t\treturn this.disposeOnce.evaluated;\n\t}\n\tpublic readonly dispose = (): void => this.disposeOnce.value;\n\n\t/**\n\t * We've flushed an empty batch, and need to track it locally until the corresponding\n\t * ack is processed, to properly track batch IDs\n\t */\n\tpublic onFlushEmptyBatch(\n\t\tplaceholder: LocalEmptyBatchPlaceholder,\n\t\tclientSequenceNumber: number | undefined,\n\t\tstaged: boolean,\n\t): void {\n\t\t// We have to cast because runtimeOp doesn't apply for empty batches and is missing on LocalEmptyBatchPlaceholder\n\t\tthis.onFlushBatch(\n\t\t\t[placeholder satisfies Omit<LocalBatchMessage, \"runtimeOp\"> as LocalBatchMessage],\n\t\t\tclientSequenceNumber,\n\t\t\tstaged,\n\t\t);\n\t}\n\n\t/**\n\t * The given batch has been flushed, and needs to be tracked locally until the corresponding\n\t * acks are processed, to ensure it is successfully sent.\n\t * @param batch - The batch that was flushed\n\t * @param clientSequenceNumber - The CSN of the first message in the batch,\n\t * or undefined if the batch was not yet sent (e.g. by the time we flushed we lost the connection)\n\t * @param staged - Indicates whether batch is staged (not to be submitted while runtime is in Staging Mode)\n\t * @param ignoreBatchId - Whether to ignore the batchId in the batchStartInfo\n\t */\n\tpublic onFlushBatch(\n\t\tbatch: LocalBatchMessage[],\n\t\tclientSequenceNumber: number | undefined,\n\t\tstaged: boolean,\n\t\tignoreBatchId?: boolean,\n\t): void {\n\t\t// clientId and batchStartCsn are used for generating the batchId so we can detect container forks\n\t\t// where this batch was submitted by two different clients rehydrating from the same local state.\n\t\t// In the typical case where the batch was actually sent, use the clientId and clientSequenceNumber.\n\t\t// In the case where the batch was not sent, use a random uuid for clientId, and -1 for clientSequenceNumber to indicate this case.\n\t\t// This will guarantee uniqueness of the batchId, and is a suitable fallback since clientId/CSN is only needed if the batch was actually sent/sequenced.\n\t\tconst batchWasSent = clientSequenceNumber !== undefined;\n\t\tif (batchWasSent) {\n\t\t\tassert(!staged, 0xb84 /* Staged batches should not have been submitted */);\n\t\t}\n\t\tconst [clientId, batchStartCsn] = batchWasSent\n\t\t\t? [this.stateHandler.clientId(), clientSequenceNumber]\n\t\t\t: [uuid(), -1]; // -1 will indicate not a real clientId/CSN pair\n\t\tassert(\n\t\t\tclientId !== undefined,\n\t\t\t0xa33 /* clientId (from stateHandler) could only be undefined if we've never connected, but we have a CSN so we know that's not the case */,\n\t\t);\n\n\t\tfor (const message of batch) {\n\t\t\tconst {\n\t\t\t\truntimeOp,\n\t\t\t\treferenceSequenceNumber,\n\t\t\t\tlocalOpMetadata,\n\t\t\t\tmetadata: opMetadata,\n\t\t\t} = message;\n\t\t\tconst pendingMessage: IPendingMessage = {\n\t\t\t\ttype: \"message\",\n\t\t\t\treferenceSequenceNumber,\n\t\t\t\tcontent: serializeOp(runtimeOp),\n\t\t\t\truntimeOp,\n\t\t\t\tlocalOpMetadata,\n\t\t\t\topMetadata,\n\t\t\t\t// Note: We only will read this off the first message, but put it on all for simplicity\n\t\t\t\tbatchInfo: { clientId, batchStartCsn, length: batch.length, ignoreBatchId, staged },\n\t\t\t};\n\t\t\tthis.pendingMessages.push(pendingMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Applies stashed ops at their reference sequence number so they are ready to be ACKed or resubmitted\n\t * @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.\n\t */\n\tpublic async applyStashedOpsAt(seqNum?: number): Promise<void> {\n\t\t// apply stashed ops at sequence number\n\t\twhile (!this.initialMessages.isEmpty()) {\n\t\t\tif (seqNum !== undefined) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tconst peekMessage = this.initialMessages.peekFront()!;\n\t\t\t\tif (peekMessage.referenceSequenceNumber > seqNum) {\n\t\t\t\t\tbreak; // nothing left to do at this sequence number\n\t\t\t\t}\n\t\t\t\tif (peekMessage.referenceSequenceNumber < seqNum) {\n\t\t\t\t\tthrow new Error(\"loaded from snapshot too recent to apply stashed ops\");\n\t\t\t\t}\n\t\t\t}\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tconst nextMessage = this.initialMessages.shift()!;\n\t\t\t// Nothing to apply if the message is an empty batch.\n\t\t\t// We still need to track it for resubmission.\n\t\t\ttry {\n\t\t\t\tif (isEmptyBatchPendingMessage(nextMessage)) {\n\t\t\t\t\tnextMessage.localOpMetadata = { emptyBatch: true }; // equivalent to applyStashedOp for empty batch\n\t\t\t\t\tpatchbatchInfo(nextMessage); // Back compat\n\t\t\t\t\tthis.pendingMessages.push(nextMessage);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it\n\t\t\t\tconst localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);\n\t\t\t\tif (this.stateHandler.isAttached()) {\n\t\t\t\t\tnextMessage.localOpMetadata = localOpMetadata;\n\t\t\t\t\t// NOTE: This runtimeOp has been roundtripped through string, which is technically lossy.\n\t\t\t\t\t// e.g. At this point, handles are in their encoded form.\n\t\t\t\t\tnextMessage.runtimeOp = JSON.parse(\n\t\t\t\t\t\tnextMessage.content,\n\t\t\t\t\t) as LocalContainerRuntimeMessage;\n\t\t\t\t\t// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect\n\t\t\t\t\tpatchbatchInfo(nextMessage); // Back compat\n\t\t\t\t\tthis.pendingMessages.push(nextMessage);\n\t\t\t\t} else {\n\t\t\t\t\tif (localOpMetadata !== undefined) {\n\t\t\t\t\t\tthrow new Error(\"Local Op Metadata must be undefined when not attached\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthrow DataProcessingError.wrapIfUnrecognized(error, \"applyStashedOp\", nextMessage);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Compares the batch ID of the incoming batch with the pending batch ID for this client.\n\t * They should not match, as that would indicate a forked container.\n\t * @param remoteBatchStart - BatchStartInfo for an incoming batch *NOT* submitted by this client\n\t * @returns whether the batch IDs match\n\t */\n\tprivate remoteBatchMatchesPendingBatch(remoteBatchStart: BatchStartInfo): boolean {\n\t\t// Find the first pending message that uses Batch ID, to compare to the incoming remote batch.\n\t\t// If there is no such message, then the incoming remote batch doesn't have a match here and we can return.\n\t\tconst firstIndexUsingBatchId = Array.from({\n\t\t\tlength: this.pendingMessages.length,\n\t\t}).findIndex((_, i) => this.pendingMessages.get(i)?.batchInfo.ignoreBatchId !== true);\n\t\tconst pendingMessageUsingBatchId =\n\t\t\tfirstIndexUsingBatchId === -1\n\t\t\t\t? undefined\n\t\t\t\t: this.pendingMessages.get(firstIndexUsingBatchId);\n\n\t\tif (pendingMessageUsingBatchId === undefined) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// We must compare the effective batch IDs, since one of these ops\n\t\t// may have been the original, not resubmitted, so wouldn't have its batch ID stamped yet.\n\t\tconst pendingBatchId = getEffectiveBatchId(pendingMessageUsingBatchId);\n\t\tconst inboundBatchId = getEffectiveBatchId(remoteBatchStart);\n\n\t\treturn pendingBatchId === inboundBatchId;\n\t}\n\n\t/**\n\t * Processes an inbound message or batch of messages - May be local or remote.\n\t *\n\t * @param inbound - The inbound message(s) to process, with extra info (e.g. about the start of a batch). Could be local or remote.\n\t * @param local - true if we submitted these messages and expect corresponding pending messages\n\t * @returns The inbound messages with localOpMetadata \"zipped\" in.\n\t *\n\t * @throws a DataProcessingError in either of these cases:\n\t * - The pending message content doesn't match the incoming message content for any message here\n\t * - The batch IDs *do match* but it's not local (indicates Container forking).\n\t */\n\tpublic processInboundMessages(\n\t\tinbound: InboundMessageResult,\n\t\tlocal: boolean,\n\t): {\n\t\tmessage: InboundSequencedContainerRuntimeMessage;\n\t\tlocalOpMetadata?: unknown;\n\t}[] {\n\t\tif (local) {\n\t\t\treturn this.processPendingLocalMessages(inbound);\n\t\t}\n\n\t\t// An inbound remote batch should not match the pending batch ID for this client.\n\t\t// That would indicate the container forked (two instances trying to submit the same local state)\n\t\tif (\"batchStart\" in inbound && this.remoteBatchMatchesPendingBatch(inbound.batchStart)) {\n\t\t\tthrow DataProcessingError.create(\n\t\t\t\t\"Forked Container Error! Matching batchIds but mismatched clientId\",\n\t\t\t\t\"PendingStateManager.processInboundMessages\",\n\t\t\t\tinbound.batchStart.keyMessage,\n\t\t\t);\n\t\t}\n\n\t\t// No localOpMetadata for remote messages\n\t\tconst messages = inbound.type === \"fullBatch\" ? inbound.messages : [inbound.nextMessage];\n\t\treturn messages.map((message) => ({ message }));\n\t}\n\n\t/**\n\t * Processes the incoming message(s) from the server that were submitted by this client.\n\t * It verifies that messages are received in the right order and that any batch information is correct.\n\t * @param inbound - The inbound message(s) (originating from this client) to correlate with the pending local state\n\t * @throws DataProcessingError if the pending message content doesn't match the incoming message content for any message here\n\t * @returns The inbound messages with localOpMetadata \"zipped\" in.\n\t */\n\tprivate processPendingLocalMessages(inbound: InboundMessageResult): {\n\t\tmessage: InboundSequencedContainerRuntimeMessage;\n\t\tlocalOpMetadata: unknown;\n\t}[] {\n\t\tif (\"batchStart\" in inbound) {\n\t\t\tthis.onLocalBatchBegin(inbound.batchStart, inbound.length);\n\t\t}\n\n\t\t// Empty batch\n\t\tif (inbound.length === 0) {\n\t\t\tconst localOpMetadata = this.processNextPendingMessage(\n\t\t\t\tinbound.batchStart.keyMessage.sequenceNumber,\n\t\t\t);\n\t\t\tassert(\n\t\t\t\tasEmptyBatchLocalOpMetadata(localOpMetadata)?.emptyBatch === true,\n\t\t\t\t0xa20 /* Expected empty batch marker */,\n\t\t\t);\n\t\t\treturn [];\n\t\t}\n\n\t\tconst messages = inbound.type === \"fullBatch\" ? inbound.messages : [inbound.nextMessage];\n\n\t\treturn messages.map((message) => ({\n\t\t\tmessage,\n\t\t\tlocalOpMetadata: this.processNextPendingMessage(message.sequenceNumber, message),\n\t\t}));\n\t}\n\n\t/**\n\t * Processes the pending local copy of message that's been ack'd by the server.\n\t * @param sequenceNumber - The sequenceNumber from the server corresponding to the next pending message.\n\t * @param message - [optional] The entire incoming message, for comparing contents with the pending message for extra validation.\n\t * @throws DataProcessingError if the pending message content doesn't match the incoming message content.\n\t * @returns - The localOpMetadata of the next pending message, to be sent to whoever submitted the original message.\n\t */\n\tprivate processNextPendingMessage(\n\t\tsequenceNumber: number,\n\t\tmessage?: InboundSequencedContainerRuntimeMessage,\n\t): unknown {\n\t\tconst pendingMessage = this.pendingMessages.peekFront();\n\t\tassert(\n\t\t\tpendingMessage !== undefined,\n\t\t\t0x169 /* \"No pending message found for this remote message\" */,\n\t\t);\n\n\t\tpendingMessage.sequenceNumber = sequenceNumber;\n\t\tthis.savedOps.push(toSerializableForm(pendingMessage));\n\n\t\tthis.pendingMessages.shift();\n\n\t\t// message is undefined in the Empty Batch case,\n\t\t// because we don't have an incoming message to compare and pendingMessage is just a placeholder anyway.\n\t\tif (message !== undefined) {\n\t\t\tconst messageContent = buildPendingMessageContent(message);\n\n\t\t\t// Stringified content should match\n\t\t\t// If it doesn't, collect as much info about the difference as possible (privacy-wise) and log it\n\t\t\tif (pendingMessage.content !== messageContent) {\n\t\t\t\tconst [pendingLength, incomingLength] = [\n\t\t\t\t\tpendingMessage.content.length,\n\t\t\t\t\tmessageContent.length,\n\t\t\t\t];\n\t\t\t\tconst [mismatchStartIndex, pendingChar, incomingChar] = findFirstCharacterMismatched(\n\t\t\t\t\tpendingMessage.content,\n\t\t\t\t\tmessageContent,\n\t\t\t\t);\n\n\t\t\t\tconst pendingContentObj = JSON.parse(\n\t\t\t\t\tpendingMessage.content,\n\t\t\t\t) as LocalContainerRuntimeMessage;\n\t\t\t\tconst incomingContentObj = JSON.parse(\n\t\t\t\t\tmessageContent,\n\t\t\t\t) as InboundContainerRuntimeMessage;\n\n\t\t\t\t// Compare inner contents object, since that both should be { type, contents }\n\t\t\t\tconst contentsMatch =\n\t\t\t\t\tpendingContentObj.contents === incomingContentObj.contents ||\n\t\t\t\t\t(pendingContentObj.contents !== undefined &&\n\t\t\t\t\t\tincomingContentObj.contents !== undefined &&\n\t\t\t\t\t\tJSON.stringify(pendingContentObj.contents) ===\n\t\t\t\t\t\t\tJSON.stringify(incomingContentObj.contents));\n\n\t\t\t\tthis.logger.sendErrorEvent({\n\t\t\t\t\teventName: \"unexpectedAckReceived\",\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tpendingContentScrubbed: scrubAndStringify(pendingContentObj),\n\t\t\t\t\t\tincomingContentScrubbed: scrubAndStringify(incomingContentObj),\n\t\t\t\t\t\tcontentsMatch,\n\t\t\t\t\t\tpendingLength,\n\t\t\t\t\t\tincomingLength,\n\t\t\t\t\t\tmismatchStartIndex,\n\t\t\t\t\t\tpendingChar,\n\t\t\t\t\t\tincomingChar,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tthrow DataProcessingError.create(\n\t\t\t\t\t\"pending local message content mismatch\",\n\t\t\t\t\t\"unexpectedAckReceived\",\n\t\t\t\t\tmessage,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn pendingMessage.localOpMetadata;\n\t}\n\n\t/**\n\t * Check if the incoming batch matches the batch info for the next pending message.\n\t */\n\tprivate onLocalBatchBegin(batchStart: BatchStartInfo, batchLength?: number): void {\n\t\t// Get the next message from the pending queue. Verify a message exists.\n\t\tconst pendingMessage = this.pendingMessages.peekFront();\n\t\tassert(\n\t\t\tpendingMessage !== undefined,\n\t\t\t0xa21 /* No pending message found as we start processing this remote batch */,\n\t\t);\n\t\tassert(!pendingMessage.batchInfo.staged, 0xb85 /* Can't get an ack from a staged batch */);\n\n\t\t// If this batch became empty on resubmit, batch.messages will be empty (but keyMessage is always set)\n\t\t// and the next pending message should be an empty batch marker.\n\t\t// More Info: We must submit empty batches and track them in case a different fork\n\t\t// of this container also submitted the same batch (and it may not be empty for that fork).\n\t\tconst firstMessage = batchStart.keyMessage;\n\t\t// -1 length is for back compat, undefined length means we actually don't know it\n\t\tconst skipLengthCheck =\n\t\t\tpendingMessage.batchInfo.length === -1 || batchLength === undefined;\n\t\tconst expectedPendingBatchLength =\n\t\t\tbatchLength === 0\n\t\t\t\t? 1 // For an empty batch, expect a singleton array with the empty batch marker\n\t\t\t\t: batchLength; // Otherwise, the lengths should actually match\n\n\t\t// Note: We don't need to use getEffectiveBatchId here, just check the explicit stamped batchID\n\t\t// That logic is needed only when comparing across potential container forks.\n\t\t// Furthermore, we also are comparing the batch IDs constituent data - clientId (it's local) and batchStartCsn.\n\t\tconst pendingBatchId = asBatchMetadata(pendingMessage.opMetadata)?.batchId;\n\t\tconst inboundBatchId = batchStart.batchId;\n\n\t\t// We expect the incoming batch to be of the same length, starting at the same clientSequenceNumber,\n\t\t// as the batch we originally submitted. The batchIds should match as well, if set (or neither should be set)\n\t\t// We have another later check to compare the message contents, which we'd expect to fail if this check does,\n\t\t// so we don't throw here, merely log. In a later release this check may replace that one since it's cheaper.\n\t\tif (\n\t\t\tpendingMessage.batchInfo.batchStartCsn !== batchStart.batchStartCsn ||\n\t\t\t(!skipLengthCheck && pendingMessage.batchInfo.length !== expectedPendingBatchLength) ||\n\t\t\tpendingBatchId !== inboundBatchId\n\t\t) {\n\t\t\tthis.logger?.sendErrorEvent({\n\t\t\t\teventName: \"BatchInfoMismatch\",\n\t\t\t\tdetails: {\n\t\t\t\t\tpendingBatchCsn: pendingMessage.batchInfo.batchStartCsn,\n\t\t\t\t\tbatchStartCsn: batchStart.batchStartCsn,\n\t\t\t\t\tpendingBatchLength: pendingMessage.batchInfo.length,\n\t\t\t\t\texpectedPendingBatchLength,\n\t\t\t\t\tbatchLength,\n\t\t\t\t\tpendingBatchId,\n\t\t\t\t\tinboundBatchId,\n\t\t\t\t\tpendingMessageBatchMetadata: asBatchMetadata(pendingMessage.opMetadata)?.batch,\n\t\t\t\t\tmessageBatchMetadata: asBatchMetadata(firstMessage?.metadata)?.batch,\n\t\t\t\t},\n\t\t\t\tmessageDetails: firstMessage && extractSafePropertiesFromMessage(firstMessage),\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Called when the Container's connection state changes. If the Container gets connected, it replays all the pending\n\t * states in its queue. This includes triggering resubmission of unacked ops.\n\t * ! Note: successfully resubmitting an op that has been successfully sequenced is not possible due to checks in the ConnectionStateHandler (Loader layer)\n\t * @param onlyStagedBatches - If true, only replay staged batches. This is used when we are exiting staging mode and want to rebase and submit the staged batches.\n\t */\n\tpublic replayPendingStates(onlyStagedBatches?: boolean): void {\n\t\tassert(\n\t\t\tthis.stateHandler.connected(),\n\t\t\t0x172 /* \"The connection state is not consistent with the runtime\" */,\n\t\t);\n\n\t\t// Staged batches have not yet been submitted so check doesn't apply\n\t\tif (!onlyStagedBatches) {\n\t\t\t// This assert suggests we are about to send same ops twice, which will result in data loss.\n\t\t\tassert(\n\t\t\t\tthis.clientIdFromLastReplay !== this.stateHandler.clientId(),\n\t\t\t\t0x173 /* \"replayPendingStates called twice for same clientId!\" */,\n\t\t\t);\n\t\t}\n\t\tthis.clientIdFromLastReplay = this.stateHandler.clientId();\n\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x174 /* \"initial states should be empty before replaying pending\" */,\n\t\t);\n\n\t\tconst initialPendingMessagesCount = this.pendingMessages.length;\n\t\tlet remainingPendingMessagesCount = this.pendingMessages.length;\n\n\t\tlet seenStagedBatch = false;\n\n\t\t// Process exactly `pendingMessagesCount` items in the queue as it represents the number of messages that were\n\t\t// pending when we connected. This is important because the `reSubmitFn` might add more items in the queue\n\t\t// which must not be replayed.\n\t\twhile (remainingPendingMessagesCount > 0) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tlet pendingMessage = this.pendingMessages.shift()!;\n\t\t\tremainingPendingMessagesCount--;\n\n\t\t\t// Re-queue pre-staging messages if we are only processing staged batches\n\t\t\tif (onlyStagedBatches) {\n\t\t\t\tif (!pendingMessage.batchInfo.staged) {\n\t\t\t\t\tassert(!seenStagedBatch, 0xb86 /* Staged batch was followed by non-staged batch */);\n\t\t\t\t\tthis.pendingMessages.push(pendingMessage);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tseenStagedBatch = true;\n\t\t\t\tpendingMessage.batchInfo.staged = false; // Clear staged flag so we can submit\n\t\t\t}\n\n\t\t\tconst batchMetadataFlag = asBatchMetadata(pendingMessage.opMetadata)?.batch;\n\t\t\tassert(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);\n\n\t\t\t// The next message starts a batch (possibly single-message), and we'll need its batchId.\n\t\t\tconst batchId = getEffectiveBatchId(pendingMessage);\n\n\t\t\tconst staged = pendingMessage.batchInfo.staged;\n\n\t\t\tif (asEmptyBatchLocalOpMetadata(pendingMessage.localOpMetadata)?.emptyBatch === true) {\n\t\t\t\t// Resubmit no messages, with the batchId. Will result in another empty batch marker.\n\t\t\t\tthis.stateHandler.reSubmitBatch([], batchId, staged);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tassert(\n\t\t\t\tpendingMessage.runtimeOp !== undefined,\n\t\t\t\t0xb87 /* viableOp is only undefined for empty batches */,\n\t\t\t);\n\n\t\t\t/**\n\t\t\t * We must preserve the distinct batches on resubmit.\n\t\t\t * Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will\n\t\t\t * either receive the whole batch ack or nothing at all. See {@link InboundBatchAggregator} for how this works.\n\t\t\t */\n\t\t\tif (batchMetadataFlag === undefined) {\n\t\t\t\t// Single-message batch\n\n\t\t\t\tthis.stateHandler.reSubmitBatch(\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\truntimeOp: pendingMessage.runtimeOp,\n\t\t\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\tbatchId,\n\t\t\t\t\tstaged,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// else: batchMetadataFlag === true (It's a typical multi-message batch)\n\n\t\t\tassert(\n\t\t\t\tremainingPendingMessagesCount > 0,\n\t\t\t\t0x554 /* Last pending message cannot be a batch begin */,\n\t\t\t);\n\n\t\t\tconst batch: PendingMessageResubmitData[] = [];\n\n\t\t\t// check is >= because batch end may be last pending message\n\t\t\twhile (remainingPendingMessagesCount >= 0) {\n\t\t\t\tassert(\n\t\t\t\t\tpendingMessage.runtimeOp !== undefined,\n\t\t\t\t\t0xb88 /* viableOp is only undefined for empty batches */,\n\t\t\t\t);\n\t\t\t\tbatch.push({\n\t\t\t\t\truntimeOp: pendingMessage.runtimeOp,\n\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t});\n\n\t\t\t\t// End of the batch\n\t\t\t\tif (pendingMessage.opMetadata?.batch === false) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tassert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);\n\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tpendingMessage = this.pendingMessages.shift()!;\n\t\t\t\tremainingPendingMessagesCount--;\n\t\t\t\tassert(\n\t\t\t\t\tpendingMessage.opMetadata?.batch !== true,\n\t\t\t\t\t0x556 /* Batch start needs a corresponding batch end */,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.stateHandler.reSubmitBatch(batch, batchId, staged);\n\t\t}\n\n\t\t// pending ops should no longer depend on previous sequenced local ops after resubmit\n\t\tthis.savedOps = [];\n\n\t\t// We replayPendingStates on read connections too - we expect these to get nack'd though, and to then reconnect\n\t\t// on a write connection and replay again. This filters out the replay that happens on the read connection so\n\t\t// we only see the replays on write connections (that have a chance to go through).\n\t\tif (this.stateHandler.isActiveConnection()) {\n\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\teventName: \"PendingStatesReplayed\",\n\t\t\t\tcount: initialPendingMessagesCount,\n\t\t\t\tclientId: this.stateHandler.clientId(),\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Clears the 'staged' flag off all pending messages.\n\t */\n\tpublic clearStagingFlags(): void {\n\t\tfor (const message of this.pendingMessages.toArray()) {\n\t\t\tif (message.batchInfo.staged) {\n\t\t\t\tmessage.batchInfo.staged = false;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Pops all staged batches, invoking the callback on each one in order (LIFO)\n\t */\n\tpublic popStagedBatches(callback: (stagedMessage: IPendingMessage) => void): void {\n\t\twhile (!this.pendingMessages.isEmpty()) {\n\t\t\tconst stagedMessage = this.pendingMessages.peekBack();\n\t\t\tif (stagedMessage?.batchInfo.staged === true) {\n\t\t\t\tcallback(stagedMessage);\n\t\t\t\tthis.pendingMessages.pop();\n\t\t\t} else {\n\t\t\t\tbreak; // no more staged messages\n\t\t\t}\n\t\t}\n\t\tassert(\n\t\t\tthis.pendingMessages.toArray().every((m) => m.batchInfo.staged !== true),\n\t\t\t0xb89 /* Shouldn't be any more staged messages */,\n\t\t);\n\t}\n}\n\n/**\n * For back-compat if trying to apply stashed ops that pre-date batchInfo\n */\nfunction patchbatchInfo(\n\tmessage: IPendingMessageFromStash,\n): asserts message is IPendingMessage {\n\tconst batchInfo: IPendingMessageFromStash[\"batchInfo\"] = message.batchInfo;\n\tif (batchInfo === undefined) {\n\t\t// Using uuid guarantees uniqueness, retaining existing behavior\n\t\tmessage.batchInfo = { clientId: uuid(), batchStartCsn: -1, length: -1, staged: false };\n\t}\n}\n"]}
1
+ {"version":3,"file":"pendingStateManager.js","sourceRoot":"","sources":["../src/pendingStateManager.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 { IDisposable, type ITelemetryBaseLogger } from \"@fluidframework/core-interfaces\";\nimport { assert, Lazy } from \"@fluidframework/core-utils/internal\";\nimport {\n\tITelemetryLoggerExt,\n\tDataProcessingError,\n\tLoggingError,\n\textractSafePropertiesFromMessage,\n\tcreateChildLogger,\n} from \"@fluidframework/telemetry-utils/internal\";\nimport Deque from \"double-ended-queue\";\nimport { v4 as uuid } from \"uuid\";\n\nimport {\n\ttype InboundContainerRuntimeMessage,\n\ttype InboundSequencedContainerRuntimeMessage,\n\ttype LocalContainerRuntimeMessage,\n} from \"./messageTypes.js\";\nimport { asBatchMetadata, asEmptyBatchLocalOpMetadata } from \"./metadata.js\";\nimport {\n\tLocalBatchMessage,\n\tgetEffectiveBatchId,\n\tBatchStartInfo,\n\tInboundMessageResult,\n\tserializeOp,\n\ttype LocalEmptyBatchPlaceholder,\n\ttype BatchResubmitInfo,\n} from \"./opLifecycle/index.js\";\n\n/**\n * This represents a message that has been submitted and is added to the pending queue when `submit` is called on the\n * ContainerRuntime. This message has either not been ack'd by the server or has not been submitted to the server yet.\n *\n * @remarks This is the current serialization format for pending local state when a Container is serialized.\n */\nexport interface IPendingMessage {\n\ttype: \"message\";\n\treferenceSequenceNumber: number;\n\t/**\n\t * Serialized copy of runtimeOp\n\t */\n\tcontent: string;\n\t/**\n\t * The original runtime op that was submitted to the ContainerRuntime\n\t * Unless this pending message came from stashed content, in which case this was roundtripped through string\n\t */\n\truntimeOp?: LocalContainerRuntimeMessage | undefined; // Undefined for empty batches and initial messages before parsing\n\tlocalOpMetadata: unknown;\n\topMetadata: Record<string, unknown> | undefined;\n\tsequenceNumber?: number;\n\t/**\n\t * Info about the batch this pending message belongs to, for validation and for computing the batchId on reconnect\n\t * We don't include batchId itself to avoid redundancy, because that's stamped on opMetadata above\n\t */\n\tbatchInfo: {\n\t\t/**\n\t\t * The Batch's original clientId, from when it was first flushed to be submitted.\n\t\t * Or, a random uuid if it was never submitted (and batchStartCsn will be -1)\n\t\t */\n\t\tclientId: string;\n\t\t/**\n\t\t * The Batch's original clientSequenceNumber, from when it was first flushed to be submitted\n\t\t * Or, -1 if it was never submitted (and clientId will be a random uuid)\n\t\t */\n\t\tbatchStartCsn: number;\n\t\t/**\n\t\t * length of the batch (how many runtime messages here)\n\t\t */\n\t\tlength: number;\n\t\t/**\n\t\t * If true, don't compare batchID of incoming batches to this. e.g. ID Allocation Batch IDs should be ignored\n\t\t */\n\t\tignoreBatchId?: boolean;\n\t\t/**\n\t\t * If true, this batch is staged and should not actually be submitted on replayPendingStates.\n\t\t */\n\t\tstaged: boolean;\n\t};\n}\n\ntype Patch<T, U> = U & Omit<T, keyof U>;\n\n/**\n * First version of the type (pre-dates batchInfo)\n */\ntype IPendingMessageV0 = Patch<IPendingMessage, { batchInfo?: undefined }>;\n\n/**\n * Union of all supported schemas for when applying stashed ops\n *\n * @remarks When the format changes, this type should update to reflect all possible schemas.\n */\ntype IPendingMessageFromStash = IPendingMessageV0 | IPendingMessage;\n\nexport interface IPendingLocalState {\n\t/**\n\t * list of pending states, including ops and batch information\n\t */\n\tpendingStates: IPendingMessage[];\n}\n\n/**\n * Info needed to replay/resubmit a pending message\n */\nexport type PendingMessageResubmitData = Pick<\n\tIPendingMessage,\n\t\"runtimeOp\" | \"localOpMetadata\" | \"opMetadata\"\n> & {\n\t// Required (it's only missing on IPendingMessage for empty batch, which will be resubmitted as an empty array)\n\truntimeOp: LocalContainerRuntimeMessage;\n};\n\nexport interface PendingBatchResubmitMetadata extends BatchResubmitInfo {\n\t/**\n\t * Whether changes in this batch should be squashed when resubmitting.\n\t */\n\tsquash: boolean;\n}\n\nexport interface IRuntimeStateHandler {\n\tconnected(): boolean;\n\tclientId(): string | undefined;\n\tapplyStashedOp(serializedOp: string): Promise<unknown>;\n\treSubmitBatch(\n\t\tbatch: PendingMessageResubmitData[],\n\t\tmetadata: PendingBatchResubmitMetadata,\n\t): void;\n\tisActiveConnection: () => boolean;\n\tisAttached: () => boolean;\n}\n\nfunction isEmptyBatchPendingMessage(message: IPendingMessageFromStash): boolean {\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n\tconst content = JSON.parse(message.content);\n\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n\treturn content.type === \"groupedBatch\" && content.contents?.length === 0;\n}\n\nfunction buildPendingMessageContent(message: InboundSequencedContainerRuntimeMessage): string {\n\t// IMPORTANT: Order matters here, this must match the order of the properties used\n\t// when submitting the message.\n\tconst { type, contents }: InboundContainerRuntimeMessage = message;\n\t// Any properties that are not defined, won't be emitted by stringify.\n\treturn JSON.stringify({ type, contents });\n}\n\nfunction typesOfKeys<T extends object>(obj: T): Record<keyof T, string> {\n\treturn Object.keys(obj).reduce((acc, key) => {\n\t\tacc[key] = typeof obj[key];\n\t\treturn acc;\n\t}, {}) as Record<keyof T, string>;\n}\n\nfunction scrubAndStringify(\n\tmessage: InboundContainerRuntimeMessage | LocalContainerRuntimeMessage,\n): string {\n\t// Scrub the whole object in case there are unexpected keys\n\tconst scrubbed: Record<string, unknown> = typesOfKeys(message);\n\n\t// For these known/expected keys, we can either drill into the object (for contents)\n\t// or just use the value as-is (since it's not personal info)\n\tscrubbed.contents = message.contents && typesOfKeys(message.contents);\n\tscrubbed.type = message.type;\n\n\treturn JSON.stringify(scrubbed);\n}\n\n/**\n * Finds and returns the index where the strings diverge, and the character at that index in each string (or undefined if not applicable)\n */\nfunction findFirstRawCharacterMismatched(\n\ta: string,\n\tb: string,\n): [index: number, charA?: string, charB?: string] {\n\tconst minLength = Math.min(a.length, b.length);\n\tfor (let i = 0; i < minLength; i++) {\n\t\tif (a[i] !== b[i]) {\n\t\t\treturn [i, a[i], b[i]];\n\t\t}\n\t}\n\n\t// Since we didn't return in the loop, the shorter string must be a prefix of the other.\n\t// If they're the same length, return -1 to indicate they're identical.\n\t// Otherwise, the next character of the longer one is where they differ. No need to return that next character.\n\treturn a.length === b.length\n\t\t? [-1, undefined, undefined]\n\t\t: [minLength, a[minLength], b[minLength]];\n}\n\n/**\n * Finds and returns the index where the strings diverge, and the character at that index in each string (or undefined if not applicable)\n * It scrubs non-ASCII characters since they convey more meaning (privacy consideration)\n */\nexport function findFirstCharacterMismatched(\n\ta: string,\n\tb: string,\n): [index: number, charA?: string, charB?: string] {\n\tconst [index, rawCharA, rawCharB] = findFirstRawCharacterMismatched(a, b);\n\n\tconst charA = (rawCharA?.codePointAt(0) ?? 0) <= 0x7f ? rawCharA : \"[non-ASCII]\";\n\tconst charB = (rawCharB?.codePointAt(0) ?? 0) <= 0x7f ? rawCharB : \"[non-ASCII]\";\n\n\treturn [index, charA, charB];\n}\n\n/**\n * Returns a shallow copy of the given message with the non-serializable properties removed.\n * Note that the runtimeOp's data has already been serialized in the content property.\n */\nfunction toSerializableForm(\n\tmessage: IPendingMessage,\n): IPendingMessage & { runtimeOp: undefined; localOpMetadata: undefined } {\n\treturn {\n\t\t...message,\n\t\tlocalOpMetadata: undefined,\n\t\truntimeOp: undefined,\n\t};\n}\n\ninterface ReplayPendingStateOptions {\n\t/**\n\t * If true, only replay staged batches. This is used when we are exiting staging mode and want to rebase and submit the staged batches.\n\t * Default: false\n\t */\n\tonlyStagedBatches: boolean;\n\t/**\n\t * @param squash - If true, edits should be squashed when resubmitting.\n\t * Default: false\n\t */\n\tsquash: boolean;\n}\n\nconst defaultReplayPendingStatesOptions: ReplayPendingStateOptions = {\n\tonlyStagedBatches: false,\n\tsquash: false,\n};\n\n/**\n * PendingStateManager is responsible for maintaining the messages that have not been sent or have not yet been\n * acknowledged by the server. It also maintains the batch information for both automatically and manually flushed\n * batches along with the messages.\n * When the Container reconnects, it replays the pending states, which includes manual flushing\n * of messages and triggering resubmission of unacked ops.\n *\n * It verifies that all the ops are acked, are received in the right order and batch information is correct.\n */\nexport class PendingStateManager implements IDisposable {\n\t/**\n\t * Messages that will need to be resubmitted if not ack'd before the next reconnection\n\t */\n\tprivate readonly pendingMessages = new Deque<IPendingMessage>();\n\t/**\n\t * Messages stashed from a previous container, now being rehydrated. Need to be resubmitted.\n\t */\n\tprivate readonly initialMessages = new Deque<IPendingMessageFromStash>();\n\n\t/**\n\t * Sequenced local ops that are saved when stashing since pending ops may depend on them\n\t */\n\tprivate savedOps: IPendingMessage[] = [];\n\n\t// eslint-disable-next-line unicorn/consistent-function-scoping -- Property is defined once; no need to extract inner lambda\n\tprivate readonly disposeOnce = new Lazy<void>(() => {\n\t\tthis.initialMessages.clear();\n\t\tthis.pendingMessages.clear();\n\t});\n\n\t/**\n\t * Used to ensure we don't replay ops on the same connection twice\n\t */\n\tprivate clientIdFromLastReplay: string | undefined;\n\n\t/**\n\t * The pending messages count. Includes `pendingMessages` and `initialMessages` to keep in sync with\n\t * 'hasPendingMessages'.\n\t */\n\tpublic get pendingMessagesCount(): number {\n\t\treturn this.pendingMessages.length + this.initialMessages.length;\n\t}\n\n\t/**\n\t * The minimumPendingMessageSequenceNumber is the minimum of the first pending message and the first initial message.\n\t *\n\t * We need this so that we can properly keep local data and maintain the correct sequence window.\n\t */\n\tpublic get minimumPendingMessageSequenceNumber(): number | undefined {\n\t\treturn this.pendingMessages.peekFront()?.referenceSequenceNumber;\n\t}\n\n\t/**\n\t * Called to check if there are any pending messages in the pending message queue.\n\t * @returns A boolean indicating whether there are messages or not.\n\t */\n\tpublic hasPendingMessages(): boolean {\n\t\treturn this.pendingMessagesCount !== 0;\n\t}\n\n\tpublic getLocalState(snapshotSequenceNumber?: number): IPendingLocalState {\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x2e9 /* \"Must call getLocalState() after applying initial states\" */,\n\t\t);\n\t\t// Using snapshot sequence number to filter ops older than our latest snapshot.\n\t\t// Such ops should not be declared in pending/stashed state. Snapshot seq num will not\n\t\t// be available when the container is not attached. Therefore, no filtering is needed.\n\t\tconst newSavedOps = [...this.savedOps].filter((message) => {\n\t\t\tassert(\n\t\t\t\tmessage.sequenceNumber !== undefined,\n\t\t\t\t0x97c /* saved op should already have a sequence number */,\n\t\t\t);\n\t\t\treturn message.sequenceNumber > (snapshotSequenceNumber ?? 0);\n\t\t});\n\t\tfor (const message of this.pendingMessages.toArray()) {\n\t\t\tif (\n\t\t\t\tsnapshotSequenceNumber !== undefined &&\n\t\t\t\tmessage.referenceSequenceNumber < snapshotSequenceNumber\n\t\t\t) {\n\t\t\t\tthrow new LoggingError(\"trying to stash ops older than our latest snapshot\");\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tpendingStates: [\n\t\t\t\t...newSavedOps,\n\t\t\t\t...this.pendingMessages.toArray().map((message) => toSerializableForm(message)),\n\t\t\t],\n\t\t};\n\t}\n\n\tprivate readonly logger: ITelemetryLoggerExt;\n\n\tconstructor(\n\t\tprivate readonly stateHandler: IRuntimeStateHandler,\n\t\tstashedLocalState: IPendingLocalState | undefined,\n\t\tlogger: ITelemetryBaseLogger,\n\t) {\n\t\tthis.logger = createChildLogger({ logger });\n\t\tif (stashedLocalState?.pendingStates) {\n\t\t\tthis.initialMessages.push(...stashedLocalState.pendingStates);\n\t\t}\n\t}\n\n\tpublic get disposed(): boolean {\n\t\treturn this.disposeOnce.evaluated;\n\t}\n\tpublic readonly dispose = (): void => this.disposeOnce.value;\n\n\t/**\n\t * We've flushed an empty batch, and need to track it locally until the corresponding\n\t * ack is processed, to properly track batch IDs\n\t */\n\tpublic onFlushEmptyBatch(\n\t\tplaceholder: LocalEmptyBatchPlaceholder,\n\t\tclientSequenceNumber: number | undefined,\n\t\tstaged: boolean,\n\t): void {\n\t\t// We have to cast because runtimeOp doesn't apply for empty batches and is missing on LocalEmptyBatchPlaceholder\n\t\tthis.onFlushBatch(\n\t\t\t[placeholder satisfies Omit<LocalBatchMessage, \"runtimeOp\"> as LocalBatchMessage],\n\t\t\tclientSequenceNumber,\n\t\t\tstaged,\n\t\t);\n\t}\n\n\t/**\n\t * The given batch has been flushed, and needs to be tracked locally until the corresponding\n\t * acks are processed, to ensure it is successfully sent.\n\t * @param batch - The batch that was flushed\n\t * @param clientSequenceNumber - The CSN of the first message in the batch,\n\t * or undefined if the batch was not yet sent (e.g. by the time we flushed we lost the connection)\n\t * @param staged - Indicates whether batch is staged (not to be submitted while runtime is in Staging Mode)\n\t * @param ignoreBatchId - Whether to ignore the batchId in the batchStartInfo\n\t */\n\tpublic onFlushBatch(\n\t\tbatch: LocalBatchMessage[],\n\t\tclientSequenceNumber: number | undefined,\n\t\tstaged: boolean,\n\t\tignoreBatchId?: boolean,\n\t): void {\n\t\t// clientId and batchStartCsn are used for generating the batchId so we can detect container forks\n\t\t// where this batch was submitted by two different clients rehydrating from the same local state.\n\t\t// In the typical case where the batch was actually sent, use the clientId and clientSequenceNumber.\n\t\t// In the case where the batch was not sent, use a random uuid for clientId, and -1 for clientSequenceNumber to indicate this case.\n\t\t// This will guarantee uniqueness of the batchId, and is a suitable fallback since clientId/CSN is only needed if the batch was actually sent/sequenced.\n\t\tconst batchWasSent = clientSequenceNumber !== undefined;\n\t\tif (batchWasSent) {\n\t\t\tassert(!staged, 0xb84 /* Staged batches should not have been submitted */);\n\t\t}\n\t\tconst [clientId, batchStartCsn] = batchWasSent\n\t\t\t? [this.stateHandler.clientId(), clientSequenceNumber]\n\t\t\t: [uuid(), -1]; // -1 will indicate not a real clientId/CSN pair\n\t\tassert(\n\t\t\tclientId !== undefined,\n\t\t\t0xa33 /* clientId (from stateHandler) could only be undefined if we've never connected, but we have a CSN so we know that's not the case */,\n\t\t);\n\n\t\tfor (const message of batch) {\n\t\t\tconst {\n\t\t\t\truntimeOp,\n\t\t\t\treferenceSequenceNumber,\n\t\t\t\tlocalOpMetadata,\n\t\t\t\tmetadata: opMetadata,\n\t\t\t} = message;\n\t\t\tconst pendingMessage: IPendingMessage = {\n\t\t\t\ttype: \"message\",\n\t\t\t\treferenceSequenceNumber,\n\t\t\t\tcontent: serializeOp(runtimeOp),\n\t\t\t\truntimeOp,\n\t\t\t\tlocalOpMetadata,\n\t\t\t\topMetadata,\n\t\t\t\t// Note: We only will read this off the first message, but put it on all for simplicity\n\t\t\t\tbatchInfo: { clientId, batchStartCsn, length: batch.length, ignoreBatchId, staged },\n\t\t\t};\n\t\t\tthis.pendingMessages.push(pendingMessage);\n\t\t}\n\t}\n\n\t/**\n\t * Applies stashed ops at their reference sequence number so they are ready to be ACKed or resubmitted\n\t * @param seqNum - Sequence number at which to apply ops. Will apply all ops if seqNum is undefined.\n\t */\n\tpublic async applyStashedOpsAt(seqNum?: number): Promise<void> {\n\t\t// apply stashed ops at sequence number\n\t\twhile (!this.initialMessages.isEmpty()) {\n\t\t\tif (seqNum !== undefined) {\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tconst peekMessage = this.initialMessages.peekFront()!;\n\t\t\t\tif (peekMessage.referenceSequenceNumber > seqNum) {\n\t\t\t\t\tbreak; // nothing left to do at this sequence number\n\t\t\t\t}\n\t\t\t\tif (peekMessage.referenceSequenceNumber < seqNum) {\n\t\t\t\t\tthrow new Error(\"loaded from snapshot too recent to apply stashed ops\");\n\t\t\t\t}\n\t\t\t}\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tconst nextMessage = this.initialMessages.shift()!;\n\t\t\t// Nothing to apply if the message is an empty batch.\n\t\t\t// We still need to track it for resubmission.\n\t\t\ttry {\n\t\t\t\tif (isEmptyBatchPendingMessage(nextMessage)) {\n\t\t\t\t\tnextMessage.localOpMetadata = { emptyBatch: true }; // equivalent to applyStashedOp for empty batch\n\t\t\t\t\tpatchbatchInfo(nextMessage); // Back compat\n\t\t\t\t\tthis.pendingMessages.push(nextMessage);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\t// applyStashedOp will cause the DDS to behave as if it has sent the op but not actually send it\n\t\t\t\tconst localOpMetadata = await this.stateHandler.applyStashedOp(nextMessage.content);\n\t\t\t\tif (this.stateHandler.isAttached()) {\n\t\t\t\t\tnextMessage.localOpMetadata = localOpMetadata;\n\t\t\t\t\t// NOTE: This runtimeOp has been roundtripped through string, which is technically lossy.\n\t\t\t\t\t// e.g. At this point, handles are in their encoded form.\n\t\t\t\t\tnextMessage.runtimeOp = JSON.parse(\n\t\t\t\t\t\tnextMessage.content,\n\t\t\t\t\t) as LocalContainerRuntimeMessage;\n\t\t\t\t\t// then we push onto pendingMessages which will cause PendingStateManager to resubmit when we connect\n\t\t\t\t\tpatchbatchInfo(nextMessage); // Back compat\n\t\t\t\t\tthis.pendingMessages.push(nextMessage);\n\t\t\t\t} else {\n\t\t\t\t\tif (localOpMetadata !== undefined) {\n\t\t\t\t\t\tthrow new Error(\"Local Op Metadata must be undefined when not attached\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tthrow DataProcessingError.wrapIfUnrecognized(error, \"applyStashedOp\", nextMessage);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Compares the batch ID of the incoming batch with the pending batch ID for this client.\n\t * They should not match, as that would indicate a forked container.\n\t * @param remoteBatchStart - BatchStartInfo for an incoming batch *NOT* submitted by this client\n\t * @returns whether the batch IDs match\n\t */\n\tprivate remoteBatchMatchesPendingBatch(remoteBatchStart: BatchStartInfo): boolean {\n\t\t// Find the first pending message that uses Batch ID, to compare to the incoming remote batch.\n\t\t// If there is no such message, then the incoming remote batch doesn't have a match here and we can return.\n\t\tconst firstIndexUsingBatchId = Array.from({\n\t\t\tlength: this.pendingMessages.length,\n\t\t}).findIndex((_, i) => this.pendingMessages.get(i)?.batchInfo.ignoreBatchId !== true);\n\t\tconst pendingMessageUsingBatchId =\n\t\t\tfirstIndexUsingBatchId === -1\n\t\t\t\t? undefined\n\t\t\t\t: this.pendingMessages.get(firstIndexUsingBatchId);\n\n\t\tif (pendingMessageUsingBatchId === undefined) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// We must compare the effective batch IDs, since one of these ops\n\t\t// may have been the original, not resubmitted, so wouldn't have its batch ID stamped yet.\n\t\tconst pendingBatchId = getEffectiveBatchId(pendingMessageUsingBatchId);\n\t\tconst inboundBatchId = getEffectiveBatchId(remoteBatchStart);\n\n\t\treturn pendingBatchId === inboundBatchId;\n\t}\n\n\t/**\n\t * Processes an inbound message or batch of messages - May be local or remote.\n\t *\n\t * @param inbound - The inbound message(s) to process, with extra info (e.g. about the start of a batch). Could be local or remote.\n\t * @param local - true if we submitted these messages and expect corresponding pending messages\n\t * @returns The inbound messages with localOpMetadata \"zipped\" in.\n\t *\n\t * @throws a DataProcessingError in either of these cases:\n\t * - The pending message content doesn't match the incoming message content for any message here\n\t * - The batch IDs *do match* but it's not local (indicates Container forking).\n\t */\n\tpublic processInboundMessages(\n\t\tinbound: InboundMessageResult,\n\t\tlocal: boolean,\n\t): {\n\t\tmessage: InboundSequencedContainerRuntimeMessage;\n\t\tlocalOpMetadata?: unknown;\n\t}[] {\n\t\tif (local) {\n\t\t\treturn this.processPendingLocalMessages(inbound);\n\t\t}\n\n\t\t// An inbound remote batch should not match the pending batch ID for this client.\n\t\t// That would indicate the container forked (two instances trying to submit the same local state)\n\t\tif (\"batchStart\" in inbound && this.remoteBatchMatchesPendingBatch(inbound.batchStart)) {\n\t\t\tthrow DataProcessingError.create(\n\t\t\t\t\"Forked Container Error! Matching batchIds but mismatched clientId\",\n\t\t\t\t\"PendingStateManager.processInboundMessages\",\n\t\t\t\tinbound.batchStart.keyMessage,\n\t\t\t);\n\t\t}\n\n\t\t// No localOpMetadata for remote messages\n\t\tconst messages = inbound.type === \"fullBatch\" ? inbound.messages : [inbound.nextMessage];\n\t\treturn messages.map((message) => ({ message }));\n\t}\n\n\t/**\n\t * Processes the incoming message(s) from the server that were submitted by this client.\n\t * It verifies that messages are received in the right order and that any batch information is correct.\n\t * @param inbound - The inbound message(s) (originating from this client) to correlate with the pending local state\n\t * @throws DataProcessingError if the pending message content doesn't match the incoming message content for any message here\n\t * @returns The inbound messages with localOpMetadata \"zipped\" in.\n\t */\n\tprivate processPendingLocalMessages(inbound: InboundMessageResult): {\n\t\tmessage: InboundSequencedContainerRuntimeMessage;\n\t\tlocalOpMetadata: unknown;\n\t}[] {\n\t\tif (\"batchStart\" in inbound) {\n\t\t\tthis.onLocalBatchBegin(inbound.batchStart, inbound.length);\n\t\t}\n\n\t\t// Empty batch\n\t\tif (inbound.length === 0) {\n\t\t\tconst localOpMetadata = this.processNextPendingMessage(\n\t\t\t\tinbound.batchStart.keyMessage.sequenceNumber,\n\t\t\t);\n\t\t\tassert(\n\t\t\t\tasEmptyBatchLocalOpMetadata(localOpMetadata)?.emptyBatch === true,\n\t\t\t\t0xa20 /* Expected empty batch marker */,\n\t\t\t);\n\t\t\treturn [];\n\t\t}\n\n\t\tconst messages = inbound.type === \"fullBatch\" ? inbound.messages : [inbound.nextMessage];\n\n\t\treturn messages.map((message) => ({\n\t\t\tmessage,\n\t\t\tlocalOpMetadata: this.processNextPendingMessage(message.sequenceNumber, message),\n\t\t}));\n\t}\n\n\t/**\n\t * Processes the pending local copy of message that's been ack'd by the server.\n\t * @param sequenceNumber - The sequenceNumber from the server corresponding to the next pending message.\n\t * @param message - [optional] The entire incoming message, for comparing contents with the pending message for extra validation.\n\t * @throws DataProcessingError if the pending message content doesn't match the incoming message content.\n\t * @returns - The localOpMetadata of the next pending message, to be sent to whoever submitted the original message.\n\t */\n\tprivate processNextPendingMessage(\n\t\tsequenceNumber: number,\n\t\tmessage?: InboundSequencedContainerRuntimeMessage,\n\t): unknown {\n\t\tconst pendingMessage = this.pendingMessages.peekFront();\n\t\tassert(\n\t\t\tpendingMessage !== undefined,\n\t\t\t0x169 /* \"No pending message found for this remote message\" */,\n\t\t);\n\n\t\tpendingMessage.sequenceNumber = sequenceNumber;\n\t\tthis.savedOps.push(toSerializableForm(pendingMessage));\n\n\t\tthis.pendingMessages.shift();\n\n\t\t// message is undefined in the Empty Batch case,\n\t\t// because we don't have an incoming message to compare and pendingMessage is just a placeholder anyway.\n\t\tif (message !== undefined) {\n\t\t\tconst messageContent = buildPendingMessageContent(message);\n\n\t\t\t// Stringified content should match\n\t\t\t// If it doesn't, collect as much info about the difference as possible (privacy-wise) and log it\n\t\t\tif (pendingMessage.content !== messageContent) {\n\t\t\t\tconst [pendingLength, incomingLength] = [\n\t\t\t\t\tpendingMessage.content.length,\n\t\t\t\t\tmessageContent.length,\n\t\t\t\t];\n\t\t\t\tconst [mismatchStartIndex, pendingChar, incomingChar] = findFirstCharacterMismatched(\n\t\t\t\t\tpendingMessage.content,\n\t\t\t\t\tmessageContent,\n\t\t\t\t);\n\n\t\t\t\tconst pendingContentObj = JSON.parse(\n\t\t\t\t\tpendingMessage.content,\n\t\t\t\t) as LocalContainerRuntimeMessage;\n\t\t\t\tconst incomingContentObj = JSON.parse(\n\t\t\t\t\tmessageContent,\n\t\t\t\t) as InboundContainerRuntimeMessage;\n\n\t\t\t\t// Compare inner contents object, since that both should be { type, contents }\n\t\t\t\tconst contentsMatch =\n\t\t\t\t\tpendingContentObj.contents === incomingContentObj.contents ||\n\t\t\t\t\t(pendingContentObj.contents !== undefined &&\n\t\t\t\t\t\tincomingContentObj.contents !== undefined &&\n\t\t\t\t\t\tJSON.stringify(pendingContentObj.contents) ===\n\t\t\t\t\t\t\tJSON.stringify(incomingContentObj.contents));\n\n\t\t\t\tthis.logger.sendErrorEvent({\n\t\t\t\t\teventName: \"unexpectedAckReceived\",\n\t\t\t\t\tdetails: {\n\t\t\t\t\t\tpendingContentScrubbed: scrubAndStringify(pendingContentObj),\n\t\t\t\t\t\tincomingContentScrubbed: scrubAndStringify(incomingContentObj),\n\t\t\t\t\t\tcontentsMatch,\n\t\t\t\t\t\tpendingLength,\n\t\t\t\t\t\tincomingLength,\n\t\t\t\t\t\tmismatchStartIndex,\n\t\t\t\t\t\tpendingChar,\n\t\t\t\t\t\tincomingChar,\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\tthrow DataProcessingError.create(\n\t\t\t\t\t\"pending local message content mismatch\",\n\t\t\t\t\t\"unexpectedAckReceived\",\n\t\t\t\t\tmessage,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn pendingMessage.localOpMetadata;\n\t}\n\n\t/**\n\t * Check if the incoming batch matches the batch info for the next pending message.\n\t */\n\tprivate onLocalBatchBegin(batchStart: BatchStartInfo, batchLength?: number): void {\n\t\t// Get the next message from the pending queue. Verify a message exists.\n\t\tconst pendingMessage = this.pendingMessages.peekFront();\n\t\tassert(\n\t\t\tpendingMessage !== undefined,\n\t\t\t0xa21 /* No pending message found as we start processing this remote batch */,\n\t\t);\n\t\tassert(!pendingMessage.batchInfo.staged, 0xb85 /* Can't get an ack from a staged batch */);\n\n\t\t// If this batch became empty on resubmit, batch.messages will be empty (but keyMessage is always set)\n\t\t// and the next pending message should be an empty batch marker.\n\t\t// More Info: We must submit empty batches and track them in case a different fork\n\t\t// of this container also submitted the same batch (and it may not be empty for that fork).\n\t\tconst firstMessage = batchStart.keyMessage;\n\t\t// -1 length is for back compat, undefined length means we actually don't know it\n\t\tconst skipLengthCheck =\n\t\t\tpendingMessage.batchInfo.length === -1 || batchLength === undefined;\n\t\tconst expectedPendingBatchLength =\n\t\t\tbatchLength === 0\n\t\t\t\t? 1 // For an empty batch, expect a singleton array with the empty batch marker\n\t\t\t\t: batchLength; // Otherwise, the lengths should actually match\n\n\t\t// Note: We don't need to use getEffectiveBatchId here, just check the explicit stamped batchID\n\t\t// That logic is needed only when comparing across potential container forks.\n\t\t// Furthermore, we also are comparing the batch IDs constituent data - clientId (it's local) and batchStartCsn.\n\t\tconst pendingBatchId = asBatchMetadata(pendingMessage.opMetadata)?.batchId;\n\t\tconst inboundBatchId = batchStart.batchId;\n\n\t\t// We expect the incoming batch to be of the same length, starting at the same clientSequenceNumber,\n\t\t// as the batch we originally submitted. The batchIds should match as well, if set (or neither should be set)\n\t\t// We have another later check to compare the message contents, which we'd expect to fail if this check does,\n\t\t// so we don't throw here, merely log. In a later release this check may replace that one since it's cheaper.\n\t\tif (\n\t\t\tpendingMessage.batchInfo.batchStartCsn !== batchStart.batchStartCsn ||\n\t\t\t(!skipLengthCheck && pendingMessage.batchInfo.length !== expectedPendingBatchLength) ||\n\t\t\tpendingBatchId !== inboundBatchId\n\t\t) {\n\t\t\tthis.logger?.sendErrorEvent({\n\t\t\t\teventName: \"BatchInfoMismatch\",\n\t\t\t\tdetails: {\n\t\t\t\t\tpendingBatchCsn: pendingMessage.batchInfo.batchStartCsn,\n\t\t\t\t\tbatchStartCsn: batchStart.batchStartCsn,\n\t\t\t\t\tpendingBatchLength: pendingMessage.batchInfo.length,\n\t\t\t\t\texpectedPendingBatchLength,\n\t\t\t\t\tbatchLength,\n\t\t\t\t\tpendingBatchId,\n\t\t\t\t\tinboundBatchId,\n\t\t\t\t\tpendingMessageBatchMetadata: asBatchMetadata(pendingMessage.opMetadata)?.batch,\n\t\t\t\t\tmessageBatchMetadata: asBatchMetadata(firstMessage?.metadata)?.batch,\n\t\t\t\t},\n\t\t\t\tmessageDetails: firstMessage && extractSafePropertiesFromMessage(firstMessage),\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Called when the Container's connection state changes. If the Container gets connected, it replays all the pending\n\t * states in its queue. This includes triggering resubmission of unacked ops.\n\t * ! Note: successfully resubmitting an op that has been successfully sequenced is not possible due to checks in the ConnectionStateHandler (Loader layer)\n\t */\n\tpublic replayPendingStates(optionsParam?: ReplayPendingStateOptions): void {\n\t\tconst options = { ...defaultReplayPendingStatesOptions, ...optionsParam };\n\t\tconst { onlyStagedBatches, squash } = options;\n\t\tassert(\n\t\t\tthis.stateHandler.connected() || onlyStagedBatches === true,\n\t\t\t0x172 /* \"The connection state is not consistent with the runtime\" */,\n\t\t);\n\n\t\t// Staged batches have not yet been submitted so check doesn't apply\n\t\tif (!onlyStagedBatches) {\n\t\t\t// This assert suggests we are about to send same ops twice, which will result in data loss.\n\t\t\tassert(\n\t\t\t\tthis.clientIdFromLastReplay !== this.stateHandler.clientId(),\n\t\t\t\t0x173 /* \"replayPendingStates called twice for same clientId!\" */,\n\t\t\t);\n\t\t}\n\t\tthis.clientIdFromLastReplay = this.stateHandler.clientId();\n\n\t\tassert(\n\t\t\tthis.initialMessages.isEmpty(),\n\t\t\t0x174 /* \"initial states should be empty before replaying pending\" */,\n\t\t);\n\n\t\tconst initialPendingMessagesCount = this.pendingMessages.length;\n\t\tlet remainingPendingMessagesCount = this.pendingMessages.length;\n\n\t\tlet seenStagedBatch = false;\n\n\t\t// Process exactly `pendingMessagesCount` items in the queue as it represents the number of messages that were\n\t\t// pending when we connected. This is important because the `reSubmitFn` might add more items in the queue\n\t\t// which must not be replayed.\n\t\twhile (remainingPendingMessagesCount > 0) {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\tlet pendingMessage = this.pendingMessages.shift()!;\n\t\t\tremainingPendingMessagesCount--;\n\n\t\t\t// Re-queue pre-staging messages if we are only processing staged batches\n\t\t\tif (onlyStagedBatches) {\n\t\t\t\tif (!pendingMessage.batchInfo.staged) {\n\t\t\t\t\tassert(!seenStagedBatch, 0xb86 /* Staged batch was followed by non-staged batch */);\n\t\t\t\t\tthis.pendingMessages.push(pendingMessage);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tseenStagedBatch = true;\n\t\t\t\tpendingMessage.batchInfo.staged = false; // Clear staged flag so we can submit\n\t\t\t}\n\n\t\t\tconst batchMetadataFlag = asBatchMetadata(pendingMessage.opMetadata)?.batch;\n\t\t\tassert(batchMetadataFlag !== false, 0x41b /* We cannot process batches in chunks */);\n\n\t\t\t// The next message starts a batch (possibly single-message), and we'll need its batchId.\n\t\t\tconst batchId = getEffectiveBatchId(pendingMessage);\n\n\t\t\tconst staged = pendingMessage.batchInfo.staged;\n\n\t\t\tif (asEmptyBatchLocalOpMetadata(pendingMessage.localOpMetadata)?.emptyBatch === true) {\n\t\t\t\t// Resubmit no messages, with the batchId. Will result in another empty batch marker.\n\t\t\t\tthis.stateHandler.reSubmitBatch([], { batchId, staged, squash });\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tassert(\n\t\t\t\tpendingMessage.runtimeOp !== undefined,\n\t\t\t\t0xb87 /* viableOp is only undefined for empty batches */,\n\t\t\t);\n\n\t\t\t/**\n\t\t\t * We must preserve the distinct batches on resubmit.\n\t\t\t * Note: It is not possible for the PendingStateManager to receive a partially acked batch. It will\n\t\t\t * either receive the whole batch ack or nothing at all. See {@link InboundBatchAggregator} for how this works.\n\t\t\t */\n\t\t\tif (batchMetadataFlag === undefined) {\n\t\t\t\t// Single-message batch\n\n\t\t\t\tthis.stateHandler.reSubmitBatch(\n\t\t\t\t\t[\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\truntimeOp: pendingMessage.runtimeOp,\n\t\t\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t\t{ batchId, staged, squash },\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// else: batchMetadataFlag === true (It's a typical multi-message batch)\n\n\t\t\tassert(\n\t\t\t\tremainingPendingMessagesCount > 0,\n\t\t\t\t0x554 /* Last pending message cannot be a batch begin */,\n\t\t\t);\n\n\t\t\tconst batch: PendingMessageResubmitData[] = [];\n\n\t\t\t// check is >= because batch end may be last pending message\n\t\t\twhile (remainingPendingMessagesCount >= 0) {\n\t\t\t\tassert(\n\t\t\t\t\tpendingMessage.runtimeOp !== undefined,\n\t\t\t\t\t0xb88 /* viableOp is only undefined for empty batches */,\n\t\t\t\t);\n\t\t\t\tbatch.push({\n\t\t\t\t\truntimeOp: pendingMessage.runtimeOp,\n\t\t\t\t\tlocalOpMetadata: pendingMessage.localOpMetadata,\n\t\t\t\t\topMetadata: pendingMessage.opMetadata,\n\t\t\t\t});\n\n\t\t\t\t// End of the batch\n\t\t\t\tif (pendingMessage.opMetadata?.batch === false) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tassert(remainingPendingMessagesCount > 0, 0x555 /* No batch end found */);\n\n\t\t\t\t// eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n\t\t\t\tpendingMessage = this.pendingMessages.shift()!;\n\t\t\t\tremainingPendingMessagesCount--;\n\t\t\t\tassert(\n\t\t\t\t\tpendingMessage.opMetadata?.batch !== true,\n\t\t\t\t\t0x556 /* Batch start needs a corresponding batch end */,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.stateHandler.reSubmitBatch(batch, { batchId, staged, squash });\n\t\t}\n\n\t\t// pending ops should no longer depend on previous sequenced local ops after resubmit\n\t\tthis.savedOps = [];\n\n\t\t// We replayPendingStates on read connections too - we expect these to get nack'd though, and to then reconnect\n\t\t// on a write connection and replay again. This filters out the replay that happens on the read connection so\n\t\t// we only see the replays on write connections (that have a chance to go through).\n\t\tif (this.stateHandler.isActiveConnection()) {\n\t\t\tthis.logger?.sendTelemetryEvent({\n\t\t\t\teventName: \"PendingStatesReplayed\",\n\t\t\t\tcount: initialPendingMessagesCount,\n\t\t\t\tclientId: this.stateHandler.clientId(),\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Pops all staged batches, invoking the callback on each one in order (LIFO)\n\t */\n\tpublic popStagedBatches(callback: (stagedMessage: IPendingMessage) => void): void {\n\t\twhile (!this.pendingMessages.isEmpty()) {\n\t\t\tconst stagedMessage = this.pendingMessages.peekBack();\n\t\t\tif (stagedMessage?.batchInfo.staged === true) {\n\t\t\t\tcallback(stagedMessage);\n\t\t\t\tthis.pendingMessages.pop();\n\t\t\t} else {\n\t\t\t\tbreak; // no more staged messages\n\t\t\t}\n\t\t}\n\t\tassert(\n\t\t\tthis.pendingMessages.toArray().every((m) => m.batchInfo.staged !== true),\n\t\t\t0xb89 /* Shouldn't be any more staged messages */,\n\t\t);\n\t}\n}\n\n/**\n * For back-compat if trying to apply stashed ops that pre-date batchInfo\n */\nfunction patchbatchInfo(\n\tmessage: IPendingMessageFromStash,\n): asserts message is IPendingMessage {\n\tconst batchInfo: IPendingMessageFromStash[\"batchInfo\"] = message.batchInfo;\n\tif (batchInfo === undefined) {\n\t\t// Using uuid guarantees uniqueness, retaining existing behavior\n\t\tmessage.batchInfo = { clientId: uuid(), batchStartCsn: -1, length: -1, staged: false };\n\t}\n}\n"]}
@@ -2,9 +2,8 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
- import { TypedEventEmitter } from "@fluid-internal/client-utils";
6
5
  import { IContainerRuntime, IContainerRuntimeEvents } from "@fluidframework/container-runtime-definitions/internal";
7
- import type { IEvent, IEventProvider, IFluidHandleContext, IFluidHandleInternal, IFluidHandleInternalPayloadPending } from "@fluidframework/core-interfaces/internal";
6
+ import type { IEventProvider, IFluidHandleContext, IFluidHandleInternal, IFluidHandleInternalPayloadPending, ILocalFluidHandle, ILocalFluidHandleEvents, Listenable, PayloadState } from "@fluidframework/core-interfaces/internal";
8
7
  import { IDocumentStorageService, ICreateBlobResponse } from "@fluidframework/driver-definitions/internal";
9
8
  import { IGarbageCollectionData, ISummaryTreeWithStats, ITelemetryContext, type ISequencedMessageEnvelope } from "@fluidframework/runtime-definitions/internal";
10
9
  import { FluidHandleBase } from "@fluidframework/runtime-utils/internal";
@@ -16,7 +15,7 @@ import { type IBlobManagerLoadInfo } from "./blobManagerSnapSum.js";
16
15
  * DataObject.request() recognizes requests in the form of `/blobs/<id>`
17
16
  * and loads blob.
18
17
  */
19
- export declare class BlobHandle extends FluidHandleBase<ArrayBufferLike> implements IFluidHandleInternalPayloadPending<ArrayBufferLike> {
18
+ export declare class BlobHandle extends FluidHandleBase<ArrayBufferLike> implements ILocalFluidHandle<ArrayBufferLike>, IFluidHandleInternalPayloadPending<ArrayBufferLike> {
20
19
  readonly path: string;
21
20
  readonly routeContext: IFluidHandleContext;
22
21
  get: () => Promise<ArrayBufferLike>;
@@ -24,12 +23,27 @@ export declare class BlobHandle extends FluidHandleBase<ArrayBufferLike> impleme
24
23
  private readonly onAttachGraph?;
25
24
  private attached;
26
25
  get isAttached(): boolean;
26
+ private _events;
27
+ get events(): Listenable<ILocalFluidHandleEvents>;
28
+ private _state;
29
+ get payloadState(): PayloadState;
30
+ /**
31
+ * The error property starts undefined, signalling that there has been no error yet.
32
+ * If an error occurs, the property will contain the error.
33
+ */
34
+ private _payloadShareError;
35
+ get payloadShareError(): unknown;
27
36
  readonly absolutePath: string;
28
37
  constructor(path: string, routeContext: IFluidHandleContext, get: () => Promise<ArrayBufferLike>, payloadPending: boolean, onAttachGraph?: (() => void) | undefined);
38
+ readonly notifyShared: () => void;
39
+ readonly notifyFailed: (error: unknown) => void;
29
40
  attachGraph(): void;
41
+ /**
42
+ * @deprecated No replacement provided. Arbitrary handles may not serve as a bind source.
43
+ */
30
44
  bind(handle: IFluidHandleInternal): void;
31
45
  }
32
- export type IBlobManagerRuntime = Pick<IContainerRuntime, "attachState" | "connected" | "baseLogger" | "clientDetails" | "disposed"> & TypedEventEmitter<IContainerRuntimeEvents>;
46
+ export type IBlobManagerRuntime = Pick<IContainerRuntime, "attachState" | "connected" | "baseLogger" | "clientDetails" | "disposed"> & IEventProvider<IContainerRuntimeEvents>;
33
47
  export interface IPendingBlobs {
34
48
  [localId: string]: {
35
49
  blob: string;
@@ -39,14 +53,14 @@ export interface IPendingBlobs {
39
53
  acked?: boolean;
40
54
  };
41
55
  }
42
- export interface IBlobManagerEvents extends IEvent {
43
- (event: "noPendingBlobs", listener: () => void): any;
56
+ export interface IBlobManagerEvents {
57
+ noPendingBlobs: () => void;
44
58
  }
45
59
  export declare const blobManagerBasePath: "_blobs";
46
60
  export declare class BlobManager {
47
61
  private readonly mc;
48
62
  private readonly publicEvents;
49
- get events(): IEventProvider<IBlobManagerEvents>;
63
+ get events(): Listenable<IBlobManagerEvents>;
50
64
  private readonly internalEvents;
51
65
  /**
52
66
  * Map of local IDs to storage IDs. Contains identity entries (storageId → storageId) for storage IDs. All requested IDs should
@@ -76,6 +90,7 @@ export declare class BlobManager {
76
90
  private readonly localBlobIdGenerator;
77
91
  private readonly pendingStashedBlobs;
78
92
  readonly stashedBlobsUploadP: Promise<(void | ICreateBlobResponse)[]>;
93
+ private readonly createBlobPayloadPending;
79
94
  constructor(props: {
80
95
  readonly routeContext: IFluidHandleContext;
81
96
  blobManagerLoadInfo: IBlobManagerLoadInfo;
@@ -103,10 +118,18 @@ export declare class BlobManager {
103
118
  private createAbortError;
104
119
  hasPendingStashedUploads(): boolean;
105
120
  hasBlob(blobId: string): boolean;
121
+ /**
122
+ * Retrieve the blob with the given local blob id.
123
+ * @param blobId - The local blob id. Likely coming from a handle.
124
+ * @param payloadPending - Whether we suspect the payload may be pending and not available yet.
125
+ * @returns A promise which resolves to the blob contents
126
+ */
106
127
  getBlob(blobId: string, payloadPending: boolean): Promise<ArrayBufferLike>;
107
128
  private getBlobHandle;
108
129
  private createBlobDetached;
109
- createBlob(blob: ArrayBufferLike, signal?: AbortSignal): Promise<IFluidHandleInternal<ArrayBufferLike>>;
130
+ createBlob(blob: ArrayBufferLike, signal?: AbortSignal): Promise<IFluidHandleInternalPayloadPending<ArrayBufferLike>>;
131
+ private createBlobLegacy;
132
+ private createBlobWithPayloadPending;
110
133
  private uploadBlob;
111
134
  /**
112
135
  * Set up a mapping in the redirect table from fromId to toId. Also, notify the runtime that a reference is added
@@ -1 +1 @@
1
- {"version":3,"file":"blobManager.d.ts","sourceRoot":"","sources":["../../src/blobManager/blobManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"blobManager.d.ts","sourceRoot":"","sources":["../../src/blobManager/blobManager.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
- import { TypedEventEmitter, bufferToString, stringToBuffer, } from "@fluid-internal/client-utils";
5
+ import { bufferToString, createEmitter, stringToBuffer } from "@fluid-internal/client-utils";
6
6
  import { AttachState } from "@fluidframework/container-definitions";
7
7
  import { assert, Deferred } from "@fluidframework/core-utils/internal";
8
8
  import { canRetryOnError, runWithRetry } from "@fluidframework/driver-utils/internal";
@@ -21,6 +21,15 @@ export class BlobHandle extends FluidHandleBase {
21
21
  get isAttached() {
22
22
  return this.routeContext.isAttached && this.attached;
23
23
  }
24
+ get events() {
25
+ return (this._events ??= createEmitter());
26
+ }
27
+ get payloadState() {
28
+ return this._state;
29
+ }
30
+ get payloadShareError() {
31
+ return this._payloadShareError;
32
+ }
24
33
  constructor(path, routeContext, get, payloadPending, onAttachGraph) {
25
34
  super();
26
35
  this.path = path;
@@ -29,6 +38,15 @@ export class BlobHandle extends FluidHandleBase {
29
38
  this.payloadPending = payloadPending;
30
39
  this.onAttachGraph = onAttachGraph;
31
40
  this.attached = false;
41
+ this._state = "pending";
42
+ this.notifyShared = () => {
43
+ this._state = "shared";
44
+ this._events?.emit("payloadShared");
45
+ };
46
+ this.notifyFailed = (error) => {
47
+ this._payloadShareError = error;
48
+ this._events?.emit("payloadShareFailed", error);
49
+ };
32
50
  this.absolutePath = generateHandleContextPath(path, this.routeContext);
33
51
  }
34
52
  attachGraph() {
@@ -37,6 +55,10 @@ export class BlobHandle extends FluidHandleBase {
37
55
  this.onAttachGraph?.();
38
56
  }
39
57
  }
58
+ // eslint-disable-next-line jsdoc/require-description
59
+ /**
60
+ * @deprecated No replacement provided. Arbitrary handles may not serve as a bind source.
61
+ */
40
62
  bind(handle) {
41
63
  throw new Error("Cannot bind to blob handle");
42
64
  }
@@ -53,8 +75,8 @@ export class BlobManager {
53
75
  return this.publicEvents;
54
76
  }
55
77
  constructor(props) {
56
- this.publicEvents = new TypedEventEmitter();
57
- this.internalEvents = new TypedEventEmitter();
78
+ this.publicEvents = createEmitter();
79
+ this.internalEvents = createEmitter();
58
80
  /**
59
81
  * Blobs which we have not yet seen a BlobAttach op round-trip and not yet attached to a DDS.
60
82
  */
@@ -67,13 +89,14 @@ export class BlobManager {
67
89
  this.opsInFlight = new Map();
68
90
  this.stopAttaching = false;
69
91
  this.pendingStashedBlobs = new Map();
70
- const { routeContext, blobManagerLoadInfo, storage, sendBlobAttachOp, blobRequested, isBlobDeleted, runtime, stashedBlobs, localBlobIdGenerator, } = props;
92
+ const { routeContext, blobManagerLoadInfo, storage, sendBlobAttachOp, blobRequested, isBlobDeleted, runtime, stashedBlobs, localBlobIdGenerator, createBlobPayloadPending, } = props;
71
93
  this.routeContext = routeContext;
72
94
  this.storage = storage;
73
95
  this.blobRequested = blobRequested;
74
96
  this.isBlobDeleted = isBlobDeleted;
75
97
  this.runtime = runtime;
76
98
  this.localBlobIdGenerator = localBlobIdGenerator ?? uuid;
99
+ this.createBlobPayloadPending = createBlobPayloadPending;
77
100
  this.mc = createChildMonitoringContext({
78
101
  logger: this.runtime.baseLogger,
79
102
  namespace: "BlobManager",
@@ -165,6 +188,12 @@ export class BlobManager {
165
188
  hasBlob(blobId) {
166
189
  return this.redirectTable.get(blobId) !== undefined;
167
190
  }
191
+ /**
192
+ * Retrieve the blob with the given local blob id.
193
+ * @param blobId - The local blob id. Likely coming from a handle.
194
+ * @param payloadPending - Whether we suspect the payload may be pending and not available yet.
195
+ * @returns A promise which resolves to the blob contents
196
+ */
168
197
  async getBlob(blobId, payloadPending) {
169
198
  // Verify that the blob is not deleted, i.e., it has not been garbage collected. If it is, this will throw
170
199
  // an error, failing the call.
@@ -187,7 +216,9 @@ export class BlobManager {
187
216
  else {
188
217
  const attachedStorageId = this.redirectTable.get(blobId);
189
218
  if (!payloadPending) {
190
- assert(!!attachedStorageId, 0x11f /* "requesting unknown blobs" */);
219
+ // Only blob handles explicitly marked with pending payload are permitted to exist without
220
+ // yet knowing their storage id. Otherwise they must already be associated with a storage id.
221
+ assert(attachedStorageId !== undefined, 0x11f /* "requesting unknown blobs" */);
191
222
  }
192
223
  // If we didn't find it in the redirectTable, assume the attach op is coming eventually and wait.
193
224
  // We do this even if the local client doesn't have the blob payloadPending flag enabled, in case a
@@ -218,11 +249,11 @@ export class BlobManager {
218
249
  getBlobHandle(localId) {
219
250
  assert(this.redirectTable.has(localId) || this.pendingBlobs.has(localId), 0x384 /* requesting handle for unknown blob */);
220
251
  const pending = this.pendingBlobs.get(localId);
221
- // Create a callback function for once the blob has been attached
252
+ // Create a callback function for once the handle has been attached
222
253
  const callback = pending
223
254
  ? () => {
224
255
  pending.attached = true;
225
- // Notify listeners (e.g. serialization process) that blob has been attached
256
+ // Notify listeners (e.g. serialization process) that handle has been attached
226
257
  this.internalEvents.emit("handleAttached", pending);
227
258
  this.deletePendingBlobMaybe(localId);
228
259
  }
@@ -247,6 +278,11 @@ export class BlobManager {
247
278
  await new Promise((resolve) => this.runtime.once("attached", resolve));
248
279
  }
249
280
  assert(this.runtime.attachState === AttachState.Attached, 0x385 /* For clarity and paranoid defense against adding future attachment states */);
281
+ return this.createBlobPayloadPending
282
+ ? this.createBlobWithPayloadPending(blob)
283
+ : this.createBlobLegacy(blob, signal);
284
+ }
285
+ async createBlobLegacy(blob, signal) {
250
286
  if (signal?.aborted) {
251
287
  throw this.createAbortError();
252
288
  }
@@ -273,6 +309,36 @@ export class BlobManager {
273
309
  signal?.removeEventListener("abort", abortListener);
274
310
  });
275
311
  }
312
+ createBlobWithPayloadPending(blob) {
313
+ const localId = this.localBlobIdGenerator();
314
+ const blobHandle = new BlobHandle(getGCNodePathFromBlobId(localId), this.routeContext, async () => blob, true, // payloadPending
315
+ () => {
316
+ const pendingEntry = {
317
+ blob,
318
+ handleP: new Deferred(),
319
+ uploadP: this.uploadBlob(localId, blob),
320
+ attached: true,
321
+ acked: false,
322
+ opsent: false,
323
+ };
324
+ this.pendingBlobs.set(localId, pendingEntry);
325
+ });
326
+ const onProcessedBlobAttach = (_localId, _storageId) => {
327
+ if (_localId === localId) {
328
+ this.internalEvents.off("processedBlobAttach", onProcessedBlobAttach);
329
+ blobHandle.notifyShared();
330
+ }
331
+ };
332
+ this.internalEvents.on("processedBlobAttach", onProcessedBlobAttach);
333
+ const onUploadFailed = (_localId, error) => {
334
+ if (_localId === localId) {
335
+ this.internalEvents.off("uploadFailed", onUploadFailed);
336
+ blobHandle.notifyFailed(error);
337
+ }
338
+ };
339
+ this.internalEvents.on("uploadFailed", onUploadFailed);
340
+ return blobHandle;
341
+ }
276
342
  async uploadBlob(localId, blob) {
277
343
  return runWithRetry(async () => {
278
344
  try {
@@ -301,6 +367,7 @@ export class BlobManager {
301
367
  // the promise but not throw any error outside.
302
368
  this.pendingBlobs.get(localId)?.handleP.reject(error);
303
369
  this.deletePendingBlob(localId);
370
+ this.internalEvents.emit("uploadFailed", localId, error);
304
371
  });
305
372
  }
306
373
  /**
@@ -362,7 +429,9 @@ export class BlobManager {
362
429
  // an existing blob, we don't have to wait for the op to be ack'd since this step has already
363
430
  // happened before and so, the server won't delete it.
364
431
  this.setRedirection(localId, response.id);
365
- entry.handleP.resolve(this.getBlobHandle(localId));
432
+ const blobHandle = this.getBlobHandle(localId);
433
+ blobHandle.notifyShared();
434
+ entry.handleP.resolve(blobHandle);
366
435
  this.deletePendingBlobMaybe(localId);
367
436
  }
368
437
  else {
@@ -417,8 +486,8 @@ export class BlobManager {
417
486
  }
418
487
  // set identity (id -> id) entry
419
488
  this.setRedirection(blobId, blobId);
489
+ assert(localId !== undefined, 0x50e /* local ID not present in blob attach message */);
420
490
  if (local) {
421
- assert(localId !== undefined, 0x50e /* local ID not present in blob attach message */);
422
491
  const waitingBlobs = this.opsInFlight.get(blobId);
423
492
  if (waitingBlobs !== undefined) {
424
493
  // For each op corresponding to this storage ID that we are waiting for, resolve the pending blob.
@@ -429,7 +498,9 @@ export class BlobManager {
429
498
  assert(entry !== undefined, 0x38f /* local online BlobAttach op with no pending blob entry */);
430
499
  this.setRedirection(pendingLocalId, blobId);
431
500
  entry.acked = true;
432
- entry.handleP.resolve(this.getBlobHandle(pendingLocalId));
501
+ const blobHandle = this.getBlobHandle(pendingLocalId);
502
+ blobHandle.notifyShared();
503
+ entry.handleP.resolve(blobHandle);
433
504
  this.deletePendingBlobMaybe(pendingLocalId);
434
505
  }
435
506
  this.opsInFlight.delete(blobId);
@@ -437,7 +508,9 @@ export class BlobManager {
437
508
  const localEntry = this.pendingBlobs.get(localId);
438
509
  if (localEntry) {
439
510
  localEntry.acked = true;
440
- localEntry.handleP.resolve(this.getBlobHandle(localId));
511
+ const blobHandle = this.getBlobHandle(localId);
512
+ blobHandle.notifyShared();
513
+ localEntry.handleP.resolve(blobHandle);
441
514
  this.deletePendingBlobMaybe(localId);
442
515
  }
443
516
  }
@@ -579,7 +652,7 @@ export class BlobManager {
579
652
  const localBlobs = new Set();
580
653
  // This while is used to stash blobs created while attaching and getting blobs
581
654
  while (localBlobs.size < this.pendingBlobs.size) {
582
- const attachBlobsP = [];
655
+ const attachHandlesP = [];
583
656
  for (const [localId, entry] of this.pendingBlobs) {
584
657
  if (!localBlobs.has(entry)) {
585
658
  localBlobs.add(entry);
@@ -594,15 +667,15 @@ export class BlobManager {
594
667
  // original createBlob call) and let them attach the blob. This is a lie we told since the upload
595
668
  // hasn't finished yet, but it's fine since we will retry on rehydration.
596
669
  entry.handleP.resolve(this.getBlobHandle(localId));
597
- // Array of promises that will resolve when blobs get attached.
598
- attachBlobsP.push(new Promise((resolve, reject) => {
670
+ // Array of promises that will resolve when handles get attached.
671
+ attachHandlesP.push(new Promise((resolve, reject) => {
599
672
  stopBlobAttachingSignal?.addEventListener("abort", () => {
600
673
  this.stopAttaching = true;
601
674
  reject(new Error("Operation aborted"));
602
675
  }, { once: true });
603
- const onBlobAttached = (attachedEntry) => {
676
+ const onHandleAttached = (attachedEntry) => {
604
677
  if (attachedEntry === entry) {
605
- this.internalEvents.off("handleAttached", onBlobAttached);
678
+ this.internalEvents.off("handleAttached", onHandleAttached);
606
679
  resolve();
607
680
  }
608
681
  };
@@ -610,14 +683,14 @@ export class BlobManager {
610
683
  resolve();
611
684
  }
612
685
  else {
613
- this.internalEvents.on("handleAttached", onBlobAttached);
686
+ this.internalEvents.on("handleAttached", onHandleAttached);
614
687
  }
615
688
  }));
616
689
  }
617
690
  }
618
691
  // Wait for all blobs to be attached. This is important, otherwise serialized container
619
692
  // could send the blobAttach op without any op that references the blob, making it useless.
620
- await Promise.allSettled(attachBlobsP);
693
+ await Promise.allSettled(attachHandlesP);
621
694
  }
622
695
  for (const [localId, entry] of this.pendingBlobs) {
623
696
  if (stopBlobAttachingSignal?.aborted && !entry.attached) {