@tearleads/client-sdk 0.1.23 → 0.1.25

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 (109) hide show
  1. package/data/persistence/container-contents/containerContentsPersistenceTypes.d.ts +2 -0
  2. package/data/persistence/container-contents/containerContentsPersistenceTypes.js.map +1 -1
  3. package/data/persistence/container-contents/containerDeletionPersistence.js +3 -0
  4. package/data/persistence/container-contents/containerDeletionPersistence.js.map +1 -1
  5. package/data/persistence/container-contents/documentPurgePlacement.js +15 -0
  6. package/data/persistence/container-contents/documentPurgePlacement.js.map +1 -0
  7. package/data/persistence/documents/documentsPersistence.js +7 -0
  8. package/data/persistence/documents/documentsPersistence.js.map +1 -1
  9. package/data/persistence/documents/internal/documentDeletionPersistence.js +7 -0
  10. package/data/persistence/documents/internal/documentDeletionPersistence.js.map +1 -1
  11. package/data/persistence/documents/orphanBlobPersistence.d.ts +13 -0
  12. package/data/persistence/documents/orphanBlobPersistence.js +2 -0
  13. package/data/persistence/documents/orphanBlobPersistence.js.map +1 -0
  14. package/data/persistence/documents/types.d.ts +4 -1
  15. package/data/persistence/documents/types.js.map +1 -1
  16. package/data/persistence/principalCurrentAcknowledgementPersistence.d.ts +13 -2
  17. package/data/persistence/principalCurrentAcknowledgementPersistence.js +36 -20
  18. package/data/persistence/principalCurrentAcknowledgementPersistence.js.map +1 -1
  19. package/index.d.ts +2 -2
  20. package/index.js +1 -1
  21. package/index.js.map +1 -1
  22. package/internal/api-client/ApiClient.d.ts +6 -2
  23. package/internal/api-client/ApiClient.js +4 -5
  24. package/internal/api-client/ApiClient.js.map +1 -1
  25. package/internal/api-client/apiRequestRuntime.d.ts +1 -2
  26. package/internal/api-client/apiRequestRuntime.js +12 -15
  27. package/internal/api-client/apiRequestRuntime.js.map +1 -1
  28. package/internal/api-client/routes/containers/mutations.js +2 -2
  29. package/internal/api-client/routes/containers/mutations.js.map +1 -1
  30. package/internal/api-client/sessionRefresh.js +14 -6
  31. package/internal/api-client/sessionRefresh.js.map +1 -1
  32. package/internal/api-client/sessionRenewal.js +54 -0
  33. package/internal/api-client/sessionRenewal.js.map +1 -0
  34. package/internal/validators/operation/containerMutations.d.ts +3 -0
  35. package/internal/validators/operation/containerMutations.js +1 -0
  36. package/internal/validators/operation/containerMutations.js.map +1 -1
  37. package/package.json +1 -1
  38. package/stores/documents/documentStore/attachmentHydration.js +2 -2
  39. package/stores/documents/documentStore/attachmentHydration.js.map +1 -1
  40. package/stores/documents/documentStore/attachments.js +7 -5
  41. package/stores/documents/documentStore/attachments.js.map +1 -1
  42. package/stores/documents/documentStore/discard.js +3 -2
  43. package/stores/documents/documentStore/discard.js.map +1 -1
  44. package/stores/documents/documentStore/orphanMaintenance.js +3 -5
  45. package/stores/documents/documentStore/orphanMaintenance.js.map +1 -1
  46. package/stores/documents/documentStore/persistence.js +3 -4
  47. package/stores/documents/documentStore/persistence.js.map +1 -1
  48. package/stores/documents/documentStore/syncDetachedAttachments.js +3 -2
  49. package/stores/documents/documentStore/syncDetachedAttachments.js.map +1 -1
  50. package/stores/documents/documentStore/syncRequest.js +3 -4
  51. package/stores/documents/documentStore/syncRequest.js.map +1 -1
  52. package/workflows/container-contents/container-state/delete.js +2 -0
  53. package/workflows/container-contents/container-state/delete.js.map +1 -1
  54. package/workflows/container-contents/container-state/localPurgeScope.js +49 -0
  55. package/workflows/container-contents/container-state/localPurgeScope.js.map +1 -0
  56. package/workflows/container-contents/container-state/purgeTree.js +42 -7
  57. package/workflows/container-contents/container-state/purgeTree.js.map +1 -1
  58. package/workflows/container-contents/container-state/purgeTreeCollection.js +11 -0
  59. package/workflows/container-contents/container-state/purgeTreeCollection.js.map +1 -1
  60. package/workflows/container-contents/container-state/purgeTreeContainerDeletion.js +4 -1
  61. package/workflows/container-contents/container-state/purgeTreeContainerDeletion.js.map +1 -1
  62. package/workflows/container-contents/container-state/purgeTreeScope.js +158 -0
  63. package/workflows/container-contents/container-state/purgeTreeScope.js.map +1 -0
  64. package/workflows/container-contents/container-state/readLocalPurgeScope.js +18 -0
  65. package/workflows/container-contents/container-state/readLocalPurgeScope.js.map +1 -0
  66. package/workflows/container-contents/container-state/remote.js +1 -0
  67. package/workflows/container-contents/container-state/remote.js.map +1 -1
  68. package/workflows/container-contents/container-state/types.js.map +1 -1
  69. package/workflows/container-contents/documentLinks.d.ts +4 -0
  70. package/workflows/container-contents/documentLinks.js +2 -0
  71. package/workflows/container-contents/documentLinks.js.map +1 -1
  72. package/workflows/container-contents/documentPurge.d.ts +6 -1
  73. package/workflows/container-contents/documentPurge.js +12 -5
  74. package/workflows/container-contents/documentPurge.js.map +1 -1
  75. package/workflows/documents/linkSet.d.ts +1 -0
  76. package/workflows/documents/linkSet.js +2 -0
  77. package/workflows/documents/linkSet.js.map +1 -1
  78. package/workflows/documents/linkSetRemote.d.ts +2 -0
  79. package/workflows/documents/linkSetRemote.js +3 -0
  80. package/workflows/documents/linkSetRemote.js.map +1 -1
  81. package/workflows/documents/orphanBlobReclaimState.js +20 -0
  82. package/workflows/documents/orphanBlobReclaimState.js.map +1 -0
  83. package/workflows/documents/orphanBlobReclaims.d.ts +3 -2
  84. package/workflows/documents/orphanBlobReclaims.js +49 -47
  85. package/workflows/documents/orphanBlobReclaims.js.map +1 -1
  86. package/workflows/documents/purge.d.ts +3 -0
  87. package/workflows/documents/purge.js +7 -0
  88. package/workflows/documents/purge.js.map +1 -1
  89. package/workflows/documents/subtreePurgeScope.js +11 -0
  90. package/workflows/documents/subtreePurgeScope.js.map +1 -0
  91. package/workflows/organizations/acknowledgedPrincipalPredecessor.d.ts +39 -0
  92. package/workflows/organizations/acknowledgedPrincipalPredecessor.js +87 -0
  93. package/workflows/organizations/acknowledgedPrincipalPredecessor.js.map +1 -0
  94. package/workflows/organizations/currentOrganizationMutationRetention.js +13 -2
  95. package/workflows/organizations/currentOrganizationMutationRetention.js.map +1 -1
  96. package/workflows/organizations/index.d.ts +1 -0
  97. package/workflows/organizations/index.js +1 -0
  98. package/workflows/organizations/index.js.map +1 -1
  99. package/workflows/organizations/initialPrincipalCurrentPublication.js +3 -2
  100. package/workflows/organizations/initialPrincipalCurrentPublication.js.map +1 -1
  101. package/workflows/organizations/reconcileAcknowledgedPrincipalCurrent.js +63 -0
  102. package/workflows/organizations/reconcileAcknowledgedPrincipalCurrent.js.map +1 -0
  103. package/workflows/organizations/retainAcknowledgedPrincipalCurrents.d.ts +3 -0
  104. package/workflows/organizations/retainAcknowledgedPrincipalCurrents.js +33 -55
  105. package/workflows/organizations/retainAcknowledgedPrincipalCurrents.js.map +1 -1
  106. package/workflows/organizations/runtimeCurrentGroupMutation.js +13 -2
  107. package/workflows/organizations/runtimeCurrentGroupMutation.js.map +1 -1
  108. package/workflows/organizations/runtimeCurrentOrganizationMutation.js +2 -2
  109. package/workflows/organizations/runtimeCurrentOrganizationMutation.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"purgeTree.js","sourceRoot":"","sources":["purgeTree.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,2CAA2C,CAAC;AAGlF,OAAO,EAAE,yCAAyC,EAAE,MAAM,6EAA6E,CAAC;AACxI,OAAO,EAAE,wCAAwC,EAAE,MAAM,0DAA0D,CAAC;AAEpH,OAAO,EAAE,6BAA6B,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,kBAAkB,CAAC;AAI1B,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AA4DhE,+EAA+E;AAC/E,gFAAgF;AAChF,uEAAuE;AACvE,MAAM,UAAU,uBAAuB,CAAC,KAKvC;IACC,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;QACtB,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IACjC,CAAC;IACD,MAAM,eAAe,GAAG,KAAK,CAAC,kBAAkB,CAAC,IAAI,CACnD,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,mBAAmB,CAAC,GAAG,CAAC,WAAW,CAAC,CAC7D,CAAC;IACF,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,MAAM,kBAAkB,GAAG,KAAK,CAAC,IAAI,CACnC,IAAI,GAAG,CACL,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAC9C,KAAK,CAAC,mBAAmB,CAAC,GAAG,CAAC,WAAW,CAAC,CAC3C,CACF,CACF,CAAC;QACF,MAAM,mBAAmB,GACvB,KAAK,CAAC,oBAAoB;YAC1B,kBAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC,oBAAoB,CAAC;YACrD,CAAC,CAAC,KAAK,CAAC,oBAAoB;YAC5B,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;QAC5B,OAAO;YACL,IAAI,EAAE,OAAO;YACb,kBAAkB,EAAE,kBAAkB,CAAC,MAAM,CAC3C,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,KAAK,mBAAmB,CACrD;SACF,CAAC;IACJ,CAAC;IACD,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,YAAY,EAAE,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAC5D,KAAK,CAAC,mBAAmB,CAAC,GAAG,CAAC,WAAW,CAAC,CAC3C;KACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,oBAAoB,CAAC,KAGnC;IACC,MAAM,SAAS,GAAG,MAAM,wCAAwC,CAC9D,KAAK,CAAC,OAAO,EACb;QACE,YAAY,EAAE,CAAC,GAAG,KAAK,CAAC,mBAAmB,CAAC;QAC5C,WAAW,EAAE,EAAE;KAChB,CACF,CAAC;IACF,MAAM,iBAAiB,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CACvD,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CACjD,CAAC;IACF,MAAM,8BAA8B,GAClC,MAAM,yCAAyC,CAAC,mCAAmC,CACjF,KAAK,CAAC,OAAO,EACb,iBAAiB,CAClB,CAAC;IAEJ,MAAM,KAAK,GAGN,EAAE,CAAC;IACR,MAAM,UAAU,GAAsB,EAAE,CAAC;IACzC,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAG7B,CAAC;IACJ,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,WAAW,GAAG,uBAAuB,CAAC;YAC1C,UAAU,EAAE,QAAQ,CAAC,UAAU,IAAI,IAAI;YACvC,kBAAkB,EAAE,QAAQ,CAAC,UAAU;gBACrC,CAAC,CAAC,CAAC,8BAA8B,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;gBACjE,CAAC,CAAC,EAAE;YACN,oBAAoB,EAAE,QAAQ,CAAC,WAAW;YAC1C,mBAAmB,EAAE,KAAK,CAAC,mBAAmB;SAC/C,CAAC,CAAC;QACH,IAAI,WAAW,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YACvC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;aAAM,IAAI,WAAW,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACxC,KAAK,CAAC,IAAI,CAAC;gBACT,QAAQ;gBACR,kBAAkB,EAAE,WAAW,CAAC,kBAAkB;aACnD,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;YAC/B,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE;gBACxC,QAAQ;gBACR,YAAY,EAAE,WAAW,CAAC,YAAY;aACvC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC;AACjD,CAAC;AAED,KAAK,UAAU,qBAAqB,CAAC,KAMpC;IACC,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC7C,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,gBAAmC,CAAC;IACxC,IAAI,CAAC;QACH,gBAAgB,GAAG,MAAM,KAAK,CAAC,+BAA+B,CAC5D,KAAK,CAAC,QAAQ,CACf,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,MAAM,WAAW,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QAC7C,MAAM,MAAM,GAAG,MAAM,6BAA6B,CAAC;YACjD,UAAU;YACV,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,EAAE;YACzB,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;YACxD,gBAAgB;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,iBAAiB,EAAE,WAAW;SAC/B,CAAC,CAAC;QACH,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAWD,SAAS,gCAAgC,CACvC,KAA8B;IAE9B,OAAO,CACL,KAAK,CAAC,kBAAkB,IAAI;QAC1B,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAC7B,CAAC,MAAM,2BAA2B,CAAC;YACjC,MAAM,EAAE,QAAQ,CAAC,EAAE;YACnB,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC,CAAC,KAAK,IAAI;QACd,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAC9B,OAAO,CACL,QAAQ,CAAC,UAAU;YACjB,CAAC,MAAM,4BAA4B,CAAC;gBAClC,UAAU,EAAE,QAAQ,CAAC,UAAU;gBAC/B,YAAY,EAAE,QAAQ,CAAC,YAAY,IAAI,qBAAqB;gBAC5D,MAAM,EAAE,QAAQ,CAAC,EAAE;gBACnB,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;gBACxD,OAAO,EAAE,KAAK,CAAC,OAAO;aACvB,CAAC,CAAC,CACN;QACH,MAAM,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,CACjC,qBAAqB,CAAC;YACpB,YAAY;YACZ,QAAQ;YACR,+BAA+B,EAC7B,KAAK,CAAC,+BAA+B;YACvC,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;YACxD,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC;KACL,CACF,CAAC;AACJ,CAAC;AAMD,SAAS,iBAAiB,CAAC,KAG1B;IACC,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK,CAAC;AAC5E,CAAC;AAED,gFAAgF;AAChF,gFAAgF;AAChF,qEAAqE;AACrE,KAAK,UAAU,wBAAwB,CAAC,KAMvC;IACC,KAAK,MAAM,EACT,QAAQ,EACR,YAAY,GACb,IAAI,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,EAAE,CAAC;QAC1C,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC3B,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,kBAAkB,CAAC,MAAM,CACpD,QAAQ,EACR,YAAY,CACb,CAAC;QACF,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IACD,KAAK,MAAM,EAAE,QAAQ,EAAE,kBAAkB,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAChE,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC3B,CAAC;QACD,IACE,kBAAkB,CAAC,MAAM,GAAG,CAAC;YAC7B,CAAC,CAAC,MAAM,KAAK,CAAC,kBAAkB,CAAC,MAAM,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC,EACtE,CAAC;YACD,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YACxB,SAAS;QACX,CAAC;QACD,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,kBAAkB,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzE,CAAC;IACD,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QAC7C,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC3B,CAAC;QACD,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,kBAAkB,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxE,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED,6EAA6E;AAC7E,6EAA6E;AAC7E,kFAAkF;AAClF,8EAA8E;AAC9E,4EAA4E;AAC5E,iFAAiF;AACjF,kFAAkF;AAClF,4EAA4E;AAC5E,qCAAqC;AACrC,EAAE;AACF,uEAAuE;AACvE,iFAAiF;AACjF,iFAAiF;AACjF,4EAA4E;AAC5E,6EAA6E;AAC7E,gDAAgD;AAChD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAA8B;IAE9B,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,aAAa,GAAG,uBAAuB,CAC3C,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,eAAe,CACtB,CAAC;IACF,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,mBAAmB,GAAG,IAAI,GAAG,CACjC,aAAa,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC,CACnE,CAAC;IAEF,MAAM,IAAI,GAAG,MAAM,oBAAoB,CAAC;QACtC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO;QACpC,mBAAmB;KACpB,CAAC,CAAC;IACH,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,6EAA6E;IAC7E,6EAA6E;IAC7E,6BAA6B;IAC7B,MAAM,uBAAuB,GAAG,KAAK,CAAC,iBAAiB;QACrD,CAAC,CAAC,aAAa,CAAC,MAAM,CAClB,CAAC,cAAc,EAAE,EAAE,CACjB,cAAc,CAAC,SAAS,CAAC,EAAE,KAAK,KAAK,CAAC,eAAe,CACxD;QACH,CAAC,CAAC,aAAa,CAAC;IAElB,gFAAgF;IAChF,+EAA+E;IAC/E,6EAA6E;IAC7E,oBAAoB;IACpB,MAAM,UAAU,GACd,IAAI,CAAC,gBAAgB,CAAC,IAAI;QAC1B,IAAI,CAAC,KAAK,CAAC,MAAM;QACjB,IAAI,CAAC,UAAU,CAAC,MAAM;QACtB,uBAAuB,CAAC,MAAM,CAAC;IAEjC,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,KAAK,CAAC,UAAU,EAAE,CAAC,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC;IACF,MAAM,UAAU,GAAG,CAAC,EAAW,EAAE,EAAE;QACjC,IAAI,EAAE,EAAE,CAAC;YACP,cAAc,IAAI,CAAC,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,WAAW,IAAI,CAAC,CAAC;QACnB,CAAC;QACD,YAAY,EAAE,CAAC;IACjB,CAAC,CAAC;IACF,gFAAgF;IAChF,wCAAwC;IACxC,YAAY,EAAE,CAAC;IAEf,MAAM,QAAQ,GAAG,MAAM,wBAAwB,CAAC;QAC9C,kBAAkB,EAAE,gCAAgC,CAAC,KAAK,CAAC;QAC3D,IAAI;QACJ,UAAU;QACV,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,YAAY,EAAE,KAAK,CAAC,YAAY;KACjC,CAAC,CAAC;IAEH,IAAI,kBAAkB,GAAsB,EAAE,CAAC;IAC/C,IAAI,yBAAyB,GAAsB,EAAE,CAAC;IACtD,IAAI,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;IAC/B,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,MAAM,uBAAuB,CAAC;YAC7C,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,UAAU;YACV,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,aAAa,EAAE,uBAAuB;SACvC,CAAC,CAAC;QACH,kBAAkB,GAAG,QAAQ,CAAC,kBAAkB,CAAC;QACjD,yBAAyB,GAAG,QAAQ,CAAC,yBAAyB,CAAC;QAC/D,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;IAC7B,CAAC;IAED,OAAO;QACL,OAAO;QACP,cAAc;QACd,WAAW;QACX,kBAAkB;QAClB,yBAAyB;QACzB,UAAU;KACX,CAAC;AACJ,CAAC","sourcesContent":["import { DEFAULT_DOCUMENT_KIND } from \"../../../data/documents/documentConstants\";\nimport type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport { sqlDocumentContainerProjectionPersistence } from \"../../../data/persistence/containers/documentContainerProjectionPersistence\";\nimport { listDocumentsByContainerIdsOrDocumentIds } from \"../../../data/persistence/documents/documentsPersistence\";\nimport type { ContainerContentsPersistence } from \"../containerPersistence\";\nimport { unlinkRemoteContainerDocument } from \"../documentLinks\";\nimport {\n purgeLocalContainerDocument,\n purgeRemoteContainerDocument,\n} from \"../documentPurge\";\nimport type { ContainerState } from \"../remoteHydration\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\nimport type { PurgeProgress } from \"./purgeProgress\";\nimport { collectSubtreeLeafFirst } from \"./purgeTreeCollection\";\nimport { deleteSubtreeContainers } from \"./purgeTreeContainerDeletion\";\n\nexport { collectSubtreeLeafFirst } from \"./purgeTreeCollection\";\n\n// The store runtime satisfies the per-document purge/unlink workflows, so the\n// cascade reuses it verbatim.\ntype PurgeContainerTreeRuntime = ContainerContentsWorkflowRuntime;\n\ninterface PurgeContainerTreeInput {\n readonly containersById: ReadonlyMap<string, ContainerState>;\n readonly documentOperations?: SubtreeDocumentOperations | undefined;\n // When true, tear down everything UNDER the root (its documents plus every\n // descendant container) but never delete the root container itself. This is\n // how \"Empty Trash\" reuses the same engine: the Trash bin is a protected system\n // container that must survive while its whole contents are destroyed.\n readonly keepRootContainer?: boolean;\n readonly onProgress?: ((progress: PurgeProgress) => void) | undefined;\n readonly persistence: ContainerContentsPersistence;\n readonly prepareDocumentRotationSnapshot: (\n document: DocumentSummary,\n ) => Promise<Uint8Array | null>;\n readonly resolveProjectionUserKey: ProjectionUserKeyResolver;\n readonly rootContainerId: string;\n readonly runtime: PurgeContainerTreeRuntime;\n /** Stops a stale store generation between complete destructive units. */\n readonly stillCurrent?: (() => boolean) | undefined;\n // Checked at each unit boundary (between whole documents / whole containers),\n // never mid-remote-call, so a cancelled run stops on a consistent prefix.\n readonly signal?: AbortSignal | undefined;\n}\n\ninterface PurgeContainerTreeResult {\n readonly aborted: boolean;\n readonly completedCount: number;\n readonly failedCount: number;\n readonly purgedContainerIds: readonly string[];\n readonly remoteDeletedContainerIds: readonly string[];\n readonly totalCount: number;\n}\n\ninterface SubtreeDocumentPlan {\n // Synced documents whose every link falls inside the subtree: reduce them to\n // one retained link, as required by the purge endpoint, then destroy them.\n readonly purge: readonly {\n document: DocumentSummary;\n unlinkContainerIds: readonly string[];\n }[];\n // Synced documents also linked outside the subtree: keep them, but unlink\n // from each in-subtree container so the containers can be deleted.\n readonly unlinkByDocument: ReadonlyMap<\n string,\n { document: DocumentSummary; containerIds: readonly string[] }\n >;\n // Never-synced documents (no remote id): destroy locally only.\n readonly purgeLocal: readonly DocumentSummary[];\n}\n\ntype SubtreeDocumentDisposition =\n | { kind: \"purge\"; unlinkContainerIds: readonly string[] }\n | { kind: \"purge-local\" }\n | { kind: \"unlink\"; containerIds: readonly string[] };\n\n// Destroy local-only documents locally. Destroy synced documents only when all\n// links are in the subtree; preserve documents with an external link. The purge\n// plan reduces owned documents to the server-required sole link first.\nexport function classifySubtreeDocument(input: {\n readonly documentId: string | null;\n readonly linkedContainerIds: readonly string[];\n readonly preferredContainerId?: string | null | undefined;\n readonly subtreeContainerIds: ReadonlySet<string>;\n}): SubtreeDocumentDisposition {\n if (!input.documentId) {\n return { kind: \"purge-local\" };\n }\n const hasExternalLink = input.linkedContainerIds.some(\n (containerId) => !input.subtreeContainerIds.has(containerId),\n );\n if (!hasExternalLink) {\n const linkedContainerIds = Array.from(\n new Set(\n input.linkedContainerIds.filter((containerId) =>\n input.subtreeContainerIds.has(containerId),\n ),\n ),\n );\n const retainedContainerId =\n input.preferredContainerId &&\n linkedContainerIds.includes(input.preferredContainerId)\n ? input.preferredContainerId\n : linkedContainerIds[0];\n return {\n kind: \"purge\",\n unlinkContainerIds: linkedContainerIds.filter(\n (containerId) => containerId !== retainedContainerId,\n ),\n };\n }\n return {\n kind: \"unlink\",\n containerIds: input.linkedContainerIds.filter((containerId) =>\n input.subtreeContainerIds.has(containerId),\n ),\n };\n}\n\nasync function planSubtreeDocuments(input: {\n readonly execSql: PurgeContainerTreeRuntime[\"infra\"][\"execSql\"];\n readonly subtreeContainerIds: ReadonlySet<string>;\n}): Promise<SubtreeDocumentPlan> {\n const documents = await listDocumentsByContainerIdsOrDocumentIds(\n input.execSql,\n {\n containerIds: [...input.subtreeContainerIds],\n documentIds: [],\n },\n );\n const remoteDocumentIds = documents.flatMap((document) =>\n document.documentId ? [document.documentId] : [],\n );\n const linkedContainerIdsByDocumentId =\n await sqlDocumentContainerProjectionPersistence.listLinkedContainerIdsByDocumentIds(\n input.execSql,\n remoteDocumentIds,\n );\n\n const purge: Array<{\n document: DocumentSummary;\n unlinkContainerIds: readonly string[];\n }> = [];\n const purgeLocal: DocumentSummary[] = [];\n const unlinkByDocument = new Map<\n string,\n { document: DocumentSummary; containerIds: readonly string[] }\n >();\n for (const document of documents) {\n const disposition = classifySubtreeDocument({\n documentId: document.documentId ?? null,\n linkedContainerIds: document.documentId\n ? (linkedContainerIdsByDocumentId.get(document.documentId) ?? [])\n : [],\n preferredContainerId: document.containerId,\n subtreeContainerIds: input.subtreeContainerIds,\n });\n if (disposition.kind === \"purge-local\") {\n purgeLocal.push(document);\n } else if (disposition.kind === \"purge\") {\n purge.push({\n document,\n unlinkContainerIds: disposition.unlinkContainerIds,\n });\n } else if (document.documentId) {\n unlinkByDocument.set(document.documentId, {\n document,\n containerIds: disposition.containerIds,\n });\n }\n }\n\n return { purge, purgeLocal, unlinkByDocument };\n}\n\nasync function unlinkSubtreeDocument(input: {\n readonly containerIds: readonly string[];\n readonly document: DocumentSummary;\n readonly prepareDocumentRotationSnapshot: PurgeContainerTreeInput[\"prepareDocumentRotationSnapshot\"];\n readonly resolveProjectionUserKey: ProjectionUserKeyResolver;\n readonly runtime: PurgeContainerTreeRuntime;\n}): Promise<boolean> {\n const documentId = input.document.documentId;\n if (!documentId) {\n return true;\n }\n let rotationSnapshot: Uint8Array | null;\n try {\n rotationSnapshot = await input.prepareDocumentRotationSnapshot(\n input.document,\n );\n } catch {\n return false;\n }\n if (!rotationSnapshot) {\n return false;\n }\n for (const containerId of input.containerIds) {\n const result = await unlinkRemoteContainerDocument({\n documentId,\n noteId: input.document.id,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n rotationSnapshot,\n runtime: input.runtime,\n targetContainerId: containerId,\n });\n if (result === null) {\n return false;\n }\n }\n return true;\n}\n\ninterface SubtreeDocumentOperations {\n readonly purgeLocal: (document: DocumentSummary) => Promise<boolean>;\n readonly purgeRemote: (document: DocumentSummary) => Promise<boolean>;\n readonly unlink: (\n document: DocumentSummary,\n containerIds: readonly string[],\n ) => Promise<boolean>;\n}\n\nfunction resolveSubtreeDocumentOperations(\n input: PurgeContainerTreeInput,\n): SubtreeDocumentOperations {\n return (\n input.documentOperations ?? {\n purgeLocal: async (document) =>\n (await purgeLocalContainerDocument({\n noteId: document.id,\n runtime: input.runtime,\n })) !== null,\n purgeRemote: async (document) =>\n Boolean(\n document.documentId &&\n (await purgeRemoteContainerDocument({\n documentId: document.documentId,\n documentKind: document.documentKind ?? DEFAULT_DOCUMENT_KIND,\n noteId: document.id,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n runtime: input.runtime,\n })),\n ),\n unlink: (document, containerIds) =>\n unlinkSubtreeDocument({\n containerIds,\n document,\n prepareDocumentRotationSnapshot:\n input.prepareDocumentRotationSnapshot,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n runtime: input.runtime,\n }),\n }\n );\n}\n\ninterface SubtreeTeardownResult {\n readonly aborted: boolean;\n}\n\nfunction purgeWasCancelled(input: {\n readonly signal?: AbortSignal | undefined;\n readonly stillCurrent?: (() => boolean) | undefined;\n}): boolean {\n return input.signal?.aborted === true || input.stillCurrent?.() === false;\n}\n\n// Tear down one whole document per progress step. A failed document is skipped,\n// leaving its container undeletable while the remaining subtree work continues.\n// Cancellation is honored only between complete document operations.\nasync function teardownSubtreeDocuments(input: {\n readonly documentOperations: SubtreeDocumentOperations;\n readonly plan: SubtreeDocumentPlan;\n readonly reportStep: (ok: boolean) => void;\n readonly signal?: AbortSignal | undefined;\n readonly stillCurrent?: (() => boolean) | undefined;\n}): Promise<SubtreeTeardownResult> {\n for (const {\n document,\n containerIds,\n } of input.plan.unlinkByDocument.values()) {\n if (purgeWasCancelled(input)) {\n return { aborted: true };\n }\n const unlinked = await input.documentOperations.unlink(\n document,\n containerIds,\n );\n input.reportStep(unlinked);\n }\n for (const { document, unlinkContainerIds } of input.plan.purge) {\n if (purgeWasCancelled(input)) {\n return { aborted: true };\n }\n if (\n unlinkContainerIds.length > 0 &&\n !(await input.documentOperations.unlink(document, unlinkContainerIds))\n ) {\n input.reportStep(false);\n continue;\n }\n input.reportStep(await input.documentOperations.purgeRemote(document));\n }\n for (const document of input.plan.purgeLocal) {\n if (purgeWasCancelled(input)) {\n return { aborted: true };\n }\n input.reportStep(await input.documentOperations.purgeLocal(document));\n }\n return { aborted: false };\n}\n\n// Permanently destroy a folder that lives inside Trash, including everything\n// under it (or, with keepRootContainer, everything under the Trash bin while\n// leaving the bin in place — this is \"Empty Trash\"). Documents are destroyed only\n// when the subtree holds their last link; multi-folder documents are unlinked\n// from the subtree and preserved. Containers are deleted leaf-first so each\n// delete obeys the leaf-only constraint the store and server enforce. Every step\n// is remote-first then local (the per-document and per-container workflows handle\n// that internally), so a cancel between steps leaves local state reflecting\n// exactly the completed remote work.\n//\n// Progress is determinate: the plan (a pure read) completes before any\n// destructive step, so onProgress reports completed/failed against a total known\n// up front. A failed item is skipped rather than aborting the run, so the result\n// reports whatever prefix was actually destroyed alongside completed/failed\n// counts and whether a cancellation cut it short. Returns null only when the\n// target container is absent from the snapshot.\nexport async function purgeContainerTree(\n input: PurgeContainerTreeInput,\n): Promise<PurgeContainerTreeResult | null> {\n if (purgeWasCancelled(input)) {\n return null;\n }\n const subtreeStates = collectSubtreeLeafFirst(\n input.containersById,\n input.rootContainerId,\n );\n if (subtreeStates.length === 0) {\n return null;\n }\n const subtreeContainerIds = new Set(\n subtreeStates.map((containerState) => containerState.container.id),\n );\n\n const plan = await planSubtreeDocuments({\n execSql: input.runtime.infra.execSql,\n subtreeContainerIds,\n });\n if (purgeWasCancelled(input)) {\n return null;\n }\n\n // Empty Trash keeps the Trash bin itself: tear down everything it holds, but\n // exclude the root from the container deletions (deleting a system container\n // would be rejected anyway).\n const containerStatesToDelete = input.keepRootContainer\n ? subtreeStates.filter(\n (containerState) =>\n containerState.container.id !== input.rootContainerId,\n )\n : subtreeStates;\n\n // The total is fully known here — planning is a pure read that completes before\n // any destructive step. Unlink work is counted per document (not per container\n // link) so completed can never exceed total even when a document has several\n // in-subtree links.\n const totalCount =\n plan.unlinkByDocument.size +\n plan.purge.length +\n plan.purgeLocal.length +\n containerStatesToDelete.length;\n\n let completedCount = 0;\n let failedCount = 0;\n const emitProgress = () => {\n input.onProgress?.({ completedCount, failedCount, totalCount });\n };\n const reportStep = (ok: boolean) => {\n if (ok) {\n completedCount += 1;\n } else {\n failedCount += 1;\n }\n emitProgress();\n };\n // Emit the initial 0/total so a determinate bar renders immediately, before the\n // first (potentially slow) remote call.\n emitProgress();\n\n const teardown = await teardownSubtreeDocuments({\n documentOperations: resolveSubtreeDocumentOperations(input),\n plan,\n reportStep,\n signal: input.signal,\n stillCurrent: input.stillCurrent,\n });\n\n let purgedContainerIds: readonly string[] = [];\n let remoteDeletedContainerIds: readonly string[] = [];\n let aborted = teardown.aborted;\n if (!teardown.aborted) {\n const deletion = await deleteSubtreeContainers({\n persistence: input.persistence,\n reportStep,\n runtime: input.runtime,\n signal: input.signal,\n stillCurrent: input.stillCurrent,\n subtreeStates: containerStatesToDelete,\n });\n purgedContainerIds = deletion.purgedContainerIds;\n remoteDeletedContainerIds = deletion.remoteDeletedContainerIds;\n aborted = deletion.aborted;\n }\n\n return {\n aborted,\n completedCount,\n failedCount,\n purgedContainerIds,\n remoteDeletedContainerIds,\n totalCount,\n };\n}\n"]}
1
+ {"version":3,"file":"purgeTree.js","sourceRoot":"","sources":["purgeTree.ts"],"names":[],"mappings":"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","sourcesContent":["import { DEFAULT_DOCUMENT_KIND } from \"../../../data/documents/documentConstants\";\nimport type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport { sqlDocumentContainerProjectionPersistence } from \"../../../data/persistence/containers/documentContainerProjectionPersistence\";\nimport { listDocumentsByContainerIdsOrDocumentIds } from \"../../../data/persistence/documents/documentsPersistence\";\nimport type { ContainerContentsPersistence } from \"../containerPersistence\";\nimport { unlinkRemoteContainerDocument } from \"../documentLinks\";\nimport {\n purgeLocalContainerDocument,\n purgeRemoteContainerDocument,\n} from \"../documentPurge\";\nimport type { ContainerState } from \"../remoteHydration\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\nimport type { PurgeProgress } from \"./purgeProgress\";\nimport {\n collectSubtreeLeafFirst,\n snapshotContainerStates,\n} from \"./purgeTreeCollection\";\nimport { deleteSubtreeContainers } from \"./purgeTreeContainerDeletion\";\nimport { createSubtreePurgeScope } from \"./purgeTreeScope\";\n\nexport { collectSubtreeLeafFirst } from \"./purgeTreeCollection\";\n\n// The store runtime satisfies the per-document purge/unlink workflows, so the\n// cascade reuses it verbatim.\ntype PurgeContainerTreeRuntime = ContainerContentsWorkflowRuntime;\n\ninterface PurgeContainerTreeInput {\n readonly containersById: ReadonlyMap<string, ContainerState>;\n readonly documentOperations?: SubtreeDocumentOperations | undefined;\n // When true, tear down everything UNDER the root (its documents plus every\n // descendant container) but never delete the root container itself. This is\n // how \"Empty Trash\" reuses the same engine: the Trash bin is a protected system\n // container that must survive while its whole contents are destroyed.\n readonly keepRootContainer?: boolean;\n readonly onProgress?: ((progress: PurgeProgress) => void) | undefined;\n readonly persistence: ContainerContentsPersistence;\n readonly prepareDocumentRotationSnapshot: (\n document: DocumentSummary,\n ) => Promise<Uint8Array | null>;\n readonly resolveProjectionUserKey: ProjectionUserKeyResolver;\n readonly rootContainerId: string;\n readonly runtime: PurgeContainerTreeRuntime;\n /** Stops a stale store generation between complete destructive units. */\n readonly stillCurrent?: (() => boolean) | undefined;\n // Checked at each unit boundary (between whole documents / whole containers),\n // never mid-remote-call, so a cancelled run stops on a consistent prefix.\n readonly signal?: AbortSignal | undefined;\n}\n\ninterface PurgeContainerTreeResult {\n readonly aborted: boolean;\n readonly completedCount: number;\n readonly failedCount: number;\n readonly purgedContainerIds: readonly string[];\n readonly remoteDeletedContainerIds: readonly string[];\n readonly totalCount: number;\n}\n\ninterface SubtreeDocumentPlan {\n // Synced documents whose every link falls inside the subtree: reduce them to\n // one retained link, as required by the purge endpoint, then destroy them.\n readonly purge: readonly {\n document: DocumentSummary;\n unlinkContainerIds: readonly string[];\n }[];\n // Synced documents also linked outside the subtree: keep them, but unlink\n // from each in-subtree container so the containers can be deleted.\n readonly unlinkByDocument: ReadonlyMap<\n string,\n { document: DocumentSummary; containerIds: readonly string[] }\n >;\n // Never-synced documents (no remote id): destroy locally only.\n readonly purgeLocal: readonly DocumentSummary[];\n}\n\ntype SubtreeDocumentDisposition =\n | { kind: \"purge\"; unlinkContainerIds: readonly string[] }\n | { kind: \"purge-local\" }\n | { kind: \"unlink\"; containerIds: readonly string[] };\n\n// Destroy local-only documents locally. Destroy synced documents only when all\n// links are in the subtree; preserve documents with an external link. The purge\n// plan reduces owned documents to the server-required sole link first.\nexport function classifySubtreeDocument(input: {\n readonly documentId: string | null;\n readonly linkedContainerIds: readonly string[];\n readonly preferredContainerId?: string | null | undefined;\n readonly subtreeContainerIds: ReadonlySet<string>;\n}): SubtreeDocumentDisposition {\n if (!input.documentId) {\n return { kind: \"purge-local\" };\n }\n const hasExternalLink = input.linkedContainerIds.some(\n (containerId) => !input.subtreeContainerIds.has(containerId),\n );\n if (!hasExternalLink) {\n const linkedContainerIds = Array.from(\n new Set(\n input.linkedContainerIds.filter((containerId) =>\n input.subtreeContainerIds.has(containerId),\n ),\n ),\n );\n const retainedContainerId =\n input.preferredContainerId &&\n linkedContainerIds.includes(input.preferredContainerId)\n ? input.preferredContainerId\n : linkedContainerIds[0];\n return {\n kind: \"purge\",\n unlinkContainerIds: linkedContainerIds.filter(\n (containerId) => containerId !== retainedContainerId,\n ),\n };\n }\n return {\n kind: \"unlink\",\n containerIds: input.linkedContainerIds.filter((containerId) =>\n input.subtreeContainerIds.has(containerId),\n ),\n };\n}\n\nasync function planSubtreeDocuments(input: {\n readonly execSql: PurgeContainerTreeRuntime[\"infra\"][\"execSql\"];\n readonly subtreeContainerIds: ReadonlySet<string>;\n}): Promise<SubtreeDocumentPlan> {\n const documents = await listDocumentsByContainerIdsOrDocumentIds(\n input.execSql,\n {\n containerIds: [...input.subtreeContainerIds],\n documentIds: [],\n },\n );\n const remoteDocumentIds = documents.flatMap((document) =>\n document.documentId ? [document.documentId] : [],\n );\n const linkedContainerIdsByDocumentId =\n await sqlDocumentContainerProjectionPersistence.listLinkedContainerIdsByDocumentIds(\n input.execSql,\n remoteDocumentIds,\n );\n\n const purge: Array<{\n document: DocumentSummary;\n unlinkContainerIds: readonly string[];\n }> = [];\n const purgeLocal: DocumentSummary[] = [];\n const unlinkByDocument = new Map<\n string,\n { document: DocumentSummary; containerIds: readonly string[] }\n >();\n for (const document of documents) {\n const disposition = classifySubtreeDocument({\n documentId: document.documentId ?? null,\n linkedContainerIds: document.documentId\n ? (linkedContainerIdsByDocumentId.get(document.documentId) ?? [])\n : [],\n preferredContainerId: document.containerId,\n subtreeContainerIds: input.subtreeContainerIds,\n });\n if (disposition.kind === \"purge-local\") {\n purgeLocal.push(document);\n } else if (disposition.kind === \"purge\") {\n purge.push({\n document,\n unlinkContainerIds: disposition.unlinkContainerIds,\n });\n } else if (document.documentId) {\n unlinkByDocument.set(document.documentId, {\n document,\n containerIds: disposition.containerIds,\n });\n }\n }\n\n return { purge, purgeLocal, unlinkByDocument };\n}\n\nasync function unlinkSubtreeDocument(input: {\n readonly beforeSubmit: () => Promise<boolean>;\n readonly expectedSubtreeRootId: string;\n readonly stillCurrent?: (() => boolean) | undefined;\n readonly containerIds: readonly string[];\n readonly document: DocumentSummary;\n readonly prepareDocumentRotationSnapshot: PurgeContainerTreeInput[\"prepareDocumentRotationSnapshot\"];\n readonly resolveProjectionUserKey: ProjectionUserKeyResolver;\n readonly runtime: PurgeContainerTreeRuntime;\n}): Promise<boolean> {\n const documentId = input.document.documentId;\n if (!documentId) {\n return true;\n }\n let rotationSnapshot: Uint8Array | null;\n try {\n rotationSnapshot = await input.prepareDocumentRotationSnapshot(\n input.document,\n );\n } catch {\n return false;\n }\n if (!rotationSnapshot) {\n return false;\n }\n for (const containerId of input.containerIds) {\n const result = await unlinkRemoteContainerDocument({\n beforeSubmit: input.beforeSubmit,\n documentId,\n expectedSubtreeRootId: input.expectedSubtreeRootId,\n isCurrent: input.stillCurrent,\n noteId: input.document.id,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n rotationSnapshot,\n runtime: input.runtime,\n targetContainerId: containerId,\n });\n if (result === null) {\n return false;\n }\n }\n return true;\n}\n\ninterface SubtreeDocumentOperations {\n readonly purgeLocal: (document: DocumentSummary) => Promise<boolean>;\n readonly purgeRemote: (document: DocumentSummary) => Promise<boolean>;\n readonly unlink: (\n document: DocumentSummary,\n containerIds: readonly string[],\n ) => Promise<boolean>;\n}\n\nfunction resolveSubtreeDocumentOperations(\n input: PurgeContainerTreeInput,\n scope: ReturnType<typeof createSubtreePurgeScope>,\n): SubtreeDocumentOperations {\n const operations: SubtreeDocumentOperations = input.documentOperations ?? {\n purgeLocal: async (document) =>\n (await purgeLocalContainerDocument({\n beforeDeleteInTransaction: (execSql) =>\n scope.assertLocalScopeInTransaction(\n execSql,\n document.containerId,\n document.id,\n ),\n noteId: document.id,\n expectedContainerId: document.containerId,\n runtime: input.runtime,\n })) !== null,\n purgeRemote: async (document) =>\n Boolean(\n document.documentId &&\n (await purgeRemoteContainerDocument({\n beforeSubmit: () => scope.allowsDocument(document),\n documentId: document.documentId,\n expectedSubtreeRootId: input.rootContainerId,\n stillCurrent: input.stillCurrent,\n documentKind: document.documentKind ?? DEFAULT_DOCUMENT_KIND,\n noteId: document.id,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n runtime: input.runtime,\n })),\n ),\n unlink: (document, containerIds) =>\n unlinkSubtreeDocument({\n beforeSubmit: () => scope.allowsDocument(document),\n containerIds,\n document,\n expectedSubtreeRootId: input.rootContainerId,\n stillCurrent: input.stillCurrent,\n prepareDocumentRotationSnapshot: input.prepareDocumentRotationSnapshot,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n runtime: input.runtime,\n }),\n };\n return {\n purgeLocal: async (document) =>\n (await scope.allowsDocument(document)) &&\n (await operations.purgeLocal(document)),\n purgeRemote: async (document) =>\n (await scope.allowsDocument(document)) &&\n (await operations.purgeRemote(document)),\n unlink: async (document, containerIds) => {\n if (!(await scope.allowsDocument(document))) return false;\n for (const containerId of containerIds) {\n if (!(await scope.allowsContainer(containerId))) return false;\n }\n return operations.unlink(document, containerIds);\n },\n };\n}\n\ninterface SubtreeTeardownResult {\n readonly aborted: boolean;\n}\n\nfunction purgeWasCancelled(input: {\n readonly signal?: AbortSignal | undefined;\n readonly stillCurrent?: (() => boolean) | undefined;\n}): boolean {\n return input.signal?.aborted === true || input.stillCurrent?.() === false;\n}\n\n// Tear down one whole document per progress step. A failed document is skipped,\n// leaving its container undeletable while the remaining subtree work continues.\n// Cancellation is honored only between complete document operations.\nasync function teardownSubtreeDocuments(input: {\n readonly documentOperations: SubtreeDocumentOperations;\n readonly plan: SubtreeDocumentPlan;\n readonly reportStep: (ok: boolean) => void;\n readonly signal?: AbortSignal | undefined;\n readonly stillCurrent?: (() => boolean) | undefined;\n}): Promise<SubtreeTeardownResult> {\n for (const {\n document,\n containerIds,\n } of input.plan.unlinkByDocument.values()) {\n if (purgeWasCancelled(input)) {\n return { aborted: true };\n }\n const unlinked = await input.documentOperations.unlink(\n document,\n containerIds,\n );\n input.reportStep(unlinked);\n }\n for (const { document, unlinkContainerIds } of input.plan.purge) {\n if (purgeWasCancelled(input)) {\n return { aborted: true };\n }\n if (\n unlinkContainerIds.length > 0 &&\n !(await input.documentOperations.unlink(document, unlinkContainerIds))\n ) {\n input.reportStep(false);\n continue;\n }\n input.reportStep(await input.documentOperations.purgeRemote(document));\n }\n for (const document of input.plan.purgeLocal) {\n if (purgeWasCancelled(input)) {\n return { aborted: true };\n }\n input.reportStep(await input.documentOperations.purgeLocal(document));\n }\n return { aborted: false };\n}\n\n// Permanently destroy a folder that lives inside Trash, including everything\n// under it (or, with keepRootContainer, everything under the Trash bin while\n// leaving the bin in place — this is \"Empty Trash\"). Documents are destroyed only\n// when the subtree holds their last link; multi-folder documents are unlinked\n// from the subtree and preserved. Containers are deleted leaf-first so each\n// delete obeys the leaf-only constraint the store and server enforce. Every step\n// is remote-first then local (the per-document and per-container workflows handle\n// that internally), so a cancel between steps leaves local state reflecting\n// exactly the completed remote work.\n//\n// Progress is determinate: the plan (a pure read) completes before any\n// destructive step, so onProgress reports completed/failed against a total known\n// up front. A failed item is skipped rather than aborting the run, so the result\n// reports whatever prefix was actually destroyed alongside completed/failed\n// counts and whether a cancellation cut it short. Returns null only when the\n// target container is absent from the snapshot.\nexport async function purgeContainerTree(\n request: PurgeContainerTreeInput,\n): Promise<PurgeContainerTreeResult | null> {\n if (purgeWasCancelled(request)) {\n return null;\n }\n const input = {\n ...request,\n containersById: snapshotContainerStates(request.containersById),\n };\n const subtreeStates = collectSubtreeLeafFirst(\n input.containersById,\n input.rootContainerId,\n );\n if (subtreeStates.length === 0) {\n return null;\n }\n const subtreeContainerIds = new Set(\n subtreeStates.map((containerState) => containerState.container.id),\n );\n\n const plan = await planSubtreeDocuments({\n execSql: input.runtime.infra.execSql,\n subtreeContainerIds,\n });\n if (purgeWasCancelled(input)) {\n return null;\n }\n\n // Empty Trash keeps the Trash bin itself: tear down everything it holds, but\n // exclude the root from the container deletions (deleting a system container\n // would be rejected anyway).\n const containerStatesToDelete = input.keepRootContainer\n ? subtreeStates.filter(\n (containerState) =>\n containerState.container.id !== input.rootContainerId,\n )\n : subtreeStates;\n\n // The total is fully known here — planning is a pure read that completes before\n // any destructive step. Unlink work is counted per document (not per container\n // link) so completed can never exceed total even when a document has several\n // in-subtree links.\n const totalCount =\n plan.unlinkByDocument.size +\n plan.purge.length +\n plan.purgeLocal.length +\n containerStatesToDelete.length;\n\n let completedCount = 0;\n let failedCount = 0;\n const emitProgress = () => {\n input.onProgress?.({ completedCount, failedCount, totalCount });\n };\n const reportStep = (ok: boolean) => {\n if (ok) {\n completedCount += 1;\n } else {\n failedCount += 1;\n }\n emitProgress();\n };\n // Emit the initial 0/total so a determinate bar renders immediately, before the\n // first (potentially slow) remote call.\n emitProgress();\n\n const scope = createSubtreePurgeScope(input);\n const teardown = await teardownSubtreeDocuments({\n documentOperations: resolveSubtreeDocumentOperations(input, scope),\n plan,\n reportStep,\n signal: input.signal,\n stillCurrent: input.stillCurrent,\n });\n\n let purgedContainerIds: readonly string[] = [];\n let remoteDeletedContainerIds: readonly string[] = [];\n let aborted = teardown.aborted;\n if (!teardown.aborted) {\n const deletion = await deleteSubtreeContainers({\n allowsContainer: scope.allowsContainer,\n allowsLocalScopeInTransaction: scope.allowsLocalScopeInTransaction,\n rootContainerId: input.rootContainerId,\n persistence: input.persistence,\n reportStep,\n runtime: input.runtime,\n signal: input.signal,\n stillCurrent: input.stillCurrent,\n subtreeStates: containerStatesToDelete,\n });\n purgedContainerIds = deletion.purgedContainerIds;\n remoteDeletedContainerIds = deletion.remoteDeletedContainerIds;\n aborted = deletion.aborted;\n }\n\n return {\n aborted,\n completedCount,\n failedCount,\n purgedContainerIds,\n remoteDeletedContainerIds,\n totalCount,\n };\n}\n"]}
@@ -1,3 +1,14 @@
1
+ /** Own the placement fields; live store updates cannot revise purge intent. */
2
+ export function snapshotContainerStates(states) {
3
+ return new Map([...states].map(([id, state]) => [
4
+ id,
5
+ {
6
+ ...state,
7
+ container: { ...state.container },
8
+ record: { ...state.record },
9
+ },
10
+ ]));
11
+ }
1
12
  // Collect the target and descendants leaf-first because container deletion is
2
13
  // leaf-only. The enqueued set guards self/cyclic parent chains.
3
14
  export function collectSubtreeLeafFirst(containersById, rootContainerId) {
@@ -1 +1 @@
1
- {"version":3,"file":"purgeTreeCollection.js","sourceRoot":"","sources":["purgeTreeCollection.ts"],"names":[],"mappings":"AAEA,8EAA8E;AAC9E,gEAAgE;AAChE,MAAM,UAAU,uBAAuB,CACrC,cAAmD,EACnD,eAAuB;IAEvB,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAoB,CAAC;IACvD,KAAK,MAAM,cAAc,IAAI,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC;QACrD,MAAM,QAAQ,GAAG,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC;QACnD,IAAI,QAAQ,KAAK,IAAI;YAAE,SAAS;QAChC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxD,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAC3C,kBAAkB,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,CAAC,eAAe,CAAC,CAAC,CAAC;IACpD,2EAA2E;IAC3E,yDAAyD;IACzD,MAAM,KAAK,GAAG,CAAC,eAAe,CAAC,CAAC;IAChC,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;QAC/C,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,WAAW,KAAK,SAAS;YAAE,SAAS;QACxC,MAAM,cAAc,GAAG,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACvD,IAAI,cAAc;YAAE,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACjD,KAAK,MAAM,OAAO,IAAI,kBAAkB,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC;YAChE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3B,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBACtB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["import type { ContainerState } from \"../remoteHydration\";\n\n// Collect the target and descendants leaf-first because container deletion is\n// leaf-only. The enqueued set guards self/cyclic parent chains.\nexport function collectSubtreeLeafFirst(\n containersById: ReadonlyMap<string, ContainerState>,\n rootContainerId: string,\n): ContainerState[] {\n const childIdsByParentId = new Map<string, string[]>();\n for (const containerState of containersById.values()) {\n const parentId = containerState.container.parentId;\n if (parentId === null) continue;\n const siblings = childIdsByParentId.get(parentId) ?? [];\n siblings.push(containerState.container.id);\n childIdsByParentId.set(parentId, siblings);\n }\n\n const ordered: ContainerState[] = [];\n const enqueued = new Set<string>([rootContainerId]);\n // Read through the queue with a head index rather than shift(): shift() is\n // O(N) per call, which would make the whole walk O(N^2).\n const queue = [rootContainerId];\n for (let head = 0; head < queue.length; head++) {\n const containerId = queue[head];\n if (containerId === undefined) continue;\n const containerState = containersById.get(containerId);\n if (containerState) ordered.push(containerState);\n for (const childId of childIdsByParentId.get(containerId) ?? []) {\n if (!enqueued.has(childId)) {\n enqueued.add(childId);\n queue.push(childId);\n }\n }\n }\n\n return ordered.reverse();\n}\n"]}
1
+ {"version":3,"file":"purgeTreeCollection.js","sourceRoot":"","sources":["purgeTreeCollection.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,MAAM,UAAU,uBAAuB,CACrC,MAA2C;IAE3C,OAAO,IAAI,GAAG,CACZ,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;QAC/B,EAAE;QACF;YACE,GAAG,KAAK;YACR,SAAS,EAAE,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE;YACjC,MAAM,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE;SAC5B;KACF,CAAC,CACH,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,gEAAgE;AAChE,MAAM,UAAU,uBAAuB,CACrC,cAAmD,EACnD,eAAuB;IAEvB,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAoB,CAAC;IACvD,KAAK,MAAM,cAAc,IAAI,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC;QACrD,MAAM,QAAQ,GAAG,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC;QACnD,IAAI,QAAQ,KAAK,IAAI;YAAE,SAAS;QAChC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACxD,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAC3C,kBAAkB,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,CAAC,eAAe,CAAC,CAAC,CAAC;IACpD,2EAA2E;IAC3E,yDAAyD;IACzD,MAAM,KAAK,GAAG,CAAC,eAAe,CAAC,CAAC;IAChC,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;QAC/C,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,WAAW,KAAK,SAAS;YAAE,SAAS;QACxC,MAAM,cAAc,GAAG,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QACvD,IAAI,cAAc;YAAE,OAAO,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACjD,KAAK,MAAM,OAAO,IAAI,kBAAkB,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC;YAChE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC3B,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBACtB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["import type { ContainerState } from \"../remoteHydration\";\n\n/** Own the placement fields; live store updates cannot revise purge intent. */\nexport function snapshotContainerStates(\n states: ReadonlyMap<string, ContainerState>,\n): ReadonlyMap<string, ContainerState> {\n return new Map(\n [...states].map(([id, state]) => [\n id,\n {\n ...state,\n container: { ...state.container },\n record: { ...state.record },\n },\n ]),\n );\n}\n\n// Collect the target and descendants leaf-first because container deletion is\n// leaf-only. The enqueued set guards self/cyclic parent chains.\nexport function collectSubtreeLeafFirst(\n containersById: ReadonlyMap<string, ContainerState>,\n rootContainerId: string,\n): ContainerState[] {\n const childIdsByParentId = new Map<string, string[]>();\n for (const containerState of containersById.values()) {\n const parentId = containerState.container.parentId;\n if (parentId === null) continue;\n const siblings = childIdsByParentId.get(parentId) ?? [];\n siblings.push(containerState.container.id);\n childIdsByParentId.set(parentId, siblings);\n }\n\n const ordered: ContainerState[] = [];\n const enqueued = new Set<string>([rootContainerId]);\n // Read through the queue with a head index rather than shift(): shift() is\n // O(N) per call, which would make the whole walk O(N^2).\n const queue = [rootContainerId];\n for (let head = 0; head < queue.length; head++) {\n const containerId = queue[head];\n if (containerId === undefined) continue;\n const containerState = containersById.get(containerId);\n if (containerState) ordered.push(containerState);\n for (const childId of childIdsByParentId.get(containerId) ?? []) {\n if (!enqueued.has(childId)) {\n enqueued.add(childId);\n queue.push(childId);\n }\n }\n }\n\n return ordered.reverse();\n}\n"]}
@@ -26,7 +26,8 @@ export async function deleteSubtreeContainers(input) {
26
26
  }
27
27
  const containerId = containerState.container.id;
28
28
  const parentId = containerState.container.parentId;
29
- if (blockedParentIds.has(containerId)) {
29
+ if (blockedParentIds.has(containerId) ||
30
+ !(await input.allowsContainer(containerId))) {
30
31
  if (parentId !== null) {
31
32
  blockedParentIds.add(parentId);
32
33
  }
@@ -34,7 +35,9 @@ export async function deleteSubtreeContainers(input) {
34
35
  continue;
35
36
  }
36
37
  const deleted = await deleteContainerState({
38
+ beforeDeleteInTransaction: (execSql) => input.allowsLocalScopeInTransaction(execSql, containerId),
37
39
  containerState,
40
+ requiredAncestorId: input.rootContainerId,
38
41
  persistence: input.persistence,
39
42
  runtime: input.runtime,
40
43
  stillCurrent: input.stillCurrent,
@@ -1 +1 @@
1
- {"version":3,"file":"purgeTreeContainerDeletion.js","sourceRoot":"","sources":["purgeTreeContainerDeletion.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAiBhD,SAAS,oBAAoB,CAAC,KAAmC;IAC/D,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,IAAI,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK,CAAC;AAC5E,CAAC;AAED,SAAS,8BAA8B,CAAC,KAKvC;IACC,IAAI,KAAK,CAAC,OAAO,KAAK,gBAAgB,EAAE,CAAC;QACvC,KAAK,CAAC,yBAAyB,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC9C,KAAK,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACjE,IAAI,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;QAC3C,KAAK,CAAC,yBAAyB,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,gFAAgF;AAChF,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,KAAmC;IAEnC,MAAM,kBAAkB,GAAa,EAAE,CAAC;IACxC,MAAM,yBAAyB,GAAa,EAAE,CAAC;IAC/C,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAC;IAC3C,KAAK,MAAM,cAAc,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;QACjD,IAAI,oBAAoB,CAAC,KAAK,CAAC,EAAE,CAAC;YAChC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,CAAC;QAC1E,CAAC;QACD,MAAM,WAAW,GAAG,cAAc,CAAC,SAAS,CAAC,EAAE,CAAC;QAChD,MAAM,QAAQ,GAAG,cAAc,CAAC,SAAS,CAAC,QAAQ,CAAC;QACnD,IAAI,gBAAgB,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YACtC,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACtB,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACjC,CAAC;YACD,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YACxB,SAAS;QACX,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,oBAAoB,CAAC;YACzC,cAAc;YACd,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,YAAY,EAAE,KAAK,CAAC,YAAY;SACjC,CAAC,CAAC;QACH,IACE,8BAA8B,CAAC;YAC7B,cAAc;YACd,OAAO;YACP,kBAAkB;YAClB,yBAAyB;SAC1B,CAAC,EACF,CAAC;YACD,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,IACE,CAAC,OAAO,KAAK,gBAAgB,IAAI,OAAO,KAAK,gBAAgB,CAAC;gBAC9D,oBAAoB,CAAC,KAAK,CAAC,EAC3B,CAAC;gBACD,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,kBAAkB;oBAClB,yBAAyB;iBAC1B,CAAC;YACJ,CAAC;YACD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;gBACtB,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACjC,CAAC;YACD,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,CAAC;AAC3E,CAAC","sourcesContent":["import type { ContainerContentsPersistence } from \"../containerPersistence\";\nimport type { ContainerState } from \"../remoteHydration\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\nimport { deleteContainerState } from \"./delete\";\n\ninterface SubtreeContainerDeletionResult {\n readonly aborted: boolean;\n readonly purgedContainerIds: string[];\n readonly remoteDeletedContainerIds: string[];\n}\n\ninterface DeleteSubtreeContainersInput {\n readonly persistence: ContainerContentsPersistence;\n readonly reportStep: (ok: boolean) => void;\n readonly runtime: ContainerContentsWorkflowRuntime;\n readonly signal?: AbortSignal | undefined;\n readonly stillCurrent?: (() => boolean) | undefined;\n readonly subtreeStates: readonly ContainerState[];\n}\n\nfunction deletionWasCancelled(input: DeleteSubtreeContainersInput): boolean {\n return input.signal?.aborted === true || input.stillCurrent?.() === false;\n}\n\nfunction recordContainerDeletionOutcome(input: {\n containerState: ContainerState;\n deleted: Awaited<ReturnType<typeof deleteContainerState>>;\n purgedContainerIds: string[];\n remoteDeletedContainerIds: string[];\n}): boolean {\n if (input.deleted === \"remote-deleted\") {\n input.remoteDeletedContainerIds.push(input.containerState.container.id);\n }\n if (input.deleted !== \"deleted\") return false;\n input.purgedContainerIds.push(input.containerState.container.id);\n if (input.containerState.record.documentId) {\n input.remoteDeletedContainerIds.push(input.containerState.container.id);\n }\n return true;\n}\n\n// Delete leaf-first. A surviving document or child blocks its container and all\n// ancestors; cancellation stops at a container boundary.\nexport async function deleteSubtreeContainers(\n input: DeleteSubtreeContainersInput,\n): Promise<SubtreeContainerDeletionResult> {\n const purgedContainerIds: string[] = [];\n const remoteDeletedContainerIds: string[] = [];\n const blockedParentIds = new Set<string>();\n for (const containerState of input.subtreeStates) {\n if (deletionWasCancelled(input)) {\n return { aborted: true, purgedContainerIds, remoteDeletedContainerIds };\n }\n const containerId = containerState.container.id;\n const parentId = containerState.container.parentId;\n if (blockedParentIds.has(containerId)) {\n if (parentId !== null) {\n blockedParentIds.add(parentId);\n }\n input.reportStep(false);\n continue;\n }\n const deleted = await deleteContainerState({\n containerState,\n persistence: input.persistence,\n runtime: input.runtime,\n stillCurrent: input.stillCurrent,\n });\n if (\n recordContainerDeletionOutcome({\n containerState,\n deleted,\n purgedContainerIds,\n remoteDeletedContainerIds,\n })\n ) {\n input.reportStep(true);\n } else {\n if (\n (deleted === \"local-conflict\" || deleted === \"remote-deleted\") &&\n deletionWasCancelled(input)\n ) {\n return {\n aborted: true,\n purgedContainerIds,\n remoteDeletedContainerIds,\n };\n }\n if (parentId !== null) {\n blockedParentIds.add(parentId);\n }\n input.reportStep(false);\n }\n }\n return { aborted: false, purgedContainerIds, remoteDeletedContainerIds };\n}\n"]}
1
+ {"version":3,"file":"purgeTreeContainerDeletion.js","sourceRoot":"","sources":["purgeTreeContainerDeletion.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExecSql } from \"../../../data/sqlite/sqlSchema\";\nimport type { ContainerContentsPersistence } from \"../containerPersistence\";\nimport type { ContainerState } from \"../remoteHydration\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\nimport { deleteContainerState } from \"./delete\";\n\ninterface SubtreeContainerDeletionResult {\n readonly aborted: boolean;\n readonly purgedContainerIds: string[];\n readonly remoteDeletedContainerIds: string[];\n}\n\ninterface DeleteSubtreeContainersInput {\n readonly allowsContainer: (containerId: string) => Promise<boolean>;\n readonly allowsLocalScopeInTransaction: (\n execSql: ExecSql,\n containerId: string,\n ) => Promise<boolean>;\n readonly persistence: ContainerContentsPersistence;\n readonly rootContainerId: string;\n readonly reportStep: (ok: boolean) => void;\n readonly runtime: ContainerContentsWorkflowRuntime;\n readonly signal?: AbortSignal | undefined;\n readonly stillCurrent?: (() => boolean) | undefined;\n readonly subtreeStates: readonly ContainerState[];\n}\n\nfunction deletionWasCancelled(input: DeleteSubtreeContainersInput): boolean {\n return input.signal?.aborted === true || input.stillCurrent?.() === false;\n}\n\nfunction recordContainerDeletionOutcome(input: {\n containerState: ContainerState;\n deleted: Awaited<ReturnType<typeof deleteContainerState>>;\n purgedContainerIds: string[];\n remoteDeletedContainerIds: string[];\n}): boolean {\n if (input.deleted === \"remote-deleted\") {\n input.remoteDeletedContainerIds.push(input.containerState.container.id);\n }\n if (input.deleted !== \"deleted\") return false;\n input.purgedContainerIds.push(input.containerState.container.id);\n if (input.containerState.record.documentId) {\n input.remoteDeletedContainerIds.push(input.containerState.container.id);\n }\n return true;\n}\n\n// Delete leaf-first. A surviving document or child blocks its container and all\n// ancestors; cancellation stops at a container boundary.\nexport async function deleteSubtreeContainers(\n input: DeleteSubtreeContainersInput,\n): Promise<SubtreeContainerDeletionResult> {\n const purgedContainerIds: string[] = [];\n const remoteDeletedContainerIds: string[] = [];\n const blockedParentIds = new Set<string>();\n for (const containerState of input.subtreeStates) {\n if (deletionWasCancelled(input)) {\n return { aborted: true, purgedContainerIds, remoteDeletedContainerIds };\n }\n const containerId = containerState.container.id;\n const parentId = containerState.container.parentId;\n if (\n blockedParentIds.has(containerId) ||\n !(await input.allowsContainer(containerId))\n ) {\n if (parentId !== null) {\n blockedParentIds.add(parentId);\n }\n input.reportStep(false);\n continue;\n }\n const deleted = await deleteContainerState({\n beforeDeleteInTransaction: (execSql) =>\n input.allowsLocalScopeInTransaction(execSql, containerId),\n containerState,\n requiredAncestorId: input.rootContainerId,\n persistence: input.persistence,\n runtime: input.runtime,\n stillCurrent: input.stillCurrent,\n });\n if (\n recordContainerDeletionOutcome({\n containerState,\n deleted,\n purgedContainerIds,\n remoteDeletedContainerIds,\n })\n ) {\n input.reportStep(true);\n } else {\n if (\n (deleted === \"local-conflict\" || deleted === \"remote-deleted\") &&\n deletionWasCancelled(input)\n ) {\n return {\n aborted: true,\n purgedContainerIds,\n remoteDeletedContainerIds,\n };\n }\n if (parentId !== null) {\n blockedParentIds.add(parentId);\n }\n input.reportStep(false);\n }\n }\n return { aborted: false, purgedContainerIds, remoteDeletedContainerIds };\n}\n"]}
@@ -0,0 +1,158 @@
1
+ import { KeyingVerificationError } from "../../../internal/crypto/index.js";
2
+ import { errorMessage } from "../../../data/errorMessage.js";
3
+ import { verifyContainerWriterProjection } from "../../../data/keyingProjectionVerification/containerProjectionVerification.js";
4
+ import { runWithSecurityIncidentReporting } from "../../../data/keyingProjectionVerification/error.js";
5
+ import { rethrowProjectionVerificationCancelled } from "../../../data/keyingProjectionVerification/types.js";
6
+ import { hasUnsettledDocumentPlacement } from "../../../data/persistence/container-contents/documentPurgePlacement.js";
7
+ import { sqlDocumentsPersistence } from "../../../data/persistence/documents/documentsPersistence.js";
8
+ import { createRuntimePrincipalPolicyWarmer } from "../../principals/runtimePolicyWarmer.js";
9
+ import { assertLocalPurgeScope } from "./localPurgeScope.js";
10
+ import { snapshotContainerStates } from "./purgeTreeCollection.js";
11
+ import { readLocalPurgeScope } from "./readLocalPurgeScope.js";
12
+ async function verifiedRemoteScope(input, current, verifiedAncestorsById) {
13
+ const { runtime, rootContainerId } = input;
14
+ const targetId = current.container.id;
15
+ return runWithSecurityIncidentReporting(runtime.util.reportSecurityIncident, {
16
+ objectId: targetId,
17
+ objectKind: "container",
18
+ operation: "container.purge.scope",
19
+ }, async () => {
20
+ runtime.apiClient.evictContainerWriterProjection(targetId);
21
+ const projection = await runtime.apiClient.getContainerWriterProjection(targetId);
22
+ if (!projection || input.stillCurrent?.() === false)
23
+ return false;
24
+ if (projection.containerId !== targetId ||
25
+ projection.organizationId !== current.container.organizationId) {
26
+ throw new KeyingVerificationError("object_mismatch", "Subtree purge container identity mismatch");
27
+ }
28
+ const path = await verifyContainerWriterProjection({
29
+ execSql: runtime.infra.execSql,
30
+ projection,
31
+ resolveUserKey: input.resolveProjectionUserKey,
32
+ stillCurrent: input.stillCurrent,
33
+ warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(runtime),
34
+ });
35
+ const rootIndex = path.findIndex((head) => head.state.containerId === rootContainerId);
36
+ const latest = await input.persistence.loadContainerMetadataState(runtime.infra.execSql, targetId);
37
+ const pendingMoves = await readPendingMoves(input);
38
+ const allowed = rootIndex >= 0 &&
39
+ path[rootIndex]?.state.parentContainerId ===
40
+ input.containersById.get(rootContainerId)?.container.parentId &&
41
+ latest?.container.parentId === current.container.parentId &&
42
+ latest.record?.documentId === current.record.documentId &&
43
+ !path
44
+ .slice(rootIndex)
45
+ .some((head) => pendingMoves.has(head.state.containerId)) &&
46
+ input.stillCurrent?.() !== false;
47
+ if (allowed)
48
+ verifiedAncestorsById.set(targetId, path.slice(rootIndex).map((head) => head.state.containerId));
49
+ return allowed;
50
+ });
51
+ }
52
+ async function allowsContainer(input, containerId, verifiedAncestorsById) {
53
+ const { runtime, persistence, rootContainerId } = input;
54
+ if (input.stillCurrent?.() === false)
55
+ return false;
56
+ const execSql = runtime.infra.execSql;
57
+ const pendingMoves = await readPendingMoves(input);
58
+ if (pendingMoves.has(rootContainerId))
59
+ return false;
60
+ const seen = new Set([rootContainerId]);
61
+ let currentId = containerId;
62
+ while (currentId !== rootContainerId) {
63
+ if (!currentId || seen.has(currentId) || pendingMoves.has(currentId))
64
+ return false;
65
+ seen.add(currentId);
66
+ const expected = input.containersById.get(currentId);
67
+ const current = await persistence.loadContainerMetadataState(execSql, currentId);
68
+ if (!expected ||
69
+ !current?.record ||
70
+ current.container.parentId !== expected.container.parentId ||
71
+ current.container.organizationId !== expected.container.organizationId ||
72
+ current.record.documentId !== expected.record.documentId)
73
+ return false;
74
+ if (current.record.documentId) {
75
+ if (!(await verifiedRemoteScope(input, {
76
+ container: current.container,
77
+ record: current.record,
78
+ }, verifiedAncestorsById)))
79
+ return false;
80
+ break;
81
+ }
82
+ currentId = current.container.parentId;
83
+ }
84
+ return readLocalPurgeScope({
85
+ ...input,
86
+ containerId,
87
+ execSql,
88
+ verifiedAncestorsById,
89
+ });
90
+ }
91
+ async function readPendingMoves(input) {
92
+ return new Set((await input.persistence.listUnsyncedMoveIntents(input.runtime.infra.execSql)).map((intent) => intent.containerId));
93
+ }
94
+ async function documentPlacementIsCurrent(input, document) {
95
+ const execSql = input.runtime.infra.execSql;
96
+ if (await hasUnsettledDocumentPlacement(execSql, document.id))
97
+ return false;
98
+ const current = await sqlDocumentsPersistence.loadDocument(execSql, document.id);
99
+ return (current !== null &&
100
+ current.documentId === document.documentId &&
101
+ current.containerId === document.containerId &&
102
+ input.stillCurrent?.() !== false);
103
+ }
104
+ /** Listing edges only select candidates; each destructive unit proves its scope. */
105
+ export function createSubtreePurgeScope(request) {
106
+ const input = {
107
+ ...request,
108
+ containersById: snapshotContainerStates(request.containersById),
109
+ };
110
+ const verifiedAncestorsById = new Map();
111
+ return {
112
+ allowsLocalScopeInTransaction: (execSql, containerId) => checkAvailableScope(input, async () => {
113
+ await assertLocalPurgeScope({
114
+ ...input,
115
+ execSql,
116
+ containerId,
117
+ verifiedAncestorsById,
118
+ });
119
+ return true;
120
+ }),
121
+ assertLocalScopeInTransaction: (execSql, containerId, localId) => assertLocalPurgeScope({
122
+ ...input,
123
+ execSql,
124
+ containerId,
125
+ localId,
126
+ verifiedAncestorsById,
127
+ }),
128
+ allowsContainer: (containerId) => checkAvailableScope(input, () => allowsContainer(input, containerId, verifiedAncestorsById)),
129
+ allowsDocument: (document) => checkAvailableScope(input, async () => {
130
+ if (!document.containerId ||
131
+ !(await documentPlacementIsCurrent(input, document)))
132
+ return false;
133
+ if (!(await allowsContainer(input, document.containerId, verifiedAncestorsById)))
134
+ return false;
135
+ // Freeze local placement checks together after all remote verification.
136
+ return readLocalPurgeScope({
137
+ ...input,
138
+ containerId: document.containerId,
139
+ execSql: input.runtime.infra.execSql,
140
+ localId: document.id,
141
+ verifiedAncestorsById,
142
+ }, document);
143
+ }),
144
+ };
145
+ }
146
+ async function checkAvailableScope(input, check) {
147
+ try {
148
+ return await check();
149
+ }
150
+ catch (error) {
151
+ rethrowProjectionVerificationCancelled(error);
152
+ if (error instanceof KeyingVerificationError)
153
+ throw error;
154
+ input.runtime.util.log(`Container contents: purge scope unavailable: ${errorMessage(error)}`);
155
+ return false;
156
+ }
157
+ }
158
+ //# sourceMappingURL=purgeTreeScope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"purgeTreeScope.js","sourceRoot":"","sources":["purgeTreeScope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAE5D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EAAE,+BAA+B,EAAE,MAAM,4EAA4E,CAAC;AAC7H,OAAO,EAAE,gCAAgC,EAAE,MAAM,kDAAkD,CAAC;AACpG,OAAO,EAAE,sCAAsC,EAAE,MAAM,kDAAkD,CAAC;AAC1G,OAAO,EAAE,6BAA6B,EAAE,MAAM,qEAAqE,CAAC;AACpH,OAAO,EAAE,uBAAuB,EAAE,MAAM,0DAA0D,CAAC;AAEnG,OAAO,EAAE,kCAAkC,EAAE,MAAM,sCAAsC,CAAC;AAI1F,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAW5D,KAAK,UAAU,mBAAmB,CAChC,KAA6B,EAC7B,OAAqD,EACrD,qBAAqD;IAErD,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,KAAK,CAAC;IAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;IACtC,OAAO,gCAAgC,CACrC,OAAO,CAAC,IAAI,CAAC,sBAAsB,EACnC;QACE,QAAQ,EAAE,QAAQ;QAClB,UAAU,EAAE,WAAW;QACvB,SAAS,EAAE,uBAAuB;KACnC,EACD,KAAK,IAAI,EAAE;QACT,OAAO,CAAC,SAAS,CAAC,8BAA8B,CAAC,QAAQ,CAAC,CAAC;QAC3D,MAAM,UAAU,GACd,MAAM,OAAO,CAAC,SAAS,CAAC,4BAA4B,CAAC,QAAQ,CAAC,CAAC;QACjE,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QAClE,IACE,UAAU,CAAC,WAAW,KAAK,QAAQ;YACnC,UAAU,CAAC,cAAc,KAAK,OAAO,CAAC,SAAS,CAAC,cAAc,EAC9D,CAAC;YACD,MAAM,IAAI,uBAAuB,CAC/B,iBAAiB,EACjB,2CAA2C,CAC5C,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,+BAA+B,CAAC;YACjD,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO;YAC9B,UAAU;YACV,cAAc,EAAE,KAAK,CAAC,wBAAwB;YAC9C,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,+BAA+B,EAC7B,kCAAkC,CAAC,OAAO,CAAC;SAC9C,CAAC,CAAC;QACH,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAC9B,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,KAAK,eAAe,CACrD,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,WAAW,CAAC,0BAA0B,CAC/D,OAAO,CAAC,KAAK,CAAC,OAAO,EACrB,QAAQ,CACT,CAAC;QACF,MAAM,YAAY,GAAG,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACnD,MAAM,OAAO,GACX,SAAS,IAAI,CAAC;YACd,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,CAAC,iBAAiB;gBACtC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,SAAS,CAAC,QAAQ;YAC/D,MAAM,EAAE,SAAS,CAAC,QAAQ,KAAK,OAAO,CAAC,SAAS,CAAC,QAAQ;YACzD,MAAM,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,MAAM,CAAC,UAAU;YACvD,CAAC,IAAI;iBACF,KAAK,CAAC,SAAS,CAAC;iBAChB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YAC3D,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK,CAAC;QACnC,IAAI,OAAO;YACT,qBAAqB,CAAC,GAAG,CACvB,QAAQ,EACR,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAC5D,CAAC;QACJ,OAAO,OAAO,CAAC;IACjB,CAAC,CACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,eAAe,CAC5B,KAA6B,EAC7B,WAAmB,EACnB,qBAAqD;IAErD,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,GAAG,KAAK,CAAC;IACxD,IAAI,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IACnD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;IACtC,MAAM,YAAY,GAAG,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACnD,IAAI,YAAY,CAAC,GAAG,CAAC,eAAe,CAAC;QAAE,OAAO,KAAK,CAAC;IACpD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAS,CAAC,eAAe,CAAC,CAAC,CAAC;IAChD,IAAI,SAAS,GAAkB,WAAW,CAAC;IAC3C,OAAO,SAAS,KAAK,eAAe,EAAE,CAAC;QACrC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC;YAClE,OAAO,KAAK,CAAC;QACf,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACpB,MAAM,QAAQ,GAAG,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,0BAA0B,CAC1D,OAAO,EACP,SAAS,CACV,CAAC;QACF,IACE,CAAC,QAAQ;YACT,CAAC,OAAO,EAAE,MAAM;YAChB,OAAO,CAAC,SAAS,CAAC,QAAQ,KAAK,QAAQ,CAAC,SAAS,CAAC,QAAQ;YAC1D,OAAO,CAAC,SAAS,CAAC,cAAc,KAAK,QAAQ,CAAC,SAAS,CAAC,cAAc;YACtE,OAAO,CAAC,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,MAAM,CAAC,UAAU;YAExD,OAAO,KAAK,CAAC;QACf,IAAI,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YAC9B,IACE,CAAC,CAAC,MAAM,mBAAmB,CACzB,KAAK,EACL;gBACE,SAAS,EAAE,OAAO,CAAC,SAAS;gBAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,EACD,qBAAqB,CACtB,CAAC;gBAEF,OAAO,KAAK,CAAC;YACf,MAAM;QACR,CAAC;QACD,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC;IACzC,CAAC;IACD,OAAO,mBAAmB,CAAC;QACzB,GAAG,KAAK;QACR,WAAW;QACX,OAAO;QACP,qBAAqB;KACtB,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,KAA6B;IAC3D,OAAO,IAAI,GAAG,CACZ,CACE,MAAM,KAAK,CAAC,WAAW,CAAC,uBAAuB,CAC7C,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAC5B,CACF,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CACtC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,0BAA0B,CACvC,KAA6B,EAC7B,QAAyB;IAEzB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;IAC5C,IAAI,MAAM,6BAA6B,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5E,MAAM,OAAO,GAAG,MAAM,uBAAuB,CAAC,YAAY,CACxD,OAAO,EACP,QAAQ,CAAC,EAAE,CACZ,CAAC;IACF,OAAO,CACL,OAAO,KAAK,IAAI;QAChB,OAAO,CAAC,UAAU,KAAK,QAAQ,CAAC,UAAU;QAC1C,OAAO,CAAC,WAAW,KAAK,QAAQ,CAAC,WAAW;QAC5C,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK,CACjC,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,uBAAuB,CAAC,OAA+B;IACrE,MAAM,KAAK,GAAG;QACZ,GAAG,OAAO;QACV,cAAc,EAAE,uBAAuB,CAAC,OAAO,CAAC,cAAc,CAAC;KAChE,CAAC;IACF,MAAM,qBAAqB,GAAG,IAAI,GAAG,EAA6B,CAAC;IACnE,OAAO;QACL,6BAA6B,EAAE,CAAC,OAAgB,EAAE,WAAmB,EAAE,EAAE,CACvE,mBAAmB,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE;YACpC,MAAM,qBAAqB,CAAC;gBAC1B,GAAG,KAAK;gBACR,OAAO;gBACP,WAAW;gBACX,qBAAqB;aACtB,CAAC,CAAC;YACH,OAAO,IAAI,CAAC;QACd,CAAC,CAAC;QACJ,6BAA6B,EAAE,CAC7B,OAAgB,EAChB,WAAsC,EACtC,OAAe,EACf,EAAE,CACF,qBAAqB,CAAC;YACpB,GAAG,KAAK;YACR,OAAO;YACP,WAAW;YACX,OAAO;YACP,qBAAqB;SACtB,CAAC;QACJ,eAAe,EAAE,CAAC,WAAmB,EAAE,EAAE,CACvC,mBAAmB,CAAC,KAAK,EAAE,GAAG,EAAE,CAC9B,eAAe,CAAC,KAAK,EAAE,WAAW,EAAE,qBAAqB,CAAC,CAC3D;QACH,cAAc,EAAE,CAAC,QAAyB,EAAE,EAAE,CAC5C,mBAAmB,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE;YACpC,IACE,CAAC,QAAQ,CAAC,WAAW;gBACrB,CAAC,CAAC,MAAM,0BAA0B,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;gBAEpD,OAAO,KAAK,CAAC;YACf,IACE,CAAC,CAAC,MAAM,eAAe,CACrB,KAAK,EACL,QAAQ,CAAC,WAAW,EACpB,qBAAqB,CACtB,CAAC;gBAEF,OAAO,KAAK,CAAC;YACf,wEAAwE;YACxE,OAAO,mBAAmB,CACxB;gBACE,GAAG,KAAK;gBACR,WAAW,EAAE,QAAQ,CAAC,WAAW;gBACjC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO;gBACpC,OAAO,EAAE,QAAQ,CAAC,EAAE;gBACpB,qBAAqB;aACtB,EACD,QAAQ,CACT,CAAC;QACJ,CAAC,CAAC;KACL,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,mBAAmB,CAChC,KAA6B,EAC7B,KAA6B;IAE7B,IAAI,CAAC;QACH,OAAO,MAAM,KAAK,EAAE,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,sCAAsC,CAAC,KAAK,CAAC,CAAC;QAC9C,IAAI,KAAK,YAAY,uBAAuB;YAAE,MAAM,KAAK,CAAC;QAC1D,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CACpB,gDAAgD,YAAY,CAAC,KAAK,CAAC,EAAE,CACtE,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport { errorMessage } from \"../../../data/errorMessage\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport { verifyContainerWriterProjection } from \"../../../data/keyingProjectionVerification/containerProjectionVerification\";\nimport { runWithSecurityIncidentReporting } from \"../../../data/keyingProjectionVerification/error\";\nimport { rethrowProjectionVerificationCancelled } from \"../../../data/keyingProjectionVerification/types\";\nimport { hasUnsettledDocumentPlacement } from \"../../../data/persistence/container-contents/documentPurgePlacement\";\nimport { sqlDocumentsPersistence } from \"../../../data/persistence/documents/documentsPersistence\";\nimport type { ExecSql } from \"../../../data/sqlite/sqlSchema\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../principals/runtimePolicyWarmer\";\nimport type { ContainerContentsPersistence } from \"../containerPersistence\";\nimport type { ContainerState } from \"../remoteHydration\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\nimport { assertLocalPurgeScope } from \"./localPurgeScope\";\nimport { snapshotContainerStates } from \"./purgeTreeCollection\";\nimport { readLocalPurgeScope } from \"./readLocalPurgeScope\";\n\ninterface SubtreePurgeScopeInput {\n containersById: ReadonlyMap<string, ContainerState>;\n persistence: ContainerContentsPersistence;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n rootContainerId: string;\n runtime: ContainerContentsWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}\n\nasync function verifiedRemoteScope(\n input: SubtreePurgeScopeInput,\n current: Pick<ContainerState, \"container\" | \"record\">,\n verifiedAncestorsById: Map<string, readonly string[]>,\n): Promise<boolean> {\n const { runtime, rootContainerId } = input;\n const targetId = current.container.id;\n return runWithSecurityIncidentReporting(\n runtime.util.reportSecurityIncident,\n {\n objectId: targetId,\n objectKind: \"container\",\n operation: \"container.purge.scope\",\n },\n async () => {\n runtime.apiClient.evictContainerWriterProjection(targetId);\n const projection =\n await runtime.apiClient.getContainerWriterProjection(targetId);\n if (!projection || input.stillCurrent?.() === false) return false;\n if (\n projection.containerId !== targetId ||\n projection.organizationId !== current.container.organizationId\n ) {\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"Subtree purge container identity mismatch\",\n );\n }\n const path = await verifyContainerWriterProjection({\n execSql: runtime.infra.execSql,\n projection,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n });\n const rootIndex = path.findIndex(\n (head) => head.state.containerId === rootContainerId,\n );\n const latest = await input.persistence.loadContainerMetadataState(\n runtime.infra.execSql,\n targetId,\n );\n const pendingMoves = await readPendingMoves(input);\n const allowed =\n rootIndex >= 0 &&\n path[rootIndex]?.state.parentContainerId ===\n input.containersById.get(rootContainerId)?.container.parentId &&\n latest?.container.parentId === current.container.parentId &&\n latest.record?.documentId === current.record.documentId &&\n !path\n .slice(rootIndex)\n .some((head) => pendingMoves.has(head.state.containerId)) &&\n input.stillCurrent?.() !== false;\n if (allowed)\n verifiedAncestorsById.set(\n targetId,\n path.slice(rootIndex).map((head) => head.state.containerId),\n );\n return allowed;\n },\n );\n}\n\nasync function allowsContainer(\n input: SubtreePurgeScopeInput,\n containerId: string,\n verifiedAncestorsById: Map<string, readonly string[]>,\n): Promise<boolean> {\n const { runtime, persistence, rootContainerId } = input;\n if (input.stillCurrent?.() === false) return false;\n const execSql = runtime.infra.execSql;\n const pendingMoves = await readPendingMoves(input);\n if (pendingMoves.has(rootContainerId)) return false;\n const seen = new Set<string>([rootContainerId]);\n let currentId: string | null = containerId;\n while (currentId !== rootContainerId) {\n if (!currentId || seen.has(currentId) || pendingMoves.has(currentId))\n return false;\n seen.add(currentId);\n const expected = input.containersById.get(currentId);\n const current = await persistence.loadContainerMetadataState(\n execSql,\n currentId,\n );\n if (\n !expected ||\n !current?.record ||\n current.container.parentId !== expected.container.parentId ||\n current.container.organizationId !== expected.container.organizationId ||\n current.record.documentId !== expected.record.documentId\n )\n return false;\n if (current.record.documentId) {\n if (\n !(await verifiedRemoteScope(\n input,\n {\n container: current.container,\n record: current.record,\n },\n verifiedAncestorsById,\n ))\n )\n return false;\n break;\n }\n currentId = current.container.parentId;\n }\n return readLocalPurgeScope({\n ...input,\n containerId,\n execSql,\n verifiedAncestorsById,\n });\n}\n\nasync function readPendingMoves(input: SubtreePurgeScopeInput) {\n return new Set(\n (\n await input.persistence.listUnsyncedMoveIntents(\n input.runtime.infra.execSql,\n )\n ).map((intent) => intent.containerId),\n );\n}\n\nasync function documentPlacementIsCurrent(\n input: SubtreePurgeScopeInput,\n document: DocumentSummary,\n): Promise<boolean> {\n const execSql = input.runtime.infra.execSql;\n if (await hasUnsettledDocumentPlacement(execSql, document.id)) return false;\n const current = await sqlDocumentsPersistence.loadDocument(\n execSql,\n document.id,\n );\n return (\n current !== null &&\n current.documentId === document.documentId &&\n current.containerId === document.containerId &&\n input.stillCurrent?.() !== false\n );\n}\n\n/** Listing edges only select candidates; each destructive unit proves its scope. */\nexport function createSubtreePurgeScope(request: SubtreePurgeScopeInput) {\n const input = {\n ...request,\n containersById: snapshotContainerStates(request.containersById),\n };\n const verifiedAncestorsById = new Map<string, readonly string[]>();\n return {\n allowsLocalScopeInTransaction: (execSql: ExecSql, containerId: string) =>\n checkAvailableScope(input, async () => {\n await assertLocalPurgeScope({\n ...input,\n execSql,\n containerId,\n verifiedAncestorsById,\n });\n return true;\n }),\n assertLocalScopeInTransaction: (\n execSql: ExecSql,\n containerId: string | null | undefined,\n localId: string,\n ) =>\n assertLocalPurgeScope({\n ...input,\n execSql,\n containerId,\n localId,\n verifiedAncestorsById,\n }),\n allowsContainer: (containerId: string) =>\n checkAvailableScope(input, () =>\n allowsContainer(input, containerId, verifiedAncestorsById),\n ),\n allowsDocument: (document: DocumentSummary) =>\n checkAvailableScope(input, async () => {\n if (\n !document.containerId ||\n !(await documentPlacementIsCurrent(input, document))\n )\n return false;\n if (\n !(await allowsContainer(\n input,\n document.containerId,\n verifiedAncestorsById,\n ))\n )\n return false;\n // Freeze local placement checks together after all remote verification.\n return readLocalPurgeScope(\n {\n ...input,\n containerId: document.containerId,\n execSql: input.runtime.infra.execSql,\n localId: document.id,\n verifiedAncestorsById,\n },\n document,\n );\n }),\n };\n}\n\nasync function checkAvailableScope(\n input: SubtreePurgeScopeInput,\n check: () => Promise<boolean>,\n): Promise<boolean> {\n try {\n return await check();\n } catch (error) {\n rethrowProjectionVerificationCancelled(error);\n if (error instanceof KeyingVerificationError) throw error;\n input.runtime.util.log(\n `Container contents: purge scope unavailable: ${errorMessage(error)}`,\n );\n return false;\n }\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { sqlDocumentsPersistence } from "../../../data/persistence/documents/documentsPersistence.js";
2
+ import { getClientSQLitePersistenceRuntime } from "../../../data/sqlite/sqlitePersistenceRuntime.js";
3
+ import { runSerializedSqlMutation } from "../../../data/sqlite/sqlSchema.js";
4
+ import { assertLocalPurgeScope } from "./localPurgeScope.js";
5
+ /** Final local scope check before HTTP dispatch, with no remote I/O under lock. */
6
+ export function readLocalPurgeScope(input, document) {
7
+ return runSerializedSqlMutation(input.execSql, async (lockedExecSql) => getClientSQLitePersistenceRuntime(lockedExecSql).transaction(async () => {
8
+ await assertLocalPurgeScope({ ...input, execSql: lockedExecSql });
9
+ if (!document)
10
+ return true;
11
+ const current = await sqlDocumentsPersistence.loadDocument(lockedExecSql, document.id);
12
+ return (current !== null &&
13
+ current.documentId === document.documentId &&
14
+ current.containerId === document.containerId &&
15
+ input.stillCurrent?.() !== false);
16
+ }, { behavior: "immediate" }));
17
+ }
18
+ //# sourceMappingURL=readLocalPurgeScope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readLocalPurgeScope.js","sourceRoot":"","sources":["readLocalPurgeScope.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,0DAA0D,CAAC;AACnG,OAAO,EAAE,iCAAiC,EAAE,MAAM,+CAA+C,CAAC;AAClG,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,mFAAmF;AACnF,MAAM,UAAU,mBAAmB,CACjC,KAAkD,EAClD,QAA0B;IAE1B,OAAO,wBAAwB,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,CACrE,iCAAiC,CAAC,aAAa,CAAC,CAAC,WAAW,CAC1D,KAAK,IAAI,EAAE;QACT,MAAM,qBAAqB,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;QAClE,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC3B,MAAM,OAAO,GAAG,MAAM,uBAAuB,CAAC,YAAY,CACxD,aAAa,EACb,QAAQ,CAAC,EAAE,CACZ,CAAC;QACF,OAAO,CACL,OAAO,KAAK,IAAI;YAChB,OAAO,CAAC,UAAU,KAAK,QAAQ,CAAC,UAAU;YAC1C,OAAO,CAAC,WAAW,KAAK,QAAQ,CAAC,WAAW;YAC5C,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK,CACjC,CAAC;IACJ,CAAC,EACD,EAAE,QAAQ,EAAE,WAAW,EAAE,CAC1B,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport { sqlDocumentsPersistence } from \"../../../data/persistence/documents/documentsPersistence\";\nimport { getClientSQLitePersistenceRuntime } from \"../../../data/sqlite/sqlitePersistenceRuntime\";\nimport { runSerializedSqlMutation } from \"../../../data/sqlite/sqlSchema\";\nimport { assertLocalPurgeScope } from \"./localPurgeScope\";\n\n/** Final local scope check before HTTP dispatch, with no remote I/O under lock. */\nexport function readLocalPurgeScope(\n input: Parameters<typeof assertLocalPurgeScope>[0],\n document?: DocumentSummary,\n): Promise<boolean> {\n return runSerializedSqlMutation(input.execSql, async (lockedExecSql) =>\n getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async () => {\n await assertLocalPurgeScope({ ...input, execSql: lockedExecSql });\n if (!document) return true;\n const current = await sqlDocumentsPersistence.loadDocument(\n lockedExecSql,\n document.id,\n );\n return (\n current !== null &&\n current.documentId === document.documentId &&\n current.containerId === document.containerId &&\n input.stillCurrent?.() !== false\n );\n },\n { behavior: \"immediate\" },\n ),\n );\n}\n"]}
@@ -145,6 +145,7 @@ export async function deleteRemoteContainer(input) {
145
145
  const deleteResult = await input.runtime.apiClient.deleteContainerResult(input.containerId, {
146
146
  expectedPaymentRequiredOrganizationId: input.organizationId,
147
147
  reportErrors: false,
148
+ requiredAncestorId: input.requiredAncestorId,
148
149
  });
149
150
  if (!deleteResult.ok && !isContainerNotFoundFailure(deleteResult)) {
150
151
  deleteResult.report();
@@ -1 +1 @@
1
- {"version":3,"file":"remote.js","sourceRoot":"","sources":["remote.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContainerSystemSlot } from \"@tearleads/validators/containerSystemSlot\";\nimport type {\n ContainerMutationResponse,\n ContainerWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { isContainerNotFoundFailure } from \"../../../data/containers/shared/mutationFailures\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport { ProjectionDependencyUnavailableError } from \"../../../data/keyingProjectionVerification/dependencyUnavailable\";\nimport {\n moveRemoteContainer as moveRemoteContainerMutation,\n readContainerMutationMetadataDocumentId,\n referencedPrincipalHeadsFromContainerMutationResponse,\n shareRemoteContainer as shareRemoteContainerMutation,\n} from \"../../containers\";\nimport { createRuntimeCurrentGroupShare } from \"../../containers/child/runtimeCurrentGroupShare\";\nimport { resolveDocumentCreateAuthor } from \"../../documents\";\nimport { createRuntimeGroupMetadataAccess } from \"../../organizations/groupMetadataRuntime\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../principals/runtimePolicyWarmer\";\nimport {\n type ContainerAlreadyCommitted,\n createRemoteContainerWithMetadataDocument,\n} from \"./createWithMetadata\";\nimport type {\n ContainerWorkflowRuntime,\n CreatedRemoteContainerState,\n RemoteContainer,\n SharedRemoteContainerState,\n} from \"./types\";\n\n/**\n * Resolve the author, api client, executor, and encapsulation secret a remote\n * container mutation needs, logging a skip for the given operation when the\n * writer context is unavailable.\n */\nfunction resolveContainerWriterContext(\n runtime: ContainerWorkflowRuntime,\n operation:\n | \"container create\"\n | \"container group share\"\n | \"container move\"\n | \"container share\",\n) {\n const author = resolveDocumentCreateAuthor(runtime);\n const secretKey = runtime.crypto.encapsulationKeyPair?.secretKey;\n if (!author || !secretKey) {\n runtime.util.log(\n `Container contents: skipped ${operation} because the writer context is unavailable.`,\n );\n return null;\n }\n\n return {\n apiClient: runtime.apiClient,\n author,\n execSql: runtime.infra.execSql,\n secretKey,\n };\n}\n\nexport async function createRemoteContainer(input: {\n systemSlot?: ContainerSystemSlot | null | undefined;\n containerId: string;\n parentContainerId: string;\n parentProjection?: ContainerWriterProjectionResponse | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<CreatedRemoteContainerState | ContainerAlreadyCommitted | null> {\n return createRemoteContainerWithMetadataDocument(input);\n}\n\nfunction sharedRemoteContainerStateFromMutation(\n previousProjection: ContainerWriterProjectionResponse,\n response: ContainerMutationResponse,\n): SharedRemoteContainerState {\n if (\n response.containerId !== previousProjection.containerId ||\n response.organizationId !== previousProjection.organizationId\n )\n throw new Error(\"Container mutation response projection mismatch\");\n return {\n accessManifestHash: response.manifestHead.manifestHash,\n accessEpoch: response.manifestHead.epoch,\n createdAt: response.createdAt,\n metadataDocumentId: readContainerMutationMetadataDocumentId({ response }),\n referencedPrincipalHeads:\n referencedPrincipalHeadsFromContainerMutationResponse({ response }),\n updatedAt: response.updatedAt,\n // The next read obtains the post-commit proof. A failed read cannot undo\n // acknowledgement of this successful mutation.\n writerProjection: null,\n };\n}\n\nexport async function shareRemoteContainer(input: {\n accessLevel: \"read\" | \"write\" | \"admin\";\n containerId: string;\n previousProjection: ContainerWriterProjectionResponse;\n recipientUserId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<SharedRemoteContainerState | null> {\n const writer = resolveContainerWriterContext(\n input.runtime,\n \"container share\",\n );\n if (!writer) {\n return null;\n }\n const { apiClient, author, execSql, secretKey: targetSecretKey } = writer;\n\n const shared = await shareRemoteContainerMutation({\n reportSecurityIncident: input.runtime.util.reportSecurityIncident,\n accessLevel: input.accessLevel,\n apiClient,\n author,\n containerId: input.containerId,\n execSql,\n previousProjection: input.previousProjection,\n recipientUserId: input.recipientUserId,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n resolveTrustedUserIdentity: input.runtime.resolveTrustedUserIdentity,\n stillCurrent: input.stillCurrent,\n targetSecretKey,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n if (!shared) {\n return null;\n }\n\n return sharedRemoteContainerStateFromMutation(\n input.previousProjection,\n shared.response,\n );\n}\n\nexport async function shareRemoteContainerWithGroup(input: {\n accessLevel: \"read\" | \"write\" | \"admin\";\n containerId: string;\n expectedGroupName?: string | undefined;\n knownContainerKeks?: ReadonlyMap<string, Uint8Array> | undefined;\n previousProjection: ContainerWriterProjectionResponse;\n recipientGroupId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<SharedRemoteContainerState | null> {\n const writer = resolveContainerWriterContext(\n input.runtime,\n \"container group share\",\n );\n if (!writer) {\n return null;\n }\n const { apiClient, author, execSql, secretKey: targetSecretKey } = writer;\n\n const shareWithGroup = createRuntimeCurrentGroupShare(input.runtime);\n if (!shareWithGroup)\n throw new ProjectionDependencyUnavailableError(\n \"Group sharing requires private paged recovery\",\n );\n const shared = await shareWithGroup({\n reportSecurityIncident: input.runtime.util.reportSecurityIncident,\n accessLevel: input.accessLevel,\n apiClient,\n author,\n containerId: input.containerId,\n execSql,\n expectedGroupName: input.expectedGroupName ?? null,\n readEncryptedName: (bundle) =>\n createRuntimeGroupMetadataAccess(\n input.runtime,\n author.organizationId,\n input.stillCurrent,\n ).readName(bundle),\n knownContainerKeks: input.knownContainerKeks,\n previousProjection: input.previousProjection,\n recipientGroupId: input.recipientGroupId,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n resolveTrustedUserIdentity: input.runtime.resolveTrustedUserIdentity,\n signingKeyPair: input.runtime.crypto.signingKeyPair,\n stillCurrent: input.stillCurrent,\n targetSecretKey,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n if (!shared) {\n return null;\n }\n\n return sharedRemoteContainerStateFromMutation(\n input.previousProjection,\n shared.response,\n );\n}\n\nexport async function moveRemoteContainer(input: {\n containerId: string;\n parentContainerId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<RemoteContainer | null> {\n const writer = resolveContainerWriterContext(input.runtime, \"container move\");\n if (!writer) {\n return null;\n }\n const { apiClient, author, execSql, secretKey: targetSecretKey } = writer;\n\n const moved = await moveRemoteContainerMutation({\n reportSecurityIncident: input.runtime.util.reportSecurityIncident,\n apiClient,\n author,\n containerId: input.containerId,\n destinationParentContainerId: input.parentContainerId,\n execSql,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n targetSecretKey,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n if (!moved) {\n return null;\n }\n\n return {\n createdAt: moved.response.createdAt,\n effectiveAccessLevel: \"admin\",\n id: moved.response.containerId,\n organizationId: moved.response.organizationId,\n parentId: moved.response.parentId,\n metadataDocumentId: readContainerMutationMetadataDocumentId({\n response: moved.response,\n }),\n metadataAccessEpoch: moved.response.manifestHead.epoch,\n metadataAccessStateHash: moved.response.manifestHead.manifestHash,\n metadataReferencedPrincipals:\n referencedPrincipalHeadsFromContainerMutationResponse({\n response: moved.response,\n }),\n updatedAt: moved.response.updatedAt,\n };\n}\n\nexport async function deleteRemoteContainer(input: {\n containerId: string;\n organizationId: string;\n runtime: ContainerWorkflowRuntime;\n}): Promise<{ deletedAt: string } | null> {\n const deleteResult = await input.runtime.apiClient.deleteContainerResult(\n input.containerId,\n {\n expectedPaymentRequiredOrganizationId: input.organizationId,\n reportErrors: false,\n },\n );\n if (!deleteResult.ok && !isContainerNotFoundFailure(deleteResult)) {\n deleteResult.report();\n return null;\n }\n\n if (!deleteResult.ok) {\n // A 404 proves this immutable id is permanently absent, but it does not\n // carry the server deletion time. A maximal fence prevents an older\n // in-flight listing page from resurrecting it.\n return { deletedAt: \"9999-12-31T23:59:59.999Z\" };\n }\n\n return { deletedAt: deleteResult.data.deletedAt };\n}\n"]}
1
+ {"version":3,"file":"remote.js","sourceRoot":"","sources":["remote.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContainerSystemSlot } from \"@tearleads/validators/containerSystemSlot\";\nimport type {\n ContainerMutationResponse,\n ContainerWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { isContainerNotFoundFailure } from \"../../../data/containers/shared/mutationFailures\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport { ProjectionDependencyUnavailableError } from \"../../../data/keyingProjectionVerification/dependencyUnavailable\";\nimport {\n moveRemoteContainer as moveRemoteContainerMutation,\n readContainerMutationMetadataDocumentId,\n referencedPrincipalHeadsFromContainerMutationResponse,\n shareRemoteContainer as shareRemoteContainerMutation,\n} from \"../../containers\";\nimport { createRuntimeCurrentGroupShare } from \"../../containers/child/runtimeCurrentGroupShare\";\nimport { resolveDocumentCreateAuthor } from \"../../documents\";\nimport { createRuntimeGroupMetadataAccess } from \"../../organizations/groupMetadataRuntime\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../principals/runtimePolicyWarmer\";\nimport {\n type ContainerAlreadyCommitted,\n createRemoteContainerWithMetadataDocument,\n} from \"./createWithMetadata\";\nimport type {\n ContainerWorkflowRuntime,\n CreatedRemoteContainerState,\n RemoteContainer,\n SharedRemoteContainerState,\n} from \"./types\";\n\n/**\n * Resolve the author, api client, executor, and encapsulation secret a remote\n * container mutation needs, logging a skip for the given operation when the\n * writer context is unavailable.\n */\nfunction resolveContainerWriterContext(\n runtime: ContainerWorkflowRuntime,\n operation:\n | \"container create\"\n | \"container group share\"\n | \"container move\"\n | \"container share\",\n) {\n const author = resolveDocumentCreateAuthor(runtime);\n const secretKey = runtime.crypto.encapsulationKeyPair?.secretKey;\n if (!author || !secretKey) {\n runtime.util.log(\n `Container contents: skipped ${operation} because the writer context is unavailable.`,\n );\n return null;\n }\n\n return {\n apiClient: runtime.apiClient,\n author,\n execSql: runtime.infra.execSql,\n secretKey,\n };\n}\n\nexport async function createRemoteContainer(input: {\n systemSlot?: ContainerSystemSlot | null | undefined;\n containerId: string;\n parentContainerId: string;\n parentProjection?: ContainerWriterProjectionResponse | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<CreatedRemoteContainerState | ContainerAlreadyCommitted | null> {\n return createRemoteContainerWithMetadataDocument(input);\n}\n\nfunction sharedRemoteContainerStateFromMutation(\n previousProjection: ContainerWriterProjectionResponse,\n response: ContainerMutationResponse,\n): SharedRemoteContainerState {\n if (\n response.containerId !== previousProjection.containerId ||\n response.organizationId !== previousProjection.organizationId\n )\n throw new Error(\"Container mutation response projection mismatch\");\n return {\n accessManifestHash: response.manifestHead.manifestHash,\n accessEpoch: response.manifestHead.epoch,\n createdAt: response.createdAt,\n metadataDocumentId: readContainerMutationMetadataDocumentId({ response }),\n referencedPrincipalHeads:\n referencedPrincipalHeadsFromContainerMutationResponse({ response }),\n updatedAt: response.updatedAt,\n // The next read obtains the post-commit proof. A failed read cannot undo\n // acknowledgement of this successful mutation.\n writerProjection: null,\n };\n}\n\nexport async function shareRemoteContainer(input: {\n accessLevel: \"read\" | \"write\" | \"admin\";\n containerId: string;\n previousProjection: ContainerWriterProjectionResponse;\n recipientUserId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<SharedRemoteContainerState | null> {\n const writer = resolveContainerWriterContext(\n input.runtime,\n \"container share\",\n );\n if (!writer) {\n return null;\n }\n const { apiClient, author, execSql, secretKey: targetSecretKey } = writer;\n\n const shared = await shareRemoteContainerMutation({\n reportSecurityIncident: input.runtime.util.reportSecurityIncident,\n accessLevel: input.accessLevel,\n apiClient,\n author,\n containerId: input.containerId,\n execSql,\n previousProjection: input.previousProjection,\n recipientUserId: input.recipientUserId,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n resolveTrustedUserIdentity: input.runtime.resolveTrustedUserIdentity,\n stillCurrent: input.stillCurrent,\n targetSecretKey,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n if (!shared) {\n return null;\n }\n\n return sharedRemoteContainerStateFromMutation(\n input.previousProjection,\n shared.response,\n );\n}\n\nexport async function shareRemoteContainerWithGroup(input: {\n accessLevel: \"read\" | \"write\" | \"admin\";\n containerId: string;\n expectedGroupName?: string | undefined;\n knownContainerKeks?: ReadonlyMap<string, Uint8Array> | undefined;\n previousProjection: ContainerWriterProjectionResponse;\n recipientGroupId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<SharedRemoteContainerState | null> {\n const writer = resolveContainerWriterContext(\n input.runtime,\n \"container group share\",\n );\n if (!writer) {\n return null;\n }\n const { apiClient, author, execSql, secretKey: targetSecretKey } = writer;\n\n const shareWithGroup = createRuntimeCurrentGroupShare(input.runtime);\n if (!shareWithGroup)\n throw new ProjectionDependencyUnavailableError(\n \"Group sharing requires private paged recovery\",\n );\n const shared = await shareWithGroup({\n reportSecurityIncident: input.runtime.util.reportSecurityIncident,\n accessLevel: input.accessLevel,\n apiClient,\n author,\n containerId: input.containerId,\n execSql,\n expectedGroupName: input.expectedGroupName ?? null,\n readEncryptedName: (bundle) =>\n createRuntimeGroupMetadataAccess(\n input.runtime,\n author.organizationId,\n input.stillCurrent,\n ).readName(bundle),\n knownContainerKeks: input.knownContainerKeks,\n previousProjection: input.previousProjection,\n recipientGroupId: input.recipientGroupId,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n resolveTrustedUserIdentity: input.runtime.resolveTrustedUserIdentity,\n signingKeyPair: input.runtime.crypto.signingKeyPair,\n stillCurrent: input.stillCurrent,\n targetSecretKey,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n if (!shared) {\n return null;\n }\n\n return sharedRemoteContainerStateFromMutation(\n input.previousProjection,\n shared.response,\n );\n}\n\nexport async function moveRemoteContainer(input: {\n containerId: string;\n parentContainerId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<RemoteContainer | null> {\n const writer = resolveContainerWriterContext(input.runtime, \"container move\");\n if (!writer) {\n return null;\n }\n const { apiClient, author, execSql, secretKey: targetSecretKey } = writer;\n\n const moved = await moveRemoteContainerMutation({\n reportSecurityIncident: input.runtime.util.reportSecurityIncident,\n apiClient,\n author,\n containerId: input.containerId,\n destinationParentContainerId: input.parentContainerId,\n execSql,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n targetSecretKey,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n if (!moved) {\n return null;\n }\n\n return {\n createdAt: moved.response.createdAt,\n effectiveAccessLevel: \"admin\",\n id: moved.response.containerId,\n organizationId: moved.response.organizationId,\n parentId: moved.response.parentId,\n metadataDocumentId: readContainerMutationMetadataDocumentId({\n response: moved.response,\n }),\n metadataAccessEpoch: moved.response.manifestHead.epoch,\n metadataAccessStateHash: moved.response.manifestHead.manifestHash,\n metadataReferencedPrincipals:\n referencedPrincipalHeadsFromContainerMutationResponse({\n response: moved.response,\n }),\n updatedAt: moved.response.updatedAt,\n };\n}\n\nexport async function deleteRemoteContainer(input: {\n containerId: string;\n organizationId: string;\n requiredAncestorId?: string | undefined;\n runtime: ContainerWorkflowRuntime;\n}): Promise<{ deletedAt: string } | null> {\n const deleteResult = await input.runtime.apiClient.deleteContainerResult(\n input.containerId,\n {\n expectedPaymentRequiredOrganizationId: input.organizationId,\n reportErrors: false,\n requiredAncestorId: input.requiredAncestorId,\n },\n );\n if (!deleteResult.ok && !isContainerNotFoundFailure(deleteResult)) {\n deleteResult.report();\n return null;\n }\n\n if (!deleteResult.ok) {\n // A 404 proves this immutable id is permanently absent, but it does not\n // carry the server deletion time. A maximal fence prevents an older\n // in-flight listing page from resurrecting it.\n return { deletedAt: \"9999-12-31T23:59:59.999Z\" };\n }\n\n return { deletedAt: deleteResult.data.deletedAt };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ApiClient } from \"@tearleads/api-client\";\nimport type { ContainerSystemSlot } from \"@tearleads/validators/containerSystemSlot\";\nimport type {\n ContainerDeleteResponse,\n ContainerWriterProjectionResponse,\n PrincipalPolicyBundleResponse,\n ReferencedPrincipalStateResponse,\n} from \"@tearleads/validators/response\";\nimport type { createInitializedContainerMetadataDocument } from \"../../../data/containers/containerMetadataDocument\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport type { DocumentRecord } from \"../../../data/sqlite/documentPersistence\";\nimport type {\n createRemoteContainer,\n moveRemoteContainer,\n shareRemoteContainer,\n shareRemoteContainerWithGroup,\n} from \"../../containers\";\nimport type { createRemoteDocument } from \"../../documents\";\nimport type {\n ContainerContentsPersistence,\n ContainerCreateIntentRecord,\n ContainerMoveIntentRecord,\n} from \"../containerPersistence\";\nimport type {\n ContainerState,\n RemoteContainer as HydratedRemoteContainer,\n RemoteContainerHydrationHost,\n} from \"../remoteHydration\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\n\nexport type ContainerWorkflowApi = Parameters<\n typeof createRemoteContainer\n>[0][\"apiClient\"] &\n Parameters<typeof shareRemoteContainer>[0][\"apiClient\"] &\n Parameters<typeof shareRemoteContainerWithGroup>[0][\"apiClient\"] &\n Parameters<typeof moveRemoteContainer>[0][\"apiClient\"] &\n Parameters<typeof createRemoteDocument>[0][\"apiClient\"] &\n Partial<\n Pick<\n ApiClient,\n \"getPrincipalPolicyPages\" | \"getProjectionPolicyHistoryPages\"\n >\n > & {\n deleteContainerResult(\n containerId: string,\n options?: {\n expectedPaymentRequiredOrganizationId?: string;\n reportErrors?: boolean;\n },\n ): Promise<\n | { data: ContainerDeleteResponse; ok: true }\n | {\n ok: false;\n report: () => void;\n status: number | null;\n }\n >;\n primeContainerWriterProjection(\n containerId: string,\n projection: ContainerWriterProjectionResponse,\n ): void;\n getCurrentPrincipalPolicy(\n principalType: \"group\" | \"organization\",\n principalId: string,\n ): Promise<PrincipalPolicyBundleResponse | null>;\n };\n\nexport interface ContainerWorkflowRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n readonly apiClient: ContainerWorkflowApi;\n}\n\nexport interface CreatedRemoteContainerState {\n accessManifestHash: string;\n /** The slot this client signed into the create; never the server's echo. */\n systemSlot: ContainerSystemSlot | null;\n containerId: string;\n createdAt: string;\n metadataDocumentId: string;\n organizationId: string;\n /** A created container is a child; the acknowledgement echoed this parent. */\n parentId: string;\n persistedMetadataState: Pick<\n DocumentRecord,\n | \"documentId\"\n | \"contentKeyBundle\"\n | \"documentKekTargets\"\n | \"documentManifestBundle\"\n >;\n updatedAt: string;\n}\n\nexport interface SharedRemoteContainerState {\n accessEpoch: number;\n accessManifestHash: string;\n createdAt: string;\n metadataDocumentId: string;\n referencedPrincipalHeads: ReferencedPrincipalStateResponse[];\n updatedAt: string;\n writerProjection: ContainerWriterProjectionResponse | null;\n}\n\nexport interface SharedContainerState {\n container: ContainerState[\"container\"];\n record: ContainerState[\"record\"];\n}\n\nexport type SharedContainerStateResult =\n | { status: \"missing\" }\n | { status: \"confirmed\" }\n | (SharedContainerState & {\n status: \"identity-superseded\" | \"persisted\";\n });\n\nexport type RemoteContainer = HydratedRemoteContainer;\n\nexport type ContainerMetadataDocumentState = Awaited<\n ReturnType<typeof createInitializedContainerMetadataDocument>\n>[\"doc\"];\n\nexport interface CreatedChildContainerState {\n containerState: ContainerState;\n initialUpdate: Uint8Array;\n shouldRequestSync: boolean;\n}\n\nexport interface ContainerIntentSyncState {\n containersById: ReadonlyMap<string, ContainerState>;\n persistence: ContainerContentsPersistence;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n /**\n * Verifies that a listed container is this device's pending create before it\n * is adopted and returns where it sits; `assertContainerCreateAdoptable`\n * unless a test replaces it.\n */\n verifyCreateAdoption?: (input: {\n readonly containerId: string;\n readonly expectedOrganizationId: string;\n }) => Promise<AdoptedContainerPlacement>;\n}\n\n/** Where a verified listed create committed, and where its container is now. */\nexport interface AdoptedContainerPlacement {\n /** The parent its signed epoch-1 `container.create` named. */\n readonly createdParentId: string;\n /** The verified head's parent; another writer may have moved it since. */\n readonly currentParentId: string;\n}\n\nexport type ContainerCreateIntentSyncState = ContainerIntentSyncState;\n\nexport type ContainerCreateIntentSyncHost = Pick<\n RemoteContainerHydrationHost,\n \"persistContainerState\"\n>;\n\nexport interface ContainerCreateIntentSyncInput {\n host: ContainerCreateIntentSyncHost;\n /** A parent whose own create has not settled takes no child create. */\n isCreatePending: (containerId: string) => boolean;\n isCurrent: () => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n intent: ContainerCreateIntentRecord;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n state: ContainerCreateIntentSyncState;\n}\n\nexport type ContainerMoveIntentSyncState = ContainerIntentSyncState;\n\nexport type ContainerMoveIntentSyncHost = Pick<\n RemoteContainerHydrationHost,\n \"persistContainerState\" | \"updateSnapshot\"\n>;\n\nexport interface ContainerMoveIntentSyncInput {\n host: ContainerMoveIntentSyncHost;\n isCurrent: () => boolean;\n /** A folder whose create has not settled takes no structural writes. */\n isCreatePending: (containerId: string) => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n intent: ContainerMoveIntentRecord;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n state: ContainerMoveIntentSyncState;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ApiClient } from \"@tearleads/api-client\";\nimport type { ContainerSystemSlot } from \"@tearleads/validators/containerSystemSlot\";\nimport type {\n ContainerDeleteResponse,\n ContainerWriterProjectionResponse,\n PrincipalPolicyBundleResponse,\n ReferencedPrincipalStateResponse,\n} from \"@tearleads/validators/response\";\nimport type { createInitializedContainerMetadataDocument } from \"../../../data/containers/containerMetadataDocument\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport type { DocumentRecord } from \"../../../data/sqlite/documentPersistence\";\nimport type {\n createRemoteContainer,\n moveRemoteContainer,\n shareRemoteContainer,\n shareRemoteContainerWithGroup,\n} from \"../../containers\";\nimport type { createRemoteDocument } from \"../../documents\";\nimport type {\n ContainerContentsPersistence,\n ContainerCreateIntentRecord,\n ContainerMoveIntentRecord,\n} from \"../containerPersistence\";\nimport type {\n ContainerState,\n RemoteContainer as HydratedRemoteContainer,\n RemoteContainerHydrationHost,\n} from \"../remoteHydration\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\n\nexport type ContainerWorkflowApi = Parameters<\n typeof createRemoteContainer\n>[0][\"apiClient\"] &\n Parameters<typeof shareRemoteContainer>[0][\"apiClient\"] &\n Parameters<typeof shareRemoteContainerWithGroup>[0][\"apiClient\"] &\n Parameters<typeof moveRemoteContainer>[0][\"apiClient\"] &\n Parameters<typeof createRemoteDocument>[0][\"apiClient\"] &\n Partial<\n Pick<\n ApiClient,\n \"getPrincipalPolicyPages\" | \"getProjectionPolicyHistoryPages\"\n >\n > & {\n deleteContainerResult(\n containerId: string,\n options?: {\n expectedPaymentRequiredOrganizationId?: string;\n reportErrors?: boolean;\n requiredAncestorId?: string | undefined;\n },\n ): Promise<\n | { data: ContainerDeleteResponse; ok: true }\n | {\n ok: false;\n report: () => void;\n status: number | null;\n }\n >;\n primeContainerWriterProjection(\n containerId: string,\n projection: ContainerWriterProjectionResponse,\n ): void;\n getCurrentPrincipalPolicy(\n principalType: \"group\" | \"organization\",\n principalId: string,\n ): Promise<PrincipalPolicyBundleResponse | null>;\n };\n\nexport interface ContainerWorkflowRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n readonly apiClient: ContainerWorkflowApi;\n}\n\nexport interface CreatedRemoteContainerState {\n accessManifestHash: string;\n /** The slot this client signed into the create; never the server's echo. */\n systemSlot: ContainerSystemSlot | null;\n containerId: string;\n createdAt: string;\n metadataDocumentId: string;\n organizationId: string;\n /** A created container is a child; the acknowledgement echoed this parent. */\n parentId: string;\n persistedMetadataState: Pick<\n DocumentRecord,\n | \"documentId\"\n | \"contentKeyBundle\"\n | \"documentKekTargets\"\n | \"documentManifestBundle\"\n >;\n updatedAt: string;\n}\n\nexport interface SharedRemoteContainerState {\n accessEpoch: number;\n accessManifestHash: string;\n createdAt: string;\n metadataDocumentId: string;\n referencedPrincipalHeads: ReferencedPrincipalStateResponse[];\n updatedAt: string;\n writerProjection: ContainerWriterProjectionResponse | null;\n}\n\nexport interface SharedContainerState {\n container: ContainerState[\"container\"];\n record: ContainerState[\"record\"];\n}\n\nexport type SharedContainerStateResult =\n | { status: \"missing\" }\n | { status: \"confirmed\" }\n | (SharedContainerState & {\n status: \"identity-superseded\" | \"persisted\";\n });\n\nexport type RemoteContainer = HydratedRemoteContainer;\n\nexport type ContainerMetadataDocumentState = Awaited<\n ReturnType<typeof createInitializedContainerMetadataDocument>\n>[\"doc\"];\n\nexport interface CreatedChildContainerState {\n containerState: ContainerState;\n initialUpdate: Uint8Array;\n shouldRequestSync: boolean;\n}\n\nexport interface ContainerIntentSyncState {\n containersById: ReadonlyMap<string, ContainerState>;\n persistence: ContainerContentsPersistence;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n /**\n * Verifies that a listed container is this device's pending create before it\n * is adopted and returns where it sits; `assertContainerCreateAdoptable`\n * unless a test replaces it.\n */\n verifyCreateAdoption?: (input: {\n readonly containerId: string;\n readonly expectedOrganizationId: string;\n }) => Promise<AdoptedContainerPlacement>;\n}\n\n/** Where a verified listed create committed, and where its container is now. */\nexport interface AdoptedContainerPlacement {\n /** The parent its signed epoch-1 `container.create` named. */\n readonly createdParentId: string;\n /** The verified head's parent; another writer may have moved it since. */\n readonly currentParentId: string;\n}\n\nexport type ContainerCreateIntentSyncState = ContainerIntentSyncState;\n\nexport type ContainerCreateIntentSyncHost = Pick<\n RemoteContainerHydrationHost,\n \"persistContainerState\"\n>;\n\nexport interface ContainerCreateIntentSyncInput {\n host: ContainerCreateIntentSyncHost;\n /** A parent whose own create has not settled takes no child create. */\n isCreatePending: (containerId: string) => boolean;\n isCurrent: () => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n intent: ContainerCreateIntentRecord;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n state: ContainerCreateIntentSyncState;\n}\n\nexport type ContainerMoveIntentSyncState = ContainerIntentSyncState;\n\nexport type ContainerMoveIntentSyncHost = Pick<\n RemoteContainerHydrationHost,\n \"persistContainerState\" | \"updateSnapshot\"\n>;\n\nexport interface ContainerMoveIntentSyncInput {\n host: ContainerMoveIntentSyncHost;\n isCurrent: () => boolean;\n /** A folder whose create has not settled takes no structural writes. */\n isCreatePending: (containerId: string) => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n intent: ContainerMoveIntentRecord;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n state: ContainerMoveIntentSyncState;\n}\n"]}
@@ -22,7 +22,9 @@ export declare function initializeDocumentLinksSchema(execSql: ExecSql): Promise
22
22
  export declare function listDocumentLinkedContainerIds(execSql: ExecSql, documentId: string): Promise<ReadonlyArray<string>>;
23
23
  export declare function replaceDocumentLinks(execSql: ExecSql, documentId: string, containerIds: ReadonlyArray<string>): Promise<void>;
24
24
  export declare function relinkRemoteContainerDocument(input: {
25
+ beforeSubmit?: (() => Promise<boolean>) | undefined;
25
26
  documentId: string;
27
+ expectedSubtreeRootId?: string | undefined;
26
28
  isCurrent?: (() => boolean) | undefined;
27
29
  noteId: string;
28
30
  onFailure?: DocumentLinkSetFailureHandler | undefined;
@@ -33,7 +35,9 @@ export declare function relinkRemoteContainerDocument(input: {
33
35
  targetContainerId: string;
34
36
  }): Promise<RelinkRemoteDocumentResult | null>;
35
37
  export declare function unlinkRemoteContainerDocument(input: {
38
+ beforeSubmit?: (() => Promise<boolean>) | undefined;
36
39
  documentId: string;
40
+ expectedSubtreeRootId?: string | undefined;
37
41
  isCurrent?: (() => boolean) | undefined;
38
42
  noteId: string;
39
43
  onFailure?: DocumentLinkSetFailureHandler | undefined;
@@ -104,9 +104,11 @@ export async function relinkRemoteContainerDocument(input) {
104
104
  }
105
105
  try {
106
106
  const result = await relinkRemoteDocument({
107
+ beforeSubmit: input.beforeSubmit,
107
108
  apiClient: runtime.apiClient,
108
109
  author,
109
110
  documentId,
111
+ expectedSubtreeRootId: input.expectedSubtreeRootId,
110
112
  execSql,
111
113
  onFailure: input.onFailure,
112
114
  operation,