@tearleads/client-sdk 0.1.81 → 0.1.84

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 (160) hide show
  1. package/client/organizations/organizationReadModels.js +4 -2
  2. package/client/organizations/organizationReadModels.js.map +1 -1
  3. package/data/documents/documentSync.js.map +1 -1
  4. package/data/documents/shared/crypto.js.map +1 -1
  5. package/data/documents/shared/types.d.ts +1 -1
  6. package/data/documents/shared/types.js.map +1 -1
  7. package/data/persistence/container-contents/containerContentsPersistence.js +2 -2
  8. package/data/persistence/container-contents/containerContentsPersistence.js.map +1 -1
  9. package/data/persistence/container-contents/containerContentsPersistenceTypes.d.ts +1 -1
  10. package/data/persistence/container-contents/containerContentsPersistenceTypes.js.map +1 -1
  11. package/data/persistence/container-contents/containerDeletionPersistence.js +1 -1
  12. package/data/persistence/container-contents/containerDeletionPersistence.js.map +1 -1
  13. package/data/persistence/container-contents/containerMetadataMutationPersistence.js +3 -3
  14. package/data/persistence/container-contents/containerMetadataMutationPersistence.js.map +1 -1
  15. package/data/persistence/container-contents/containerMetadataPullContinuationPersistence.js +1 -1
  16. package/data/persistence/container-contents/containerMetadataPullContinuationPersistence.js.map +1 -1
  17. package/data/persistence/container-contents/containerMetadataRows.js +1 -1
  18. package/data/persistence/container-contents/containerMetadataRows.js.map +1 -1
  19. package/data/persistence/container-contents/containerPendingUpdatePersistence.js +2 -2
  20. package/data/persistence/container-contents/containerPendingUpdatePersistence.js.map +1 -1
  21. package/data/persistence/container-contents/containerReconcilePersistence.js +1 -1
  22. package/data/persistence/container-contents/containerReconcilePersistence.js.map +1 -1
  23. package/data/persistence/container-contents/containerRemovalWork.js +1 -1
  24. package/data/persistence/container-contents/containerRemovalWork.js.map +1 -1
  25. package/data/persistence/documents/documentDiscoveryReset.d.ts +6 -0
  26. package/data/persistence/documents/documentsPersistence.js +2 -2
  27. package/data/persistence/documents/documentsPersistence.js.map +1 -1
  28. package/data/persistence/documents/internal/createDocumentWithHistoryCheckpoint.js +1 -1
  29. package/data/persistence/documents/internal/createDocumentWithHistoryCheckpoint.js.map +1 -1
  30. package/data/persistence/documents/internal/discardDocument.js +2 -2
  31. package/data/persistence/documents/internal/discardDocument.js.map +1 -1
  32. package/data/persistence/documents/internal/documentDeletionPersistence.js +2 -2
  33. package/data/persistence/documents/internal/documentDeletionPersistence.js.map +1 -1
  34. package/data/persistence/documents/internal/documentMutationPersistence.js +3 -3
  35. package/data/persistence/documents/internal/documentMutationPersistence.js.map +1 -1
  36. package/data/persistence/documents/internal/documentPullContinuationPersistence.js +1 -1
  37. package/data/persistence/documents/internal/documentPullContinuationPersistence.js.map +1 -1
  38. package/data/persistence/documents/internal/documentRecordIdentity.js.map +1 -1
  39. package/data/persistence/documents/internal/documentRows.js.map +1 -1
  40. package/data/persistence/documents/internal/ensureDocumentsSchema.js +1 -1
  41. package/data/persistence/documents/internal/ensureDocumentsSchema.js.map +1 -1
  42. package/data/persistence/documents/internal/orphanSideRows.js +1 -1
  43. package/data/persistence/documents/internal/orphanSideRows.js.map +1 -1
  44. package/data/persistence/documents/internal/rowQueryPersistence.js +1 -1
  45. package/data/persistence/documents/internal/rowQueryPersistence.js.map +1 -1
  46. package/data/persistence/documents/internal/syncQueuePersistence.js +2 -2
  47. package/data/persistence/documents/internal/syncQueuePersistence.js.map +1 -1
  48. package/data/{sqlite/documentPersistence → persistence/documents/records}/documentHistoryPersistence.d.ts +1 -1
  49. package/data/{sqlite/documentPersistence → persistence/documents/records}/documentHistoryPersistence.js +5 -5
  50. package/data/persistence/documents/records/documentHistoryPersistence.js.map +1 -0
  51. package/data/{sqlite/documentPersistence → persistence/documents/records}/documentPendingUpdatePersistence.d.ts +2 -2
  52. package/data/{sqlite/documentPersistence → persistence/documents/records}/documentPendingUpdatePersistence.js +2 -2
  53. package/data/persistence/documents/records/documentPendingUpdatePersistence.js.map +1 -0
  54. package/data/{sqlite/documentPersistence → persistence/documents/records}/documentPersistenceTypes.d.ts +2 -2
  55. package/data/persistence/documents/records/documentPersistenceTypes.js.map +1 -0
  56. package/data/{sqlite/documentPersistence → persistence/documents/records}/documentRecordPersistence.d.ts +1 -1
  57. package/data/{sqlite/documentPersistence → persistence/documents/records}/documentRecordPersistence.js +4 -4
  58. package/data/persistence/documents/records/documentRecordPersistence.js.map +1 -0
  59. package/data/{sqlite/documentPersistence → persistence/documents/records}/documentSyncFailurePersistence.d.ts +2 -2
  60. package/data/{sqlite/documentPersistence → persistence/documents/records}/documentSyncFailurePersistence.js +3 -3
  61. package/data/persistence/documents/records/documentSyncFailurePersistence.js.map +1 -0
  62. package/data/{sqlite/documentPersistence → persistence/documents/records}/index.d.ts +2 -2
  63. package/data/{sqlite/documentPersistence → persistence/documents/records}/index.js +4 -4
  64. package/data/persistence/documents/records/index.js.map +1 -0
  65. package/data/persistence/documents/types.d.ts +2 -2
  66. package/data/persistence/documents/types.js.map +1 -1
  67. package/{workflows/sync/remoteResetPrincipalScope.d.ts → data/persistence/principalPolicy/principalPolicyRemoteReset.d.ts} +1 -1
  68. package/{workflows/sync/remoteResetPrincipalScope.js → data/persistence/principalPolicy/principalPolicyRemoteReset.js} +7 -7
  69. package/data/persistence/principalPolicy/principalPolicyRemoteReset.js.map +1 -0
  70. package/data/persistence/verifiedManifestBundlePersistence.d.ts +2 -0
  71. package/data/persistence/verifiedManifestBundlePersistence.js +7 -0
  72. package/data/persistence/verifiedManifestBundlePersistence.js.map +1 -1
  73. package/data/sqlite/{documentPersistence/documentOrphanBlobReclaims.js → schema/documentOrphanBlobReclaimSchema.js} +1 -1
  74. package/data/sqlite/schema/documentOrphanBlobReclaimSchema.js.map +1 -0
  75. package/data/sqlite/schema/index.js +1 -1
  76. package/data/sqlite/schema/index.js.map +1 -1
  77. package/data/sqlite/sqliteBindBudget.js +12 -0
  78. package/data/sqlite/sqliteBindBudget.js.map +1 -1
  79. package/package.json +1 -1
  80. package/workflows/container-contents/container-state/create/createIntentSeed.js.map +1 -1
  81. package/workflows/container-contents/container-state/create/createWithMetadataPlan.js.map +1 -1
  82. package/workflows/container-contents/container-state/types.js.map +1 -1
  83. package/workflows/container-contents/metadata/index.js.map +1 -1
  84. package/workflows/container-contents/metadata/metadataEvents.d.ts +1 -1
  85. package/workflows/container-contents/metadata/metadataEvents.js.map +1 -1
  86. package/workflows/container-contents/metadata/metadataIncomingUpdateIsolation.js +1 -1
  87. package/workflows/container-contents/metadata/metadataIncomingUpdateIsolation.js.map +1 -1
  88. package/workflows/container-contents/metadata/metadataRemoteSync.d.ts +1 -1
  89. package/workflows/container-contents/metadata/metadataRemoteSync.js.map +1 -1
  90. package/workflows/container-contents/pendingWrites/index.js +1 -1
  91. package/workflows/container-contents/pendingWrites/index.js.map +1 -1
  92. package/{client/organizations/organizationAccessRestoration.js → workflows/container-contents/recovery/accessRestoration.js} +16 -12
  93. package/workflows/container-contents/recovery/accessRestoration.js.map +1 -0
  94. package/workflows/container-contents/remoteReset/containerRemoteReset.d.ts +185 -0
  95. package/workflows/container-contents/remoteReset/containerRemoteReset.js +174 -0
  96. package/workflows/container-contents/remoteReset/containerRemoteReset.js.map +1 -0
  97. package/workflows/container-contents/remoteReset/remoteResetCursorScope.d.ts +14 -0
  98. package/workflows/{sync → container-contents/remoteReset}/remoteResetCursorScope.js +2 -2
  99. package/workflows/container-contents/remoteReset/remoteResetCursorScope.js.map +1 -0
  100. package/workflows/documents/create/documentCreationPersistence.js.map +1 -1
  101. package/workflows/documents/create/index.d.ts +1 -1
  102. package/workflows/documents/create/index.js.map +1 -1
  103. package/workflows/documents/documentMutationCommit.d.ts +1 -1
  104. package/workflows/documents/documentMutationCommit.js.map +1 -1
  105. package/workflows/documents/index.d.ts +2 -2
  106. package/workflows/documents/index.js +2 -2
  107. package/workflows/documents/index.js.map +1 -1
  108. package/workflows/documents/remoteReset/documentRemoteReset.d.ts +56 -0
  109. package/workflows/documents/remoteReset/documentRemoteReset.js +135 -0
  110. package/workflows/documents/remoteReset/documentRemoteReset.js.map +1 -0
  111. package/workflows/{sync → documents/remoteReset}/remoteResetAttachments.js +3 -3
  112. package/workflows/documents/remoteReset/remoteResetAttachments.js.map +1 -0
  113. package/workflows/{sync → documents/remoteReset}/remoteResetPlans.d.ts +1 -1
  114. package/workflows/{sync → documents/remoteReset}/remoteResetPlans.js +7 -7
  115. package/workflows/documents/remoteReset/remoteResetPlans.js.map +1 -0
  116. package/workflows/documents/sync/index.js.map +1 -1
  117. package/workflows/documents/sync/initialSync.d.ts +1 -1
  118. package/workflows/documents/sync/initialSync.js.map +1 -1
  119. package/workflows/documents/sync/readOnlySync.d.ts +1 -1
  120. package/workflows/documents/sync/readOnlySync.js.map +1 -1
  121. package/workflows/documents/sync/syncAttemptPlanning.js.map +1 -1
  122. package/workflows/documents/sync/syncAttemptState.d.ts +1 -1
  123. package/workflows/documents/sync/syncAttemptState.js.map +1 -1
  124. package/workflows/documents/sync/syncResponseResult.js.map +1 -1
  125. package/workflows/documents/sync/syncSubmittedResult.js.map +1 -1
  126. package/workflows/documents/syncFailures/index.js.map +1 -1
  127. package/workflows/documents/syncPlan/syncContentKeys.d.ts +1 -1
  128. package/workflows/documents/syncPlan/syncContentKeys.js.map +1 -1
  129. package/workflows/documents/syncPlan/syncPlanMaterial.js.map +1 -1
  130. package/workflows/documents/syncPlan/syncPlanRequestBounds.js.map +1 -1
  131. package/workflows/documents/syncRekeys/syncContainerRekeys.js.map +1 -1
  132. package/workflows/documents/syncRekeys/syncRecoveryRekey.d.ts +1 -1
  133. package/workflows/documents/syncRekeys/syncRecoveryRekey.js +1 -1
  134. package/workflows/documents/syncRekeys/syncRecoveryRekey.js.map +1 -1
  135. package/workflows/organizations/readModel/listLocalOrganizations.js +1 -1
  136. package/workflows/organizations/readModel/listLocalOrganizations.js.map +1 -1
  137. package/workflows/organizations/readModel/organizationRemoteReset.js +25 -0
  138. package/workflows/organizations/readModel/organizationRemoteReset.js.map +1 -0
  139. package/workflows/registration/persistRegistrationBootstrap.d.ts +1 -1
  140. package/workflows/registration/persistRegistrationBootstrap.js.map +1 -1
  141. package/workflows/sync/remoteReset.js +60 -229
  142. package/workflows/sync/remoteReset.js.map +1 -1
  143. package/workflows/sync/remoteResetScope.d.ts +7 -10
  144. package/workflows/sync/remoteResetScope.js +26 -108
  145. package/workflows/sync/remoteResetScope.js.map +1 -1
  146. package/client/organizations/organizationAccessRestoration.js.map +0 -1
  147. package/data/sqlite/documentPersistence/documentHistoryPersistence.js.map +0 -1
  148. package/data/sqlite/documentPersistence/documentOrphanBlobReclaims.js.map +0 -1
  149. package/data/sqlite/documentPersistence/documentPendingUpdatePersistence.js.map +0 -1
  150. package/data/sqlite/documentPersistence/documentPersistenceTypes.js.map +0 -1
  151. package/data/sqlite/documentPersistence/documentRecordPersistence.js.map +0 -1
  152. package/data/sqlite/documentPersistence/documentSyncFailurePersistence.js.map +0 -1
  153. package/data/sqlite/documentPersistence/index.js.map +0 -1
  154. package/workflows/sync/remoteResetAttachments.js.map +0 -1
  155. package/workflows/sync/remoteResetBatches.js +0 -13
  156. package/workflows/sync/remoteResetBatches.js.map +0 -1
  157. package/workflows/sync/remoteResetCursorScope.js.map +0 -1
  158. package/workflows/sync/remoteResetPlans.js.map +0 -1
  159. package/workflows/sync/remoteResetPrincipalScope.js.map +0 -1
  160. /package/data/{sqlite/documentPersistence → persistence/documents/records}/documentPersistenceTypes.js +0 -0
@@ -1,6 +1,6 @@
1
+ import { recoverOrganizationAccess } from "../../workflows/container-contents/recovery/accessRestoration.js";
1
2
  import { loadLocalOrganizationContainerGrants, loadLocalOrganizationDirectoryAndGroups, loadLocalOrganizationGroupContainers, loadLocalOrganizationGroupMembers, loadLocalOrganizationUserDetail, reconcileOrganizationDirectoryAndGroups, } from "../../workflows/organizations/index.js";
2
3
  import { organizationAccessScopeKey, wasOrganizationPresentationAccessDeniedByServer, } from "../../workflows/organizations/readModel/organizationPresentationAccessState.js";
3
- import { recoverOrganizationAccess } from "./organizationAccessRestoration.js";
4
4
  import { loadBoundedOrganizationGroupHistory } from "./organizationGroupHistory.js";
5
5
  import { hydrateOrganizationGroupNamesForRuntime } from "./organizationGroupNameHydration.js";
6
6
  import { loadBoundedOrganizationHistory } from "./organizationHistory.js";
@@ -145,9 +145,11 @@ class OrganizationReadModelCoordinatorImpl {
145
145
  directoryAndGroups !== undefined;
146
146
  if (accessWasRestored)
147
147
  await recoverOrganizationAccess({
148
- active,
149
148
  domainScope,
149
+ execSql: active.runtime.infra.execSql,
150
+ organizationId: active.organizationId,
150
151
  stillCurrent,
152
+ userId: active.userId,
151
153
  });
152
154
  if (!stillCurrent())
153
155
  return null;
@@ -1 +1 @@
1
- {"version":3,"file":"organizationReadModels.js","sourceRoot":"","sources":["organizationReadModels.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DomainScope } from \"../../data/shared/domainScope\";\nimport {\n loadLocalOrganizationContainerGrants,\n loadLocalOrganizationDirectoryAndGroups,\n loadLocalOrganizationGroupContainers,\n loadLocalOrganizationGroupMembers,\n loadLocalOrganizationUserDetail,\n type OrganizationContainerGrants,\n type OrganizationDirectoryAndGroups,\n type OrganizationGroupContainers,\n type OrganizationGroupMembers,\n type OrganizationGroupPolicyHistory,\n type OrganizationPolicyHistory,\n type OrganizationUserDetail,\n reconcileOrganizationDirectoryAndGroups,\n} from \"../../workflows/organizations\";\nimport {\n organizationAccessScopeKey,\n wasOrganizationPresentationAccessDeniedByServer,\n} from \"../../workflows/organizations/readModel/organizationPresentationAccessState\";\nimport type { InternalRuntime } from \"../runtime/workflowRuntime\";\nimport { recoverOrganizationAccess } from \"./organizationAccessRestoration\";\nimport { loadBoundedOrganizationGroupHistory } from \"./organizationGroupHistory\";\nimport { hydrateOrganizationGroupNamesForRuntime } from \"./organizationGroupNameHydration\";\nimport { loadBoundedOrganizationHistory } from \"./organizationHistory\";\nimport {\n type ActiveOrganizationDataRuntime,\n activeOrganizationDataRuntime,\n isOrganizationDataRuntimeCurrent,\n} from \"./organizationWorkflowRuntime\";\n\nconst coordinatorsByRuntime = new WeakMap<\n InternalRuntime,\n OrganizationReadModelCoordinator\n>();\n\nexport interface OrganizationReadModelCoordinator {\n loadLocal(\n organizationId?: string | undefined,\n ): Promise<OrganizationDirectoryAndGroups | null>;\n loadLocalGroupMembers(\n groupId: string,\n organizationId?: string | undefined,\n ): Promise<OrganizationGroupMembers | null>;\n loadGroupPolicyHistory(\n groupId: string,\n organizationId?: string | undefined,\n beforeVersion?: number | undefined,\n ): Promise<OrganizationGroupPolicyHistory | null>;\n loadOrganizationPolicyHistory(\n organizationId?: string | undefined,\n beforeVersion?: number | undefined,\n ): Promise<OrganizationPolicyHistory | null>;\n loadLocalGrants(\n organizationId?: string | undefined,\n ): Promise<OrganizationContainerGrants | null>;\n loadLocalGroupContainers(\n groupId: string,\n organizationId?: string | undefined,\n ): Promise<OrganizationGroupContainers | null>;\n loadLocalUserDetail(\n userId: string,\n organizationId?: string | undefined,\n ): Promise<OrganizationUserDetail | null>;\n /**\n * Resolves `undefined` when the runtime declines without I/O (offline,\n * database not ready, organization mismatch) or when the feed failed with\n * no locally retained projection — passes that must not count as caught\n * up; `null` when the reconcile completed but produced nothing presentable\n * — including an authoritative denial that purged the projection, which\n * consumers must repaint.\n */\n reconcile(\n organizationId?: string | undefined,\n ): Promise<OrganizationDirectoryAndGroups | null | undefined>;\n reconcileAfterMutation(\n organizationId?: string | undefined,\n ): Promise<OrganizationDirectoryAndGroups | null | undefined>;\n}\n\nclass OrganizationReadModelCoordinatorImpl\n implements OrganizationReadModelCoordinator\n{\n private readonly reconciliationsByScope = new WeakMap<\n DomainScope,\n Map<string, Promise<OrganizationDirectoryAndGroups | null | undefined>>\n >();\n constructor(private readonly runtimeService: InternalRuntime) {}\n\n private reconciliationMap(active: ActiveOrganizationDataRuntime) {\n const scope = active.runtime.state.domainScope;\n let byKey = this.reconciliationsByScope.get(scope);\n if (!byKey) {\n byKey = new Map();\n this.reconciliationsByScope.set(scope, byKey);\n }\n return byKey;\n }\n\n async loadLocal(organizationId?: string) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active) {\n return null;\n }\n return loadLocalOrganizationDirectoryAndGroups({\n currentUserId: active.userId,\n execSql: active.runtime.infra.execSql,\n organizationId: active.organizationId,\n });\n }\n\n async loadLocalGrants(organizationId?: string) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active) {\n return null;\n }\n return loadLocalOrganizationContainerGrants({\n currentUserId: active.userId,\n execSql: active.runtime.infra.execSql,\n organizationId: active.organizationId,\n });\n }\n\n async loadLocalGroupContainers(groupId: string, organizationId?: string) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active || groupId.length === 0) {\n return null;\n }\n return loadLocalOrganizationGroupContainers({\n currentUserId: active.userId,\n execSql: active.runtime.infra.execSql,\n groupId,\n organizationId: active.organizationId,\n });\n }\n\n async loadLocalGroupMembers(groupId: string, organizationId?: string) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active || groupId.length === 0) {\n return null;\n }\n return loadLocalOrganizationGroupMembers({\n currentUserId: active.userId,\n execSql: active.runtime.infra.execSql,\n groupId,\n organizationId: active.organizationId,\n });\n }\n\n async loadGroupPolicyHistory(\n groupId: string,\n organizationId?: string,\n beforeVersion?: number,\n ) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active || groupId.length === 0) {\n return null;\n }\n const domainScope = active.runtime.state.domainScope;\n return loadBoundedOrganizationGroupHistory({\n active,\n groupId,\n beforeVersion,\n stillCurrent: () =>\n isOrganizationDataRuntimeCurrent(\n this.runtimeService,\n active,\n domainScope,\n ),\n });\n }\n\n async loadOrganizationPolicyHistory(\n organizationId?: string,\n beforeVersion?: number,\n ) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active) return null;\n const domainScope = active.runtime.state.domainScope;\n return loadBoundedOrganizationHistory({\n active,\n beforeVersion,\n stillCurrent: () =>\n isOrganizationDataRuntimeCurrent(\n this.runtimeService,\n active,\n domainScope,\n ),\n });\n }\n\n async loadLocalUserDetail(userId: string, organizationId?: string) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active || userId.length === 0) {\n return null;\n }\n return loadLocalOrganizationUserDetail({\n currentUserId: active.userId,\n execSql: active.runtime.infra.execSql,\n organizationId: active.organizationId,\n userId,\n });\n }\n\n reconcile(\n organizationId?: string,\n ): Promise<OrganizationDirectoryAndGroups | null | undefined> {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active?.runtime.state.online) {\n return Promise.resolve(undefined);\n }\n\n const byKey = this.reconciliationMap(active);\n const key = organizationAccessScopeKey(\n active.organizationId,\n active.userId,\n );\n const existing = byKey.get(key);\n if (existing) {\n return existing;\n }\n\n // Snapshot the server-denied flag before reconciling. A reconcile that\n // flips it back to readable means org access was just restored (e.g. the\n // user was re-added to a group), and any write lanes idled by\n // permission-denied submissions have no self re-arm — regaining access is\n // their retry signal, mirroring how billing recovery re-requests all\n // lanes. Only a genuine server denial counts: a local session reset also\n // marks scopes denied, and re-arming on its first reconcile would race the\n // startup sync passes (e.g. pending-create adoption between panes).\n const accessWasDenied = wasOrganizationPresentationAccessDeniedByServer(\n {\n execSql: active.runtime.infra.execSql,\n organizationId: active.organizationId,\n requesterUserId: active.userId,\n },\n \"readModel\",\n );\n const domainScope = active.runtime.state.domainScope;\n const stillCurrent = () =>\n isOrganizationDataRuntimeCurrent(\n this.runtimeService,\n active,\n domainScope,\n );\n\n const reconciliation = reconcileOrganizationDirectoryAndGroups({\n apiClient: active.runtime.apiClient,\n currentUserId: active.userId,\n execSql: active.runtime.infra.execSql,\n logError: active.runtime.util.logError,\n organizationId: active.organizationId,\n })\n .then(async (directoryAndGroups) => {\n if (!stillCurrent()) return null;\n // Feed success restores retry eligibility even if name decryption fails.\n const accessWasRestored =\n accessWasDenied &&\n directoryAndGroups !== null &&\n directoryAndGroups !== undefined;\n if (accessWasRestored)\n await recoverOrganizationAccess({\n active,\n domainScope,\n stillCurrent,\n });\n if (!stillCurrent()) return null;\n return hydrateOrganizationGroupNamesForRuntime(\n this.runtimeService,\n active,\n domainScope,\n directoryAndGroups,\n );\n })\n .finally(() => {\n if (byKey.get(key) === reconciliation) {\n byKey.delete(key);\n }\n });\n byKey.set(key, reconciliation);\n return reconciliation;\n }\n\n async reconcileAfterMutation(\n organizationId?: string,\n ): Promise<OrganizationDirectoryAndGroups | null | undefined> {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active?.runtime.state.online) {\n return undefined;\n }\n const existing = this.reconciliationMap(active).get(\n organizationAccessScopeKey(active.organizationId, active.userId),\n );\n if (existing) {\n await existing.catch(() => null);\n }\n return this.reconcile(active.organizationId);\n }\n}\n\nexport function createOrganizationReadModelCoordinator(\n runtimeService: InternalRuntime,\n): OrganizationReadModelCoordinator {\n const existing = coordinatorsByRuntime.get(runtimeService);\n if (existing) {\n return existing;\n }\n\n const coordinator = new OrganizationReadModelCoordinatorImpl(runtimeService);\n coordinatorsByRuntime.set(runtimeService, coordinator);\n return coordinator;\n}\n"]}
1
+ {"version":3,"file":"organizationReadModels.js","sourceRoot":"","sources":["organizationReadModels.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DomainScope } from \"../../data/shared/domainScope\";\nimport { recoverOrganizationAccess } from \"../../workflows/container-contents/recovery/accessRestoration\";\nimport {\n loadLocalOrganizationContainerGrants,\n loadLocalOrganizationDirectoryAndGroups,\n loadLocalOrganizationGroupContainers,\n loadLocalOrganizationGroupMembers,\n loadLocalOrganizationUserDetail,\n type OrganizationContainerGrants,\n type OrganizationDirectoryAndGroups,\n type OrganizationGroupContainers,\n type OrganizationGroupMembers,\n type OrganizationGroupPolicyHistory,\n type OrganizationPolicyHistory,\n type OrganizationUserDetail,\n reconcileOrganizationDirectoryAndGroups,\n} from \"../../workflows/organizations\";\nimport {\n organizationAccessScopeKey,\n wasOrganizationPresentationAccessDeniedByServer,\n} from \"../../workflows/organizations/readModel/organizationPresentationAccessState\";\nimport type { InternalRuntime } from \"../runtime/workflowRuntime\";\nimport { loadBoundedOrganizationGroupHistory } from \"./organizationGroupHistory\";\nimport { hydrateOrganizationGroupNamesForRuntime } from \"./organizationGroupNameHydration\";\nimport { loadBoundedOrganizationHistory } from \"./organizationHistory\";\nimport {\n type ActiveOrganizationDataRuntime,\n activeOrganizationDataRuntime,\n isOrganizationDataRuntimeCurrent,\n} from \"./organizationWorkflowRuntime\";\n\nconst coordinatorsByRuntime = new WeakMap<\n InternalRuntime,\n OrganizationReadModelCoordinator\n>();\n\nexport interface OrganizationReadModelCoordinator {\n loadLocal(\n organizationId?: string | undefined,\n ): Promise<OrganizationDirectoryAndGroups | null>;\n loadLocalGroupMembers(\n groupId: string,\n organizationId?: string | undefined,\n ): Promise<OrganizationGroupMembers | null>;\n loadGroupPolicyHistory(\n groupId: string,\n organizationId?: string | undefined,\n beforeVersion?: number | undefined,\n ): Promise<OrganizationGroupPolicyHistory | null>;\n loadOrganizationPolicyHistory(\n organizationId?: string | undefined,\n beforeVersion?: number | undefined,\n ): Promise<OrganizationPolicyHistory | null>;\n loadLocalGrants(\n organizationId?: string | undefined,\n ): Promise<OrganizationContainerGrants | null>;\n loadLocalGroupContainers(\n groupId: string,\n organizationId?: string | undefined,\n ): Promise<OrganizationGroupContainers | null>;\n loadLocalUserDetail(\n userId: string,\n organizationId?: string | undefined,\n ): Promise<OrganizationUserDetail | null>;\n /**\n * Resolves `undefined` when the runtime declines without I/O (offline,\n * database not ready, organization mismatch) or when the feed failed with\n * no locally retained projection — passes that must not count as caught\n * up; `null` when the reconcile completed but produced nothing presentable\n * — including an authoritative denial that purged the projection, which\n * consumers must repaint.\n */\n reconcile(\n organizationId?: string | undefined,\n ): Promise<OrganizationDirectoryAndGroups | null | undefined>;\n reconcileAfterMutation(\n organizationId?: string | undefined,\n ): Promise<OrganizationDirectoryAndGroups | null | undefined>;\n}\n\nclass OrganizationReadModelCoordinatorImpl\n implements OrganizationReadModelCoordinator\n{\n private readonly reconciliationsByScope = new WeakMap<\n DomainScope,\n Map<string, Promise<OrganizationDirectoryAndGroups | null | undefined>>\n >();\n constructor(private readonly runtimeService: InternalRuntime) {}\n\n private reconciliationMap(active: ActiveOrganizationDataRuntime) {\n const scope = active.runtime.state.domainScope;\n let byKey = this.reconciliationsByScope.get(scope);\n if (!byKey) {\n byKey = new Map();\n this.reconciliationsByScope.set(scope, byKey);\n }\n return byKey;\n }\n\n async loadLocal(organizationId?: string) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active) {\n return null;\n }\n return loadLocalOrganizationDirectoryAndGroups({\n currentUserId: active.userId,\n execSql: active.runtime.infra.execSql,\n organizationId: active.organizationId,\n });\n }\n\n async loadLocalGrants(organizationId?: string) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active) {\n return null;\n }\n return loadLocalOrganizationContainerGrants({\n currentUserId: active.userId,\n execSql: active.runtime.infra.execSql,\n organizationId: active.organizationId,\n });\n }\n\n async loadLocalGroupContainers(groupId: string, organizationId?: string) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active || groupId.length === 0) {\n return null;\n }\n return loadLocalOrganizationGroupContainers({\n currentUserId: active.userId,\n execSql: active.runtime.infra.execSql,\n groupId,\n organizationId: active.organizationId,\n });\n }\n\n async loadLocalGroupMembers(groupId: string, organizationId?: string) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active || groupId.length === 0) {\n return null;\n }\n return loadLocalOrganizationGroupMembers({\n currentUserId: active.userId,\n execSql: active.runtime.infra.execSql,\n groupId,\n organizationId: active.organizationId,\n });\n }\n\n async loadGroupPolicyHistory(\n groupId: string,\n organizationId?: string,\n beforeVersion?: number,\n ) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active || groupId.length === 0) {\n return null;\n }\n const domainScope = active.runtime.state.domainScope;\n return loadBoundedOrganizationGroupHistory({\n active,\n groupId,\n beforeVersion,\n stillCurrent: () =>\n isOrganizationDataRuntimeCurrent(\n this.runtimeService,\n active,\n domainScope,\n ),\n });\n }\n\n async loadOrganizationPolicyHistory(\n organizationId?: string,\n beforeVersion?: number,\n ) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active) return null;\n const domainScope = active.runtime.state.domainScope;\n return loadBoundedOrganizationHistory({\n active,\n beforeVersion,\n stillCurrent: () =>\n isOrganizationDataRuntimeCurrent(\n this.runtimeService,\n active,\n domainScope,\n ),\n });\n }\n\n async loadLocalUserDetail(userId: string, organizationId?: string) {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active || userId.length === 0) {\n return null;\n }\n return loadLocalOrganizationUserDetail({\n currentUserId: active.userId,\n execSql: active.runtime.infra.execSql,\n organizationId: active.organizationId,\n userId,\n });\n }\n\n reconcile(\n organizationId?: string,\n ): Promise<OrganizationDirectoryAndGroups | null | undefined> {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active?.runtime.state.online) {\n return Promise.resolve(undefined);\n }\n\n const byKey = this.reconciliationMap(active);\n const key = organizationAccessScopeKey(\n active.organizationId,\n active.userId,\n );\n const existing = byKey.get(key);\n if (existing) {\n return existing;\n }\n\n // Snapshot the server-denied flag before reconciling. A reconcile that\n // flips it back to readable means org access was just restored (e.g. the\n // user was re-added to a group), and any write lanes idled by\n // permission-denied submissions have no self re-arm — regaining access is\n // their retry signal, mirroring how billing recovery re-requests all\n // lanes. Only a genuine server denial counts: a local session reset also\n // marks scopes denied, and re-arming on its first reconcile would race the\n // startup sync passes (e.g. pending-create adoption between panes).\n const accessWasDenied = wasOrganizationPresentationAccessDeniedByServer(\n {\n execSql: active.runtime.infra.execSql,\n organizationId: active.organizationId,\n requesterUserId: active.userId,\n },\n \"readModel\",\n );\n const domainScope = active.runtime.state.domainScope;\n const stillCurrent = () =>\n isOrganizationDataRuntimeCurrent(\n this.runtimeService,\n active,\n domainScope,\n );\n\n const reconciliation = reconcileOrganizationDirectoryAndGroups({\n apiClient: active.runtime.apiClient,\n currentUserId: active.userId,\n execSql: active.runtime.infra.execSql,\n logError: active.runtime.util.logError,\n organizationId: active.organizationId,\n })\n .then(async (directoryAndGroups) => {\n if (!stillCurrent()) return null;\n // Feed success restores retry eligibility even if name decryption fails.\n const accessWasRestored =\n accessWasDenied &&\n directoryAndGroups !== null &&\n directoryAndGroups !== undefined;\n if (accessWasRestored)\n await recoverOrganizationAccess({\n domainScope,\n execSql: active.runtime.infra.execSql,\n organizationId: active.organizationId,\n stillCurrent,\n userId: active.userId,\n });\n if (!stillCurrent()) return null;\n return hydrateOrganizationGroupNamesForRuntime(\n this.runtimeService,\n active,\n domainScope,\n directoryAndGroups,\n );\n })\n .finally(() => {\n if (byKey.get(key) === reconciliation) {\n byKey.delete(key);\n }\n });\n byKey.set(key, reconciliation);\n return reconciliation;\n }\n\n async reconcileAfterMutation(\n organizationId?: string,\n ): Promise<OrganizationDirectoryAndGroups | null | undefined> {\n const active = activeOrganizationDataRuntime(\n this.runtimeService,\n organizationId,\n );\n if (!active?.runtime.state.online) {\n return undefined;\n }\n const existing = this.reconciliationMap(active).get(\n organizationAccessScopeKey(active.organizationId, active.userId),\n );\n if (existing) {\n await existing.catch(() => null);\n }\n return this.reconcile(active.organizationId);\n }\n}\n\nexport function createOrganizationReadModelCoordinator(\n runtimeService: InternalRuntime,\n): OrganizationReadModelCoordinator {\n const existing = coordinatorsByRuntime.get(runtimeService);\n if (existing) {\n return existing;\n }\n\n const coordinator = new OrganizationReadModelCoordinatorImpl(runtimeService);\n coordinatorsByRuntime.set(runtimeService, coordinator);\n return coordinator;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"documentSync.js","sourceRoot":"","sources":["documentSync.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToBase64 } from \"@tearleads/encoding\";\nimport { getImportBlobMetadata, versionVectorsEqual } from \"@tearleads/loro\";\nimport { isPlainObject } from \"@tearleads/validators/isPlainObject\";\nimport {\n WsDocumentUpdateCreatedHintSchema,\n type WsInvalidationHint,\n} from \"@tearleads/validators/realtime\";\nimport type { PendingUpdateFields } from \"../sqlite/documentPersistence\";\n\ntype DocumentUpdateCreatedEvent = Extract<\n WsInvalidationHint,\n { type: \"document_update_created\" }\n>;\n\ninterface DocumentMutationCreatedEvent {\n type: \"document_mutation_created\";\n containerIds: string[];\n documentId: string;\n eventType: \"document.link\" | \"document.purge\" | \"document.unlink\";\n}\n\nexport function createPendingUpdateFields(\n update: Uint8Array,\n sourceVersionVector?: string | null,\n): PendingUpdateFields | null {\n if (update.byteLength === 0) {\n return null;\n }\n\n const { mode, partialEndVersionVector, partialStartVersionVector } =\n getImportBlobMetadata(update);\n\n // A Loro delta that encodes no ops still serializes to a small non-empty blob\n // (~22 bytes) with start == end version vectors, so the byteLength guard above\n // does not catch it. Such an update produces zero spans server-side, which the\n // frontier diff treats as permanently \"missing\" and re-sends to every reader\n // on every sync forever. Drop it: a zero-span update carries nothing.\n if (versionVectorsEqual(partialStartVersionVector, partialEndVersionVector)) {\n return null;\n }\n\n if (sourceVersionVector != null) {\n if (mode !== \"snapshot\") {\n throw new Error(\"Rotation baseline must be a full-history Loro snapshot\");\n }\n if (!versionVectorsEqual(sourceVersionVector, partialEndVersionVector)) {\n throw new Error(\n \"Rotation baseline source must equal its decoded end version vector\",\n );\n }\n }\n\n return {\n updateData: bytesToBase64(update),\n partialStartVersionVector,\n partialEndVersionVector,\n sourceVersionVector: sourceVersionVector ?? null,\n };\n}\n\nexport function isDocumentUpdateCreatedEvent(\n event: unknown,\n): event is DocumentUpdateCreatedEvent {\n return WsDocumentUpdateCreatedHintSchema.safeParse(event).success;\n}\n\nexport function isDocumentMutationCreatedEvent(\n event: unknown,\n): event is DocumentMutationCreatedEvent {\n if (!isPlainObject(event)) {\n return false;\n }\n\n const containerIds = Reflect.get(event, \"containerIds\");\n const documentId = Reflect.get(event, \"documentId\");\n const eventType = Reflect.get(event, \"eventType\");\n\n return (\n Reflect.get(event, \"type\") === \"document_mutation_created\" &&\n typeof documentId === \"string\" &&\n documentId.length > 0 &&\n (eventType === \"document.link\" ||\n eventType === \"document.purge\" ||\n eventType === \"document.unlink\") &&\n Array.isArray(containerIds) &&\n containerIds.length > 0 &&\n containerIds.every(\n (containerId) =>\n typeof containerId === \"string\" && containerId.length > 0,\n )\n );\n}\n\n/**\n * Realtime hints after which a cached writer projection may cite a stale\n * manifest: a container mutation (grant, rekey, recite, ...) or the gateway's\n * dependent-path notice. An eviction resync also drops the previously held\n * projection before revalidation; its later parent-only hint names no child.\n */\nexport function isContainerProjectionInvalidationHint(event: unknown): boolean {\n if (typeof event !== \"object\" || event === null) return false;\n const type = Reflect.get(event, \"type\");\n return (\n type === \"container_mutation_created\" ||\n type === \"container_path_changed\" ||\n type === \"container_children_changed\" ||\n type === \"resync_required\"\n );\n}\n"]}
1
+ {"version":3,"file":"documentSync.js","sourceRoot":"","sources":["documentSync.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToBase64 } from \"@tearleads/encoding\";\nimport { getImportBlobMetadata, versionVectorsEqual } from \"@tearleads/loro\";\nimport { isPlainObject } from \"@tearleads/validators/isPlainObject\";\nimport {\n WsDocumentUpdateCreatedHintSchema,\n type WsInvalidationHint,\n} from \"@tearleads/validators/realtime\";\nimport type { PendingUpdateFields } from \"../persistence/documents/records\";\n\ntype DocumentUpdateCreatedEvent = Extract<\n WsInvalidationHint,\n { type: \"document_update_created\" }\n>;\n\ninterface DocumentMutationCreatedEvent {\n type: \"document_mutation_created\";\n containerIds: string[];\n documentId: string;\n eventType: \"document.link\" | \"document.purge\" | \"document.unlink\";\n}\n\nexport function createPendingUpdateFields(\n update: Uint8Array,\n sourceVersionVector?: string | null,\n): PendingUpdateFields | null {\n if (update.byteLength === 0) {\n return null;\n }\n\n const { mode, partialEndVersionVector, partialStartVersionVector } =\n getImportBlobMetadata(update);\n\n // A Loro delta that encodes no ops still serializes to a small non-empty blob\n // (~22 bytes) with start == end version vectors, so the byteLength guard above\n // does not catch it. Such an update produces zero spans server-side, which the\n // frontier diff treats as permanently \"missing\" and re-sends to every reader\n // on every sync forever. Drop it: a zero-span update carries nothing.\n if (versionVectorsEqual(partialStartVersionVector, partialEndVersionVector)) {\n return null;\n }\n\n if (sourceVersionVector != null) {\n if (mode !== \"snapshot\") {\n throw new Error(\"Rotation baseline must be a full-history Loro snapshot\");\n }\n if (!versionVectorsEqual(sourceVersionVector, partialEndVersionVector)) {\n throw new Error(\n \"Rotation baseline source must equal its decoded end version vector\",\n );\n }\n }\n\n return {\n updateData: bytesToBase64(update),\n partialStartVersionVector,\n partialEndVersionVector,\n sourceVersionVector: sourceVersionVector ?? null,\n };\n}\n\nexport function isDocumentUpdateCreatedEvent(\n event: unknown,\n): event is DocumentUpdateCreatedEvent {\n return WsDocumentUpdateCreatedHintSchema.safeParse(event).success;\n}\n\nexport function isDocumentMutationCreatedEvent(\n event: unknown,\n): event is DocumentMutationCreatedEvent {\n if (!isPlainObject(event)) {\n return false;\n }\n\n const containerIds = Reflect.get(event, \"containerIds\");\n const documentId = Reflect.get(event, \"documentId\");\n const eventType = Reflect.get(event, \"eventType\");\n\n return (\n Reflect.get(event, \"type\") === \"document_mutation_created\" &&\n typeof documentId === \"string\" &&\n documentId.length > 0 &&\n (eventType === \"document.link\" ||\n eventType === \"document.purge\" ||\n eventType === \"document.unlink\") &&\n Array.isArray(containerIds) &&\n containerIds.length > 0 &&\n containerIds.every(\n (containerId) =>\n typeof containerId === \"string\" && containerId.length > 0,\n )\n );\n}\n\n/**\n * Realtime hints after which a cached writer projection may cite a stale\n * manifest: a container mutation (grant, rekey, recite, ...) or the gateway's\n * dependent-path notice. An eviction resync also drops the previously held\n * projection before revalidation; its later parent-only hint names no child.\n */\nexport function isContainerProjectionInvalidationHint(event: unknown): boolean {\n if (typeof event !== \"object\" || event === null) return false;\n const type = Reflect.get(event, \"type\");\n return (\n type === \"container_mutation_created\" ||\n type === \"container_path_changed\" ||\n type === \"container_children_changed\" ||\n type === \"resync_required\"\n );\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["crypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeContentRecordNonceDomainHash,\n computeDocumentContentRecordCiphertextHash,\n computeDocumentContentRecordMetadataHash,\n computeDocumentContentRecordPlaintextHash,\n createAesGcmIv,\n serializeKeyingCanonicalJson,\n} from \"@tearleads/crypto\";\nimport { base64ToBytes, bytesToBase64 } from \"@tearleads/encoding\";\nimport type { DocumentSyncResponse } from \"@tearleads/validators/response\";\nimport type { PendingUpdateRecord } from \"../../sqlite/documentPersistence\";\nimport { contentRecordAdditionalDataBytes } from \"./contentRecordAdditionalData\";\nimport {\n deriveDocumentContentRecordKey,\n deriveDocumentPlaintextHashKey,\n importContentKeyMaterial,\n} from \"./contentRecordKeys\";\nimport { asWebCryptoBytes, readWriteHeader } from \"./readers\";\nimport { decryptDocumentSyncUpdate } from \"./sync/documentSyncUpdateDecryption\";\nimport {\n isDocumentSyncUpdateIsolationError,\n isolateDocumentSyncBatchError,\n isolateDocumentSyncUpdateError,\n} from \"./sync/documentSyncUpdateIsolation\";\nimport {\n type DecryptedDocumentSyncUpdate,\n DOCUMENT_ENCRYPTED_LORO_UPDATE_FORMAT,\n type DocumentEncryptedPendingUpdate,\n} from \"./types\";\n\nexport async function encryptDocumentPendingUpdate(input: {\n contentKeyMaterial: CryptoKey;\n contentKeyEpoch: number;\n documentId: string;\n organizationId: string;\n update: PendingUpdateRecord;\n}): Promise<DocumentEncryptedPendingUpdate> {\n const plaintext = base64ToBytes(input.update.updateData);\n const contentRecordId = input.update.id;\n const nonceDomainHash = await computeContentRecordNonceDomainHash({\n version: 1,\n organizationId: input.organizationId,\n objectKind: \"document\",\n objectId: input.documentId,\n contentKeyEpoch: input.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId,\n });\n const plaintextHashKey = await deriveDocumentPlaintextHashKey({\n contentKeyMaterial: input.contentKeyMaterial,\n contentKeyEpoch: input.contentKeyEpoch,\n contentRecordId,\n documentId: input.documentId,\n organizationId: input.organizationId,\n });\n const plaintextHash = await computeDocumentContentRecordPlaintextHash(\n plaintext,\n plaintextHashKey,\n );\n const metadataHash = await computeDocumentContentRecordMetadataHash({\n ...(input.update.sourceVersionVector\n ? {\n checkpointKind: \"rotate_baseline\" as const,\n checkpointPayloadKind: \"full_history_snapshot\" as const,\n sourceVersionVector: input.update.sourceVersionVector,\n }\n : {}),\n documentId: input.documentId,\n partialEndVersionVector: input.update.partialEndVersionVector,\n partialStartVersionVector: input.update.partialStartVersionVector,\n plaintextHash,\n updateId: input.update.id,\n });\n const recordKey = await deriveDocumentContentRecordKey({\n contentKeyMaterial: input.contentKeyMaterial,\n contentKeyEpoch: input.contentKeyEpoch,\n contentRecordId,\n documentId: input.documentId,\n organizationId: input.organizationId,\n usage: \"encrypt\",\n });\n const iv = createAesGcmIv();\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt(\n {\n name: \"AES-GCM\",\n iv,\n additionalData: contentRecordAdditionalDataBytes({\n contentKeyEpoch: input.contentKeyEpoch,\n contentRecordId,\n documentId: input.documentId,\n metadataHash,\n nonceDomainHash,\n organizationId: input.organizationId,\n }),\n },\n recordKey,\n asWebCryptoBytes(plaintext),\n ),\n );\n const encryptedData = serializeKeyingCanonicalJson({\n format: DOCUMENT_ENCRYPTED_LORO_UPDATE_FORMAT,\n version: 1,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentKeyEpoch: input.contentKeyEpoch,\n contentRecordId,\n nonceDomainHash,\n metadataHash,\n iv: bytesToBase64(iv),\n ciphertext: bytesToBase64(ciphertext),\n });\n\n return {\n contentRecordId,\n encryptedData,\n metadataHash,\n plaintextHash,\n ciphertextHash:\n await computeDocumentContentRecordCiphertextHash(encryptedData),\n };\n}\n\nexport async function decryptDocumentSyncUpdates(input: {\n contentKey: Uint8Array;\n contentKeyEpoch: number;\n documentId: string;\n organizationId: string;\n updates: readonly DocumentSyncResponse[\"updates\"][number][];\n}): Promise<DecryptedDocumentSyncUpdate[]> {\n return decryptDocumentSyncUpdatesByEpoch({\n contentKeysByEpoch: new Map([[input.contentKeyEpoch, input.contentKey]]),\n documentId: input.documentId,\n organizationId: input.organizationId,\n updates: input.updates,\n });\n}\n\nexport async function decryptDocumentSyncUpdatesByEpoch(input: {\n contentKeysByEpoch: ReadonlyMap<number, Uint8Array>;\n documentId: string;\n organizationId: string;\n updates: readonly DocumentSyncResponse[\"updates\"][number][];\n}): Promise<DecryptedDocumentSyncUpdate[]> {\n if (input.updates.length === 0) {\n return [];\n }\n const contentKeyMaterialByEpoch = new Map<number, Promise<CryptoKey>>();\n const contentKeyMaterialForEpoch = (contentKeyEpoch: number) => {\n const existing = contentKeyMaterialByEpoch.get(contentKeyEpoch);\n if (existing) {\n return existing;\n }\n\n const contentKey = input.contentKeysByEpoch.get(contentKeyEpoch);\n if (!contentKey) {\n throw new Error(\"Document content key missing for sync update epoch\");\n }\n const imported = importContentKeyMaterial(contentKey);\n contentKeyMaterialByEpoch.set(contentKeyEpoch, imported);\n return imported;\n };\n\n const inspections = await Promise.allSettled(\n input.updates.map(async (update) => {\n let contentKeyEpoch: number;\n try {\n contentKeyEpoch = readWriteHeader(\n update.writeHeader,\n \"Document sync response write header\",\n ).contentKeyEpoch;\n } catch (error) {\n throw isolateDocumentSyncUpdateError({\n cause: error,\n responseUpdate: update,\n stage: \"write_header\",\n updateId: update.id,\n });\n }\n\n let contentKeyMaterial: CryptoKey;\n try {\n contentKeyMaterial = await contentKeyMaterialForEpoch(contentKeyEpoch);\n } catch (error) {\n throw isolateDocumentSyncUpdateError({\n cause: error,\n responseUpdate: update,\n stage: \"content_key\",\n updateId: update.id,\n });\n }\n\n return decryptDocumentSyncUpdate({\n contentKeyMaterial,\n contentKeyEpoch,\n documentId: input.documentId,\n organizationId: input.organizationId,\n update,\n });\n }),\n );\n const failures = inspections.flatMap((inspection, index) =>\n inspection.status === \"rejected\"\n ? [{ index, reason: inspection.reason }]\n : [],\n );\n const firstFailure = failures[0];\n if (failures.length === 1 && firstFailure) {\n throw firstFailure.reason;\n }\n if (failures.length > 1 && firstFailure) {\n const stage =\n isDocumentSyncUpdateIsolationError(firstFailure.reason) &&\n failures.every(\n ({ reason }) =>\n isDocumentSyncUpdateIsolationError(reason) &&\n reason.stage === firstFailure.reason.stage,\n )\n ? firstFailure.reason.stage\n : \"decrypt\";\n throw isolateDocumentSyncBatchError({\n // The per-update errors carry authenticated writer attribution. Once\n // more than one update fails, retaining those errors beneath an\n // anonymous batch boundary would disclose the very attribution the\n // boundary intentionally withholds.\n cause: new Error(\n \"Multiple document sync updates failed decryption inspection\",\n ),\n stage,\n updateIds: failures.map(\n ({ index }) => input.updates[index]?.id ?? \"unknown\",\n ),\n });\n }\n return inspections.map((inspection) => {\n if (inspection.status === \"rejected\") {\n throw inspection.reason;\n }\n return inspection.value;\n });\n}\n"]}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["crypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeContentRecordNonceDomainHash,\n computeDocumentContentRecordCiphertextHash,\n computeDocumentContentRecordMetadataHash,\n computeDocumentContentRecordPlaintextHash,\n createAesGcmIv,\n serializeKeyingCanonicalJson,\n} from \"@tearleads/crypto\";\nimport { base64ToBytes, bytesToBase64 } from \"@tearleads/encoding\";\nimport type { DocumentSyncResponse } from \"@tearleads/validators/response\";\nimport type { PendingUpdateRecord } from \"../../persistence/documents/records\";\nimport { contentRecordAdditionalDataBytes } from \"./contentRecordAdditionalData\";\nimport {\n deriveDocumentContentRecordKey,\n deriveDocumentPlaintextHashKey,\n importContentKeyMaterial,\n} from \"./contentRecordKeys\";\nimport { asWebCryptoBytes, readWriteHeader } from \"./readers\";\nimport { decryptDocumentSyncUpdate } from \"./sync/documentSyncUpdateDecryption\";\nimport {\n isDocumentSyncUpdateIsolationError,\n isolateDocumentSyncBatchError,\n isolateDocumentSyncUpdateError,\n} from \"./sync/documentSyncUpdateIsolation\";\nimport {\n type DecryptedDocumentSyncUpdate,\n DOCUMENT_ENCRYPTED_LORO_UPDATE_FORMAT,\n type DocumentEncryptedPendingUpdate,\n} from \"./types\";\n\nexport async function encryptDocumentPendingUpdate(input: {\n contentKeyMaterial: CryptoKey;\n contentKeyEpoch: number;\n documentId: string;\n organizationId: string;\n update: PendingUpdateRecord;\n}): Promise<DocumentEncryptedPendingUpdate> {\n const plaintext = base64ToBytes(input.update.updateData);\n const contentRecordId = input.update.id;\n const nonceDomainHash = await computeContentRecordNonceDomainHash({\n version: 1,\n organizationId: input.organizationId,\n objectKind: \"document\",\n objectId: input.documentId,\n contentKeyEpoch: input.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId,\n });\n const plaintextHashKey = await deriveDocumentPlaintextHashKey({\n contentKeyMaterial: input.contentKeyMaterial,\n contentKeyEpoch: input.contentKeyEpoch,\n contentRecordId,\n documentId: input.documentId,\n organizationId: input.organizationId,\n });\n const plaintextHash = await computeDocumentContentRecordPlaintextHash(\n plaintext,\n plaintextHashKey,\n );\n const metadataHash = await computeDocumentContentRecordMetadataHash({\n ...(input.update.sourceVersionVector\n ? {\n checkpointKind: \"rotate_baseline\" as const,\n checkpointPayloadKind: \"full_history_snapshot\" as const,\n sourceVersionVector: input.update.sourceVersionVector,\n }\n : {}),\n documentId: input.documentId,\n partialEndVersionVector: input.update.partialEndVersionVector,\n partialStartVersionVector: input.update.partialStartVersionVector,\n plaintextHash,\n updateId: input.update.id,\n });\n const recordKey = await deriveDocumentContentRecordKey({\n contentKeyMaterial: input.contentKeyMaterial,\n contentKeyEpoch: input.contentKeyEpoch,\n contentRecordId,\n documentId: input.documentId,\n organizationId: input.organizationId,\n usage: \"encrypt\",\n });\n const iv = createAesGcmIv();\n const ciphertext = new Uint8Array(\n await crypto.subtle.encrypt(\n {\n name: \"AES-GCM\",\n iv,\n additionalData: contentRecordAdditionalDataBytes({\n contentKeyEpoch: input.contentKeyEpoch,\n contentRecordId,\n documentId: input.documentId,\n metadataHash,\n nonceDomainHash,\n organizationId: input.organizationId,\n }),\n },\n recordKey,\n asWebCryptoBytes(plaintext),\n ),\n );\n const encryptedData = serializeKeyingCanonicalJson({\n format: DOCUMENT_ENCRYPTED_LORO_UPDATE_FORMAT,\n version: 1,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentKeyEpoch: input.contentKeyEpoch,\n contentRecordId,\n nonceDomainHash,\n metadataHash,\n iv: bytesToBase64(iv),\n ciphertext: bytesToBase64(ciphertext),\n });\n\n return {\n contentRecordId,\n encryptedData,\n metadataHash,\n plaintextHash,\n ciphertextHash:\n await computeDocumentContentRecordCiphertextHash(encryptedData),\n };\n}\n\nexport async function decryptDocumentSyncUpdates(input: {\n contentKey: Uint8Array;\n contentKeyEpoch: number;\n documentId: string;\n organizationId: string;\n updates: readonly DocumentSyncResponse[\"updates\"][number][];\n}): Promise<DecryptedDocumentSyncUpdate[]> {\n return decryptDocumentSyncUpdatesByEpoch({\n contentKeysByEpoch: new Map([[input.contentKeyEpoch, input.contentKey]]),\n documentId: input.documentId,\n organizationId: input.organizationId,\n updates: input.updates,\n });\n}\n\nexport async function decryptDocumentSyncUpdatesByEpoch(input: {\n contentKeysByEpoch: ReadonlyMap<number, Uint8Array>;\n documentId: string;\n organizationId: string;\n updates: readonly DocumentSyncResponse[\"updates\"][number][];\n}): Promise<DecryptedDocumentSyncUpdate[]> {\n if (input.updates.length === 0) {\n return [];\n }\n const contentKeyMaterialByEpoch = new Map<number, Promise<CryptoKey>>();\n const contentKeyMaterialForEpoch = (contentKeyEpoch: number) => {\n const existing = contentKeyMaterialByEpoch.get(contentKeyEpoch);\n if (existing) {\n return existing;\n }\n\n const contentKey = input.contentKeysByEpoch.get(contentKeyEpoch);\n if (!contentKey) {\n throw new Error(\"Document content key missing for sync update epoch\");\n }\n const imported = importContentKeyMaterial(contentKey);\n contentKeyMaterialByEpoch.set(contentKeyEpoch, imported);\n return imported;\n };\n\n const inspections = await Promise.allSettled(\n input.updates.map(async (update) => {\n let contentKeyEpoch: number;\n try {\n contentKeyEpoch = readWriteHeader(\n update.writeHeader,\n \"Document sync response write header\",\n ).contentKeyEpoch;\n } catch (error) {\n throw isolateDocumentSyncUpdateError({\n cause: error,\n responseUpdate: update,\n stage: \"write_header\",\n updateId: update.id,\n });\n }\n\n let contentKeyMaterial: CryptoKey;\n try {\n contentKeyMaterial = await contentKeyMaterialForEpoch(contentKeyEpoch);\n } catch (error) {\n throw isolateDocumentSyncUpdateError({\n cause: error,\n responseUpdate: update,\n stage: \"content_key\",\n updateId: update.id,\n });\n }\n\n return decryptDocumentSyncUpdate({\n contentKeyMaterial,\n contentKeyEpoch,\n documentId: input.documentId,\n organizationId: input.organizationId,\n update,\n });\n }),\n );\n const failures = inspections.flatMap((inspection, index) =>\n inspection.status === \"rejected\"\n ? [{ index, reason: inspection.reason }]\n : [],\n );\n const firstFailure = failures[0];\n if (failures.length === 1 && firstFailure) {\n throw firstFailure.reason;\n }\n if (failures.length > 1 && firstFailure) {\n const stage =\n isDocumentSyncUpdateIsolationError(firstFailure.reason) &&\n failures.every(\n ({ reason }) =>\n isDocumentSyncUpdateIsolationError(reason) &&\n reason.stage === firstFailure.reason.stage,\n )\n ? firstFailure.reason.stage\n : \"decrypt\";\n throw isolateDocumentSyncBatchError({\n // The per-update errors carry authenticated writer attribution. Once\n // more than one update fails, retaining those errors beneath an\n // anonymous batch boundary would disclose the very attribution the\n // boundary intentionally withholds.\n cause: new Error(\n \"Multiple document sync updates failed decryption inspection\",\n ),\n stage,\n updateIds: failures.map(\n ({ index }) => input.updates[index]?.id ?? \"unknown\",\n ),\n });\n }\n return inspections.map((inspection) => {\n if (inspection.status === \"rejected\") {\n throw inspection.reason;\n }\n return inspection.value;\n });\n}\n"]}
@@ -4,7 +4,7 @@ import type { ContainerManifestRef, DocumentContentKeyTargetEnvelope, DocumentCr
4
4
  import type { ContainerWriterProjectionResponse, DocumentCreateResponse, DocumentLinkSetMutationResponse, DocumentPurgeProofResponse, DocumentSyncResponse, DocumentWriterProjectionResponse, ListDocumentAttachmentsResponse } from "../../../internal/validators/response/index.js";
5
5
  import type { ContainerMutationAuthor, ContainerRekeyApi } from "../../containers/shared/types.js";
6
6
  import type { ProjectionUserKeyResolver, ReferencedPrincipalPolicyWarmer, DocumentWriterProjectionAuthorization as WriterAuthorization } from "../../keyingProjectionVerification/index.js";
7
- import type { DocumentRecord } from "../../sqlite/documentPersistence/index.js";
7
+ import type { DocumentRecord } from "../../persistence/documents/records/index.js";
8
8
  import type { ExecSql } from "../../sqlite/sqlSchema.js";
9
9
  import type { DocumentSyncSubmitFailure } from "./sync/syncSubmitFailure.js";
10
10
  export type { DocumentSyncSubmitFailure } from "./sync/syncSubmitFailure.js";
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAmCA,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC;AAOtF,MAAM,CAAC,MAAM,qCAAqC,GAChD,gCAAgC,CAAC;AACnC,MAAM,CAAC,MAAM,uCAAuC,GAClD,4CAA4C,CAAC;AAC/C,MAAM,CAAC,MAAM,sDAAsD,GACjE,2DAA2D,CAAC;AAC9D,MAAM,CAAC,MAAM,kCAAkC,GAC7C,uCAAuC,CAAC;AAC1C,MAAM,CAAC,MAAM,iCAAiC,GAC5C,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,6CAA6C,CAAC,CAAC;AAC1E,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,GAAG,CAAC;IACpD,YAAY;IACZ,iBAAiB;IACjB,iBAAiB;IACjB,iBAAiB;IACjB,QAAQ;IACR,IAAI;IACJ,cAAc;IACd,iBAAiB;IACjB,SAAS;CACV,CAAC,CAAC;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC;AAsB9C,MAAM,UAAU,yBAAyB,CACvC,KAAoC,EACpC,KAAa;IAEb,IAAI,KAAK,CAAC,wBAAwB,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,sBAAsB,KAAK,IAAI,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,yEAAyE,CAClF,CAAC;QACJ,CAAC;QACD,OAAO,gCAAgC,CACrC,KAAK,CAAC,wBAAwB,EAC9B,KAAK,CACN,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,sBAAsB,KAAK,IAAI,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,iFAAiF,CAC1F,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,6BAA6B,CAC3C,KAAoC;IAEpC,MAAM,IAAI,GAAG,KAAK,CAAC,+BAA+B,CAAC;IACnD,MAAM,MAAM,GAAG;QACb,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,+BAA+B,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3D,CAAC;IACF,IAAI,KAAK,CAAC,wBAAwB,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,sBAAsB,KAAK,IAAI,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CACb,4EAA4E,CAC7E,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,GAAG,MAAM;YACT,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;SACzD,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,sBAAsB,KAAK,IAAI,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC,OAAO;YAClB,CAAC,CAAC;gBACE,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,GAAG,MAAM;gBACT,sBAAsB,EAAE,IAAI;aAC7B;YACH,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,sBAAsB,EAAE,IAAI,EAAE,CAAC;IAClD,CAAC;IAED,MAAM,IAAI,KAAK,CACb,oFAAoF,CACrF,CAAC;AACJ,CAAC","sourcesContent":["import type { BlobBytesResponse } from \"@tearleads/api-client\";\nimport type {\n AccessEvent,\n AccessManifest,\n DocumentContentKeyTarget,\n DocumentLinkAccessEventBody,\n DocumentLinkSetManifestState,\n DocumentUnlinkAccessEventBody,\n} from \"@tearleads/crypto\";\nimport type {\n ContainerManifestRef,\n DocumentContentKeyTargetEnvelope,\n DocumentCreateRequest,\n DocumentLinkSetMutationRequest,\n DocumentOutgoingUpdate,\n DocumentSyncRequest,\n} from \"@tearleads/validators/request\";\nimport type {\n ContainerWriterProjectionResponse,\n DocumentCreateResponse,\n DocumentLinkSetMutationResponse,\n DocumentPurgeProofResponse,\n DocumentSyncResponse,\n DocumentWriterProjectionResponse,\n ListDocumentAttachmentsResponse,\n} from \"@tearleads/validators/response\";\nimport type {\n ContainerMutationAuthor,\n ContainerRekeyApi,\n} from \"../../containers/shared/types\";\nimport type {\n ProjectionUserKeyResolver,\n ReferencedPrincipalPolicyWarmer,\n DocumentWriterProjectionAuthorization as WriterAuthorization,\n} from \"../../keyingProjectionVerification\";\nimport { requireProjectionUserKeyResolver } from \"../../keyingProjectionVerification\";\nimport type { DocumentRecord } from \"../../sqlite/documentPersistence\";\nimport type { ExecSql } from \"../../sqlite/sqlSchema\";\nimport type { DocumentSyncSubmitFailure } from \"./sync/syncSubmitFailure\";\n\nexport type { DocumentSyncSubmitFailure } from \"./sync/syncSubmitFailure\";\n\nexport const DOCUMENT_ENCRYPTED_LORO_UPDATE_FORMAT =\n \"tearleads.document.loro-update\";\nexport const DOCUMENT_CONTENT_RECORD_KEY_INFO_DOMAIN =\n \"tearleads.document.content-record-key-info\";\nexport const DOCUMENT_CONTENT_RECORD_PLAINTEXT_HASH_KEY_INFO_DOMAIN =\n \"tearleads.document.content-record-plaintext-hash-key-info\";\nexport const DOCUMENT_CONTENT_RECORD_AAD_DOMAIN =\n \"tearleads.document.content-record-aad\";\nexport const DOCUMENT_CONTENT_RECORD_HKDF_SALT: Uint8Array<ArrayBuffer> =\n new TextEncoder().encode(\"tearleads.document.content-record-hkdf-salt\");\nexport const DOCUMENT_ENCRYPTED_UPDATE_KEYS = new Set([\n \"ciphertext\",\n \"contentKeyEpoch\",\n \"contentRecordId\",\n \"encryptionSuite\",\n \"format\",\n \"iv\",\n \"metadataHash\",\n \"nonceDomainHash\",\n \"version\",\n]);\nexport const TEXT_ENCODER = new TextEncoder();\ninterface ProjectionVerificationBase {\n readonly stillCurrent?: (() => boolean) | undefined;\n readonly warmReferencedPrincipalPolicies?:\n | ReferencedPrincipalPolicyWarmer\n | undefined;\n}\n\nexport type ProjectionVerificationOptions = ProjectionVerificationBase &\n (\n | {\n readonly execSql: ExecSql;\n readonly resolveProjectionUserKey: ProjectionUserKeyResolver;\n readonly trustedLocalProjection?: undefined;\n }\n | {\n readonly execSql?: ExecSql | undefined;\n readonly resolveProjectionUserKey?: undefined;\n readonly trustedLocalProjection: true;\n }\n );\n\nexport function resolveProjectionVerifier(\n input: ProjectionVerificationOptions,\n label: string,\n): ProjectionUserKeyResolver | null {\n if (input.resolveProjectionUserKey) {\n if (input.trustedLocalProjection === true) {\n throw new Error(\n `${label} cannot combine projection key verification with local projection trust`,\n );\n }\n return requireProjectionUserKeyResolver(\n input.resolveProjectionUserKey,\n label,\n );\n }\n if (input.trustedLocalProjection === true) {\n return null;\n }\n throw new Error(\n `${label} requires projection key verification or an explicitly trusted local projection`,\n );\n}\n\nexport function projectionVerificationOptions(\n input: ProjectionVerificationOptions,\n): ProjectionVerificationOptions {\n const warm = input.warmReferencedPrincipalPolicies;\n const shared = {\n ...(input.stillCurrent ? { stillCurrent: input.stillCurrent } : {}),\n ...(warm ? { warmReferencedPrincipalPolicies: warm } : {}),\n };\n if (input.resolveProjectionUserKey) {\n if (input.trustedLocalProjection === true) {\n throw new Error(\n \"Projection use cannot combine key verification with local projection trust\",\n );\n }\n return {\n execSql: input.execSql,\n ...shared,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n };\n }\n if (input.trustedLocalProjection === true) {\n return input.execSql\n ? {\n execSql: input.execSql,\n ...shared,\n trustedLocalProjection: true,\n }\n : { ...shared, trustedLocalProjection: true };\n }\n\n throw new Error(\n \"Projection use requires key verification or an explicitly trusted local projection\",\n );\n}\n\nexport type DocumentCreateAuthor = ContainerMutationAuthor;\n\nexport interface BuildDocumentCreatePlanInput {\n author: DocumentCreateAuthor;\n containerProjection: ContainerWriterProjectionResponse;\n contentKeyEpoch?: number;\n documentId?: string;\n eventId?: string;\n signedAt?: string;\n targetEnvelopes: readonly DocumentContentKeyTargetEnvelope[];\n}\n\nexport interface DocumentCreatePlan {\n body: DocumentLinkAccessEventBody;\n documentId: string;\n event: AccessEvent;\n eventHash: string;\n manifest: AccessManifest;\n manifestHash: string;\n request: DocumentCreateRequest;\n state: DocumentLinkSetManifestState;\n targetHash: string;\n targets: DocumentContentKeyTarget[];\n}\n\nexport interface MaterializedDocumentCreatePlan {\n contentKey: Uint8Array;\n plan: DocumentCreatePlan;\n}\n\nexport interface DocumentCreateApi {\n // Lets idempotent create inspect an expected already-exists conflict.\n createDocumentResult(\n input: DocumentCreateRequest,\n options?: DocumentSyncRequestResultOptions | undefined,\n ): Promise<\n | {\n readonly data: DocumentCreateResponse;\n readonly ok: true;\n }\n | DocumentSyncSubmitFailure\n >;\n getContainerWriterProjection(\n containerId: string,\n ): Promise<ContainerWriterProjectionResponse | null>;\n // Result-returning variant used by the create path so a terminal projection\n // fetch failure (e.g. a 402 billing block or a 403 after access revocation)\n // can be persisted to the write queue instead of collapsing to a silent\n // null.\n getContainerWriterProjectionResult(\n containerId: string,\n options?: DocumentSyncRequestResultOptions | undefined,\n ): Promise<\n | {\n readonly data: ContainerWriterProjectionResponse;\n readonly ok: true;\n }\n | DocumentSyncSubmitFailure\n >;\n // Needed to adopt an existing remote document on a create conflict: the\n // committed manifest, content-key bundle and KEK targets are refetched here\n // (the create response that carried them was lost).\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n evictContainerWriterProjection(containerId: string): void;\n primeDocumentWriterProjection(\n documentId: string,\n projection: DocumentWriterProjectionResponse,\n ): void;\n}\n\nexport interface CreateRemoteDocumentResult {\n contentKey: Uint8Array;\n documentId: string;\n persistedState: PersistedDocumentCreateState;\n // `plan` and `response` are absent when the result was adopted from an\n // existing remote document on a create conflict: there is no fresh create\n // response, and the local plan does not match the committed manifest (the\n // first attempt used a different content key). Consumers read only\n // documentId/writerProjection/persistedState.\n plan?: DocumentCreatePlan | undefined;\n response?: DocumentCreateResponse | undefined;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport type DocumentLinkSetMutationOperation = \"link\" | \"unlink\";\nexport type DocumentLinkSetMutationBody =\n | DocumentLinkAccessEventBody\n | DocumentUnlinkAccessEventBody;\n\nexport interface DocumentLinkSetTargetState {\n readonly currentTargets: readonly DocumentContentKeyTarget[];\n readonly linkedContainerIds: readonly string[];\n readonly target: DocumentContentKeyTarget;\n readonly targets: readonly DocumentContentKeyTarget[];\n}\n\nexport interface BuildDocumentLinkSetMutationPlanInput {\n blobRewraps: DocumentLinkAccessEventBody[\"blobRewraps\"];\n author: DocumentCreateAuthor;\n contentKeyEpoch: number;\n eventId?: string | undefined;\n operation: DocumentLinkSetMutationOperation;\n signedAt?: string | undefined;\n targetContainerProjection: ContainerWriterProjectionResponse;\n targetEnvelopes: readonly DocumentContentKeyTargetEnvelope[];\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DocumentLinkSetMutationPlan {\n body: DocumentLinkSetMutationBody;\n contentKeyEpoch: number;\n documentId: string;\n event: AccessEvent;\n eventHash: string;\n manifest: AccessManifest;\n manifestHash: string;\n operation: DocumentLinkSetMutationOperation;\n request: DocumentLinkSetMutationRequest;\n state: DocumentLinkSetManifestState;\n targetHash: string;\n targets: DocumentContentKeyTarget[];\n}\n\nexport interface MaterializedDocumentLinkSetMutationPlan {\n contentKey: Uint8Array;\n contentKeyRotated: boolean;\n plan: DocumentLinkSetMutationPlan;\n}\n\nexport interface DocumentLinkSetEventPlan {\n authorizingContainerPathRefs: ContainerManifestRef[][];\n body: DocumentLinkSetMutationBody;\n event: AccessEvent;\n eventHash: string;\n}\n\n/**\n * What a failed link-set mutation could tell the caller: the HTTP status (or\n * null for network/local failures) and a short message. Threaded up to the\n * move-intent queue so a permission failure reads as one, instead of the\n * generic \"rejected or unavailable\".\n */\nexport type DocumentLinkSetMutationFailure = Pick<\n DocumentSyncSubmitFailure,\n \"code\" | \"message\" | \"status\"\n>;\n\nexport type DocumentLinkSetFailureHandler = (\n failure: DocumentLinkSetMutationFailure,\n) => void;\n\ntype DocumentLinkSetMutationResult =\n | { readonly data: DocumentLinkSetMutationResponse; readonly ok: true }\n | (DocumentLinkSetMutationFailure & { readonly ok: false });\n\nexport interface DocumentLinkSetMutationApi {\n getBlobBytes(blobId: string): Promise<BlobBytesResponse | null>;\n listDocumentAttachments(\n documentId: string,\n ): Promise<ListDocumentAttachmentsResponse | null>;\n getContainerWriterProjection(\n containerId: string,\n ): Promise<ContainerWriterProjectionResponse | null>;\n getContainerWriterProjectionResult(\n containerId: string,\n options?: DocumentSyncRequestResultOptions | undefined,\n ): Promise<\n | {\n readonly data: ContainerWriterProjectionResponse;\n readonly ok: true;\n }\n | DocumentSyncSubmitFailure\n >;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n getDocumentWriterProjectionResult(\n documentId: string,\n options?: DocumentSyncRequestResultOptions | undefined,\n ): Promise<\n | {\n readonly data: DocumentWriterProjectionResponse;\n readonly ok: true;\n }\n | DocumentSyncSubmitFailure\n >;\n primeDocumentWriterProjection(\n documentId: string,\n projection: DocumentWriterProjectionResponse,\n ): void;\n linkDocumentResult(\n documentId: string,\n input: DocumentLinkSetMutationRequest,\n options?: DocumentSyncRequestResultOptions,\n ): Promise<DocumentLinkSetMutationResult>;\n unlinkDocumentResult(\n documentId: string,\n input: DocumentLinkSetMutationRequest,\n options?: DocumentSyncRequestResultOptions,\n ): Promise<DocumentLinkSetMutationResult>;\n}\n\nexport interface RelinkRemoteDocumentResult {\n contentKey: Uint8Array;\n contentKeyRotated: boolean;\n documentId: string;\n linkedContainerIds: readonly string[];\n persistedState: PersistedDocumentCreateState;\n plan: DocumentLinkSetMutationPlan;\n response: DocumentLinkSetMutationResponse;\n}\n\nexport interface DocumentSyncPreparedUpdate {\n checkpointKind?: DocumentOutgoingUpdate[\"checkpointKind\"] | undefined;\n checkpointPayloadKind?:\n | DocumentOutgoingUpdate[\"checkpointPayloadKind\"]\n | undefined;\n ciphertextHash: string;\n contentRecordId?: string | undefined;\n encryptedData: string;\n id: string;\n metadataHash: string;\n partialEndVersionVector: string;\n partialStartVersionVector: string;\n plaintextHash: string;\n signedAt?: string | undefined;\n sourceVersionVector?: string | undefined;\n}\n\nexport interface DocumentEncryptedPendingUpdate {\n contentRecordId: string;\n encryptedData: string;\n metadataHash: string;\n ciphertextHash: string;\n plaintextHash: string;\n}\n\nexport interface ParsedDocumentEncryptedUpdate {\n ciphertext: Uint8Array;\n contentKeyEpoch: number;\n contentRecordId: string;\n metadataHash: string;\n nonceDomainHash: string;\n iv: Uint8Array;\n}\n\nexport interface DecryptedDocumentSyncUpdate {\n checkpointKind?: \"rotate_baseline\";\n checkpointPayloadKind?: \"full_history_snapshot\";\n id: string;\n partialEndVersionVector: string;\n partialStartVersionVector: string;\n sourceVersionVector?: string;\n updateData: Uint8Array;\n}\n\nexport interface BuildDocumentSyncPlanInput {\n author: DocumentCreateAuthor;\n authorizingContainerPathRefs?: readonly (readonly ContainerManifestRef[])[];\n containerRekeys?: DocumentSyncRequest[\"containerRekeys\"];\n contentKeyBundle: DocumentCreateResponse[\"contentKeyBundle\"];\n documentId?: string | undefined;\n documentKekTargets: DocumentSyncResponse[\"documentKekTargets\"];\n documentManifest: DocumentCreateResponse[\"accessManifest\"];\n historyMode?: \"raw\" | undefined;\n inlineRekeyCommitId?: string | undefined;\n localVersionVector: string | null;\n minLsn?: string | undefined;\n outgoingUpdates?: readonly DocumentSyncPreparedUpdate[] | undefined;\n pullCursor?: string | undefined;\n signedAt?: string | undefined;\n}\n\nexport interface DocumentSyncPlan {\n contentKeyEpoch: number;\n documentId: string;\n documentKekTargets: DocumentSyncResponse[\"documentKekTargets\"];\n documentManifest: DocumentCreateResponse[\"accessManifest\"];\n documentWriterAuthorization?: WriterAuthorization | undefined;\n expectedLinkSetManifestHash: string;\n expectedTargetHash: string;\n minLsn?: string | undefined;\n organizationId: string;\n request: DocumentSyncRequest;\n sourceContentKeyBundle: DocumentCreateResponse[\"contentKeyBundle\"];\n}\n\nexport interface MaterializedDocumentSyncPlan {\n contentKey: Uint8Array;\n /**\n * True when the plan carries a re-wrapped content-key bundle at the next\n * epoch to heal a stale bundle (a linked container's KEK rotated under it).\n * On submit success the caller must evict this document's cached writer\n * projection so later passes see the healed state instead of re-healing.\n */\n healedStaleContentKeyBundle: boolean;\n /**\n * Pending-queue ids a heal deliberately did NOT submit (superseded rotation\n * checkpoints). On heal success they are reported as settled — the\n * committed covering baseline subsumes their content, and resubmitting them\n * post-heal could mask it as the latest baseline at the healed epoch.\n */\n heldBackPendingUpdateIds: readonly string[];\n plan: DocumentSyncPlan;\n writerProjection: DocumentWriterProjectionResponse;\n /** Synthetic baseline id; its ack does not settle a pending queue row. */\n staleRecoveryBaselineUpdateId?: string;\n}\n\nexport interface SyncRemoteDocumentResult {\n contentKey: Uint8Array;\n decryptedUpdates: DecryptedDocumentSyncUpdate[];\n /**\n * Pending updates whose persisted re-key bound is exhausted. Non-zero means\n * a terminal failure was just recorded for them — callers must not treat\n * the pass as clean and clear it.\n */\n exhaustedPendingUpdateCount: number;\n /**\n * True only when this bounded pass made durable progress but left pending\n * work. Headless hosts should refresh the queue and schedule another\n * pass. Terminal exhaustion reports false and uses the failure callback.\n */\n hasDeferredPendingUpdates: boolean;\n /** Verified page progress was persisted, but remote pages remain. */\n hasIncompletePull: boolean;\n persistedState: PersistedDocumentSyncState;\n plan: DocumentSyncPlan;\n rekeyedPendingUpdateIds: readonly string[];\n response: DocumentSyncResponse;\n settledPendingUpdateIds: readonly string[];\n acceptedRecoveryBaseline: boolean;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface DocumentSyncRequestResultOptions {\n readonly expectedPaymentRequiredOrganizationId?: string | undefined;\n readonly reportErrors?: boolean | undefined;\n}\n\nexport interface DocumentSyncApi {\n rekeyContainer: ContainerRekeyApi[\"rekeyContainer\"];\n /**\n * Optional status-bearing rekey. Present on the real client; an adapter that\n * omits it still repairs, but a permanent refusal cannot then be told from a\n * transient one and is reported as a plain abandon.\n */\n rekeyContainerResult?: ContainerRekeyApi[\"rekeyContainerResult\"];\n /**\n * Optional. A repair that must carry descendant rekeys reads each one's\n * projection; without this such a repair is refused and the pass abandons.\n */\n getContainerWriterProjection?: ContainerRekeyApi[\"getContainerWriterProjection\"];\n clearWriterProjectionCaches?(): void;\n evictDocumentWriterProjection?(documentId: string): void;\n getDocumentPurgeProof?(\n documentId: string,\n options?: {\n readonly documentCheckpointManifestHash?: string;\n },\n requestOptions?: DocumentSyncRequestResultOptions,\n ): Promise<DocumentPurgeProofResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n getDocumentWriterProjectionResult(\n documentId: string,\n options?: DocumentSyncRequestResultOptions | undefined,\n ): Promise<\n | {\n readonly data: DocumentWriterProjectionResponse;\n readonly ok: true;\n }\n | DocumentSyncSubmitFailure\n >;\n syncDocumentResult(\n documentId: string,\n input: DocumentSyncRequest,\n options?: DocumentSyncRequestResultOptions,\n ): Promise<\n | {\n readonly data: DocumentSyncResponse;\n readonly ok: true;\n }\n | DocumentSyncSubmitFailure\n >;\n}\n\nexport type DocumentWriterPublicKeyResolver = (input: {\n writerSigningKeyFingerprint: string;\n writerUserId: string;\n}) => Promise<Uint8Array | null>;\n\nexport type PersistedDocumentCreateState = Pick<\n DocumentRecord,\n | \"documentId\"\n | \"contentKeyBundle\"\n | \"documentKekTargets\"\n | \"documentManifestBundle\"\n>;\n\nexport type PersistedDocumentSyncState = PersistedDocumentCreateState;\n\nexport interface UnwrappedContainerKek {\n containerId: string;\n /**\n * Null for keyring-recovered historical keys, which carry key material and\n * the epoch id committing to it but no epoch record to hash.\n *\n * Such a key still satisfies a parent wrap: the epoch-record fingerprint is\n * a selector for choosing among envelopes, not the security boundary, so a\n * null hash falls back to selecting on the epoch id while AEAD authenticates\n * (see `containerKekPath.ts`). That is what lets a descendant pinned to a\n * rotated parent be opened rather than stranded.\n */\n keyEpochHash: string | null;\n keyMaterial: Uint8Array;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAmCA,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC;AAOtF,MAAM,CAAC,MAAM,qCAAqC,GAChD,gCAAgC,CAAC;AACnC,MAAM,CAAC,MAAM,uCAAuC,GAClD,4CAA4C,CAAC;AAC/C,MAAM,CAAC,MAAM,sDAAsD,GACjE,2DAA2D,CAAC;AAC9D,MAAM,CAAC,MAAM,kCAAkC,GAC7C,uCAAuC,CAAC;AAC1C,MAAM,CAAC,MAAM,iCAAiC,GAC5C,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,6CAA6C,CAAC,CAAC;AAC1E,MAAM,CAAC,MAAM,8BAA8B,GAAG,IAAI,GAAG,CAAC;IACpD,YAAY;IACZ,iBAAiB;IACjB,iBAAiB;IACjB,iBAAiB;IACjB,QAAQ;IACR,IAAI;IACJ,cAAc;IACd,iBAAiB;IACjB,SAAS;CACV,CAAC,CAAC;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC;AAsB9C,MAAM,UAAU,yBAAyB,CACvC,KAAoC,EACpC,KAAa;IAEb,IAAI,KAAK,CAAC,wBAAwB,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,sBAAsB,KAAK,IAAI,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,yEAAyE,CAClF,CAAC;QACJ,CAAC;QACD,OAAO,gCAAgC,CACrC,KAAK,CAAC,wBAAwB,EAC9B,KAAK,CACN,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,sBAAsB,KAAK,IAAI,EAAE,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,iFAAiF,CAC1F,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,6BAA6B,CAC3C,KAAoC;IAEpC,MAAM,IAAI,GAAG,KAAK,CAAC,+BAA+B,CAAC;IACnD,MAAM,MAAM,GAAG;QACb,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,+BAA+B,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3D,CAAC;IACF,IAAI,KAAK,CAAC,wBAAwB,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,sBAAsB,KAAK,IAAI,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CACb,4EAA4E,CAC7E,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,GAAG,MAAM;YACT,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;SACzD,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,sBAAsB,KAAK,IAAI,EAAE,CAAC;QAC1C,OAAO,KAAK,CAAC,OAAO;YAClB,CAAC,CAAC;gBACE,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,GAAG,MAAM;gBACT,sBAAsB,EAAE,IAAI;aAC7B;YACH,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,sBAAsB,EAAE,IAAI,EAAE,CAAC;IAClD,CAAC;IAED,MAAM,IAAI,KAAK,CACb,oFAAoF,CACrF,CAAC;AACJ,CAAC","sourcesContent":["import type { BlobBytesResponse } from \"@tearleads/api-client\";\nimport type {\n AccessEvent,\n AccessManifest,\n DocumentContentKeyTarget,\n DocumentLinkAccessEventBody,\n DocumentLinkSetManifestState,\n DocumentUnlinkAccessEventBody,\n} from \"@tearleads/crypto\";\nimport type {\n ContainerManifestRef,\n DocumentContentKeyTargetEnvelope,\n DocumentCreateRequest,\n DocumentLinkSetMutationRequest,\n DocumentOutgoingUpdate,\n DocumentSyncRequest,\n} from \"@tearleads/validators/request\";\nimport type {\n ContainerWriterProjectionResponse,\n DocumentCreateResponse,\n DocumentLinkSetMutationResponse,\n DocumentPurgeProofResponse,\n DocumentSyncResponse,\n DocumentWriterProjectionResponse,\n ListDocumentAttachmentsResponse,\n} from \"@tearleads/validators/response\";\nimport type {\n ContainerMutationAuthor,\n ContainerRekeyApi,\n} from \"../../containers/shared/types\";\nimport type {\n ProjectionUserKeyResolver,\n ReferencedPrincipalPolicyWarmer,\n DocumentWriterProjectionAuthorization as WriterAuthorization,\n} from \"../../keyingProjectionVerification\";\nimport { requireProjectionUserKeyResolver } from \"../../keyingProjectionVerification\";\nimport type { DocumentRecord } from \"../../persistence/documents/records\";\nimport type { ExecSql } from \"../../sqlite/sqlSchema\";\nimport type { DocumentSyncSubmitFailure } from \"./sync/syncSubmitFailure\";\n\nexport type { DocumentSyncSubmitFailure } from \"./sync/syncSubmitFailure\";\n\nexport const DOCUMENT_ENCRYPTED_LORO_UPDATE_FORMAT =\n \"tearleads.document.loro-update\";\nexport const DOCUMENT_CONTENT_RECORD_KEY_INFO_DOMAIN =\n \"tearleads.document.content-record-key-info\";\nexport const DOCUMENT_CONTENT_RECORD_PLAINTEXT_HASH_KEY_INFO_DOMAIN =\n \"tearleads.document.content-record-plaintext-hash-key-info\";\nexport const DOCUMENT_CONTENT_RECORD_AAD_DOMAIN =\n \"tearleads.document.content-record-aad\";\nexport const DOCUMENT_CONTENT_RECORD_HKDF_SALT: Uint8Array<ArrayBuffer> =\n new TextEncoder().encode(\"tearleads.document.content-record-hkdf-salt\");\nexport const DOCUMENT_ENCRYPTED_UPDATE_KEYS = new Set([\n \"ciphertext\",\n \"contentKeyEpoch\",\n \"contentRecordId\",\n \"encryptionSuite\",\n \"format\",\n \"iv\",\n \"metadataHash\",\n \"nonceDomainHash\",\n \"version\",\n]);\nexport const TEXT_ENCODER = new TextEncoder();\ninterface ProjectionVerificationBase {\n readonly stillCurrent?: (() => boolean) | undefined;\n readonly warmReferencedPrincipalPolicies?:\n | ReferencedPrincipalPolicyWarmer\n | undefined;\n}\n\nexport type ProjectionVerificationOptions = ProjectionVerificationBase &\n (\n | {\n readonly execSql: ExecSql;\n readonly resolveProjectionUserKey: ProjectionUserKeyResolver;\n readonly trustedLocalProjection?: undefined;\n }\n | {\n readonly execSql?: ExecSql | undefined;\n readonly resolveProjectionUserKey?: undefined;\n readonly trustedLocalProjection: true;\n }\n );\n\nexport function resolveProjectionVerifier(\n input: ProjectionVerificationOptions,\n label: string,\n): ProjectionUserKeyResolver | null {\n if (input.resolveProjectionUserKey) {\n if (input.trustedLocalProjection === true) {\n throw new Error(\n `${label} cannot combine projection key verification with local projection trust`,\n );\n }\n return requireProjectionUserKeyResolver(\n input.resolveProjectionUserKey,\n label,\n );\n }\n if (input.trustedLocalProjection === true) {\n return null;\n }\n throw new Error(\n `${label} requires projection key verification or an explicitly trusted local projection`,\n );\n}\n\nexport function projectionVerificationOptions(\n input: ProjectionVerificationOptions,\n): ProjectionVerificationOptions {\n const warm = input.warmReferencedPrincipalPolicies;\n const shared = {\n ...(input.stillCurrent ? { stillCurrent: input.stillCurrent } : {}),\n ...(warm ? { warmReferencedPrincipalPolicies: warm } : {}),\n };\n if (input.resolveProjectionUserKey) {\n if (input.trustedLocalProjection === true) {\n throw new Error(\n \"Projection use cannot combine key verification with local projection trust\",\n );\n }\n return {\n execSql: input.execSql,\n ...shared,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n };\n }\n if (input.trustedLocalProjection === true) {\n return input.execSql\n ? {\n execSql: input.execSql,\n ...shared,\n trustedLocalProjection: true,\n }\n : { ...shared, trustedLocalProjection: true };\n }\n\n throw new Error(\n \"Projection use requires key verification or an explicitly trusted local projection\",\n );\n}\n\nexport type DocumentCreateAuthor = ContainerMutationAuthor;\n\nexport interface BuildDocumentCreatePlanInput {\n author: DocumentCreateAuthor;\n containerProjection: ContainerWriterProjectionResponse;\n contentKeyEpoch?: number;\n documentId?: string;\n eventId?: string;\n signedAt?: string;\n targetEnvelopes: readonly DocumentContentKeyTargetEnvelope[];\n}\n\nexport interface DocumentCreatePlan {\n body: DocumentLinkAccessEventBody;\n documentId: string;\n event: AccessEvent;\n eventHash: string;\n manifest: AccessManifest;\n manifestHash: string;\n request: DocumentCreateRequest;\n state: DocumentLinkSetManifestState;\n targetHash: string;\n targets: DocumentContentKeyTarget[];\n}\n\nexport interface MaterializedDocumentCreatePlan {\n contentKey: Uint8Array;\n plan: DocumentCreatePlan;\n}\n\nexport interface DocumentCreateApi {\n // Lets idempotent create inspect an expected already-exists conflict.\n createDocumentResult(\n input: DocumentCreateRequest,\n options?: DocumentSyncRequestResultOptions | undefined,\n ): Promise<\n | {\n readonly data: DocumentCreateResponse;\n readonly ok: true;\n }\n | DocumentSyncSubmitFailure\n >;\n getContainerWriterProjection(\n containerId: string,\n ): Promise<ContainerWriterProjectionResponse | null>;\n // Result-returning variant used by the create path so a terminal projection\n // fetch failure (e.g. a 402 billing block or a 403 after access revocation)\n // can be persisted to the write queue instead of collapsing to a silent\n // null.\n getContainerWriterProjectionResult(\n containerId: string,\n options?: DocumentSyncRequestResultOptions | undefined,\n ): Promise<\n | {\n readonly data: ContainerWriterProjectionResponse;\n readonly ok: true;\n }\n | DocumentSyncSubmitFailure\n >;\n // Needed to adopt an existing remote document on a create conflict: the\n // committed manifest, content-key bundle and KEK targets are refetched here\n // (the create response that carried them was lost).\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n evictContainerWriterProjection(containerId: string): void;\n primeDocumentWriterProjection(\n documentId: string,\n projection: DocumentWriterProjectionResponse,\n ): void;\n}\n\nexport interface CreateRemoteDocumentResult {\n contentKey: Uint8Array;\n documentId: string;\n persistedState: PersistedDocumentCreateState;\n // `plan` and `response` are absent when the result was adopted from an\n // existing remote document on a create conflict: there is no fresh create\n // response, and the local plan does not match the committed manifest (the\n // first attempt used a different content key). Consumers read only\n // documentId/writerProjection/persistedState.\n plan?: DocumentCreatePlan | undefined;\n response?: DocumentCreateResponse | undefined;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport type DocumentLinkSetMutationOperation = \"link\" | \"unlink\";\nexport type DocumentLinkSetMutationBody =\n | DocumentLinkAccessEventBody\n | DocumentUnlinkAccessEventBody;\n\nexport interface DocumentLinkSetTargetState {\n readonly currentTargets: readonly DocumentContentKeyTarget[];\n readonly linkedContainerIds: readonly string[];\n readonly target: DocumentContentKeyTarget;\n readonly targets: readonly DocumentContentKeyTarget[];\n}\n\nexport interface BuildDocumentLinkSetMutationPlanInput {\n blobRewraps: DocumentLinkAccessEventBody[\"blobRewraps\"];\n author: DocumentCreateAuthor;\n contentKeyEpoch: number;\n eventId?: string | undefined;\n operation: DocumentLinkSetMutationOperation;\n signedAt?: string | undefined;\n targetContainerProjection: ContainerWriterProjectionResponse;\n targetEnvelopes: readonly DocumentContentKeyTargetEnvelope[];\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DocumentLinkSetMutationPlan {\n body: DocumentLinkSetMutationBody;\n contentKeyEpoch: number;\n documentId: string;\n event: AccessEvent;\n eventHash: string;\n manifest: AccessManifest;\n manifestHash: string;\n operation: DocumentLinkSetMutationOperation;\n request: DocumentLinkSetMutationRequest;\n state: DocumentLinkSetManifestState;\n targetHash: string;\n targets: DocumentContentKeyTarget[];\n}\n\nexport interface MaterializedDocumentLinkSetMutationPlan {\n contentKey: Uint8Array;\n contentKeyRotated: boolean;\n plan: DocumentLinkSetMutationPlan;\n}\n\nexport interface DocumentLinkSetEventPlan {\n authorizingContainerPathRefs: ContainerManifestRef[][];\n body: DocumentLinkSetMutationBody;\n event: AccessEvent;\n eventHash: string;\n}\n\n/**\n * What a failed link-set mutation could tell the caller: the HTTP status (or\n * null for network/local failures) and a short message. Threaded up to the\n * move-intent queue so a permission failure reads as one, instead of the\n * generic \"rejected or unavailable\".\n */\nexport type DocumentLinkSetMutationFailure = Pick<\n DocumentSyncSubmitFailure,\n \"code\" | \"message\" | \"status\"\n>;\n\nexport type DocumentLinkSetFailureHandler = (\n failure: DocumentLinkSetMutationFailure,\n) => void;\n\ntype DocumentLinkSetMutationResult =\n | { readonly data: DocumentLinkSetMutationResponse; readonly ok: true }\n | (DocumentLinkSetMutationFailure & { readonly ok: false });\n\nexport interface DocumentLinkSetMutationApi {\n getBlobBytes(blobId: string): Promise<BlobBytesResponse | null>;\n listDocumentAttachments(\n documentId: string,\n ): Promise<ListDocumentAttachmentsResponse | null>;\n getContainerWriterProjection(\n containerId: string,\n ): Promise<ContainerWriterProjectionResponse | null>;\n getContainerWriterProjectionResult(\n containerId: string,\n options?: DocumentSyncRequestResultOptions | undefined,\n ): Promise<\n | {\n readonly data: ContainerWriterProjectionResponse;\n readonly ok: true;\n }\n | DocumentSyncSubmitFailure\n >;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n getDocumentWriterProjectionResult(\n documentId: string,\n options?: DocumentSyncRequestResultOptions | undefined,\n ): Promise<\n | {\n readonly data: DocumentWriterProjectionResponse;\n readonly ok: true;\n }\n | DocumentSyncSubmitFailure\n >;\n primeDocumentWriterProjection(\n documentId: string,\n projection: DocumentWriterProjectionResponse,\n ): void;\n linkDocumentResult(\n documentId: string,\n input: DocumentLinkSetMutationRequest,\n options?: DocumentSyncRequestResultOptions,\n ): Promise<DocumentLinkSetMutationResult>;\n unlinkDocumentResult(\n documentId: string,\n input: DocumentLinkSetMutationRequest,\n options?: DocumentSyncRequestResultOptions,\n ): Promise<DocumentLinkSetMutationResult>;\n}\n\nexport interface RelinkRemoteDocumentResult {\n contentKey: Uint8Array;\n contentKeyRotated: boolean;\n documentId: string;\n linkedContainerIds: readonly string[];\n persistedState: PersistedDocumentCreateState;\n plan: DocumentLinkSetMutationPlan;\n response: DocumentLinkSetMutationResponse;\n}\n\nexport interface DocumentSyncPreparedUpdate {\n checkpointKind?: DocumentOutgoingUpdate[\"checkpointKind\"] | undefined;\n checkpointPayloadKind?:\n | DocumentOutgoingUpdate[\"checkpointPayloadKind\"]\n | undefined;\n ciphertextHash: string;\n contentRecordId?: string | undefined;\n encryptedData: string;\n id: string;\n metadataHash: string;\n partialEndVersionVector: string;\n partialStartVersionVector: string;\n plaintextHash: string;\n signedAt?: string | undefined;\n sourceVersionVector?: string | undefined;\n}\n\nexport interface DocumentEncryptedPendingUpdate {\n contentRecordId: string;\n encryptedData: string;\n metadataHash: string;\n ciphertextHash: string;\n plaintextHash: string;\n}\n\nexport interface ParsedDocumentEncryptedUpdate {\n ciphertext: Uint8Array;\n contentKeyEpoch: number;\n contentRecordId: string;\n metadataHash: string;\n nonceDomainHash: string;\n iv: Uint8Array;\n}\n\nexport interface DecryptedDocumentSyncUpdate {\n checkpointKind?: \"rotate_baseline\";\n checkpointPayloadKind?: \"full_history_snapshot\";\n id: string;\n partialEndVersionVector: string;\n partialStartVersionVector: string;\n sourceVersionVector?: string;\n updateData: Uint8Array;\n}\n\nexport interface BuildDocumentSyncPlanInput {\n author: DocumentCreateAuthor;\n authorizingContainerPathRefs?: readonly (readonly ContainerManifestRef[])[];\n containerRekeys?: DocumentSyncRequest[\"containerRekeys\"];\n contentKeyBundle: DocumentCreateResponse[\"contentKeyBundle\"];\n documentId?: string | undefined;\n documentKekTargets: DocumentSyncResponse[\"documentKekTargets\"];\n documentManifest: DocumentCreateResponse[\"accessManifest\"];\n historyMode?: \"raw\" | undefined;\n inlineRekeyCommitId?: string | undefined;\n localVersionVector: string | null;\n minLsn?: string | undefined;\n outgoingUpdates?: readonly DocumentSyncPreparedUpdate[] | undefined;\n pullCursor?: string | undefined;\n signedAt?: string | undefined;\n}\n\nexport interface DocumentSyncPlan {\n contentKeyEpoch: number;\n documentId: string;\n documentKekTargets: DocumentSyncResponse[\"documentKekTargets\"];\n documentManifest: DocumentCreateResponse[\"accessManifest\"];\n documentWriterAuthorization?: WriterAuthorization | undefined;\n expectedLinkSetManifestHash: string;\n expectedTargetHash: string;\n minLsn?: string | undefined;\n organizationId: string;\n request: DocumentSyncRequest;\n sourceContentKeyBundle: DocumentCreateResponse[\"contentKeyBundle\"];\n}\n\nexport interface MaterializedDocumentSyncPlan {\n contentKey: Uint8Array;\n /**\n * True when the plan carries a re-wrapped content-key bundle at the next\n * epoch to heal a stale bundle (a linked container's KEK rotated under it).\n * On submit success the caller must evict this document's cached writer\n * projection so later passes see the healed state instead of re-healing.\n */\n healedStaleContentKeyBundle: boolean;\n /**\n * Pending-queue ids a heal deliberately did NOT submit (superseded rotation\n * checkpoints). On heal success they are reported as settled — the\n * committed covering baseline subsumes their content, and resubmitting them\n * post-heal could mask it as the latest baseline at the healed epoch.\n */\n heldBackPendingUpdateIds: readonly string[];\n plan: DocumentSyncPlan;\n writerProjection: DocumentWriterProjectionResponse;\n /** Synthetic baseline id; its ack does not settle a pending queue row. */\n staleRecoveryBaselineUpdateId?: string;\n}\n\nexport interface SyncRemoteDocumentResult {\n contentKey: Uint8Array;\n decryptedUpdates: DecryptedDocumentSyncUpdate[];\n /**\n * Pending updates whose persisted re-key bound is exhausted. Non-zero means\n * a terminal failure was just recorded for them — callers must not treat\n * the pass as clean and clear it.\n */\n exhaustedPendingUpdateCount: number;\n /**\n * True only when this bounded pass made durable progress but left pending\n * work. Headless hosts should refresh the queue and schedule another\n * pass. Terminal exhaustion reports false and uses the failure callback.\n */\n hasDeferredPendingUpdates: boolean;\n /** Verified page progress was persisted, but remote pages remain. */\n hasIncompletePull: boolean;\n persistedState: PersistedDocumentSyncState;\n plan: DocumentSyncPlan;\n rekeyedPendingUpdateIds: readonly string[];\n response: DocumentSyncResponse;\n settledPendingUpdateIds: readonly string[];\n acceptedRecoveryBaseline: boolean;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface DocumentSyncRequestResultOptions {\n readonly expectedPaymentRequiredOrganizationId?: string | undefined;\n readonly reportErrors?: boolean | undefined;\n}\n\nexport interface DocumentSyncApi {\n rekeyContainer: ContainerRekeyApi[\"rekeyContainer\"];\n /**\n * Optional status-bearing rekey. Present on the real client; an adapter that\n * omits it still repairs, but a permanent refusal cannot then be told from a\n * transient one and is reported as a plain abandon.\n */\n rekeyContainerResult?: ContainerRekeyApi[\"rekeyContainerResult\"];\n /**\n * Optional. A repair that must carry descendant rekeys reads each one's\n * projection; without this such a repair is refused and the pass abandons.\n */\n getContainerWriterProjection?: ContainerRekeyApi[\"getContainerWriterProjection\"];\n clearWriterProjectionCaches?(): void;\n evictDocumentWriterProjection?(documentId: string): void;\n getDocumentPurgeProof?(\n documentId: string,\n options?: {\n readonly documentCheckpointManifestHash?: string;\n },\n requestOptions?: DocumentSyncRequestResultOptions,\n ): Promise<DocumentPurgeProofResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n getDocumentWriterProjectionResult(\n documentId: string,\n options?: DocumentSyncRequestResultOptions | undefined,\n ): Promise<\n | {\n readonly data: DocumentWriterProjectionResponse;\n readonly ok: true;\n }\n | DocumentSyncSubmitFailure\n >;\n syncDocumentResult(\n documentId: string,\n input: DocumentSyncRequest,\n options?: DocumentSyncRequestResultOptions,\n ): Promise<\n | {\n readonly data: DocumentSyncResponse;\n readonly ok: true;\n }\n | DocumentSyncSubmitFailure\n >;\n}\n\nexport type DocumentWriterPublicKeyResolver = (input: {\n writerSigningKeyFingerprint: string;\n writerUserId: string;\n}) => Promise<Uint8Array | null>;\n\nexport type PersistedDocumentCreateState = Pick<\n DocumentRecord,\n | \"documentId\"\n | \"contentKeyBundle\"\n | \"documentKekTargets\"\n | \"documentManifestBundle\"\n>;\n\nexport type PersistedDocumentSyncState = PersistedDocumentCreateState;\n\nexport interface UnwrappedContainerKek {\n containerId: string;\n /**\n * Null for keyring-recovered historical keys, which carry key material and\n * the epoch id committing to it but no epoch record to hash.\n *\n * Such a key still satisfies a parent wrap: the epoch-record fingerprint is\n * a selector for choosing among envelopes, not the security boundary, so a\n * null hash falls back to selecting on the epoch id while AEAD authenticates\n * (see `containerKekPath.ts`). That is what lets a descendant pinned to a\n * rotated parent be opened rather than stranded.\n */\n keyEpochHash: string | null;\n keyMaterial: Uint8Array;\n}\n"]}
@@ -1,9 +1,9 @@
1
1
  import { eq } from "drizzle-orm";
2
- import { enqueueDocumentPendingUpdate, ensureDocumentProjectionTables, ensureDocumentTables, } from "../../sqlite/documentPersistence/index.js";
3
- import { hasQueuedDocumentUpdates, settleQueuedDocumentUpdatesInTransaction, } from "../../sqlite/documentPersistence/documentPendingUpdatePersistence.js";
4
2
  import { containerHydrationTombstones, containers, documentContainerProjectionTables, localIntentTables, } from "../../sqlite/schema/index.js";
5
3
  import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
6
4
  import { ensureSqlTables, runOncePerConnection, runSerializedSqlMutation, } from "../../sqlite/sqlSchema.js";
5
+ import { enqueueDocumentPendingUpdate, ensureDocumentProjectionTables, ensureDocumentTables, } from "../documents/records/index.js";
6
+ import { hasQueuedDocumentUpdates, settleQueuedDocumentUpdatesInTransaction, } from "../documents/records/documentPendingUpdatePersistence.js";
7
7
  import { getLatestTimestamp } from "../latestTimestamp.js";
8
8
  import { ensureContainerTables, loadContainerById, loadContainers as loadContainerRecords, } from "./containerPersistence.js";
9
9
  import { sqlContainerSyncWatermarkPersistence } from "./containerSyncWatermarkPersistence.js";
@@ -1 +1 @@
1
- {"version":3,"file":"containerContentsPersistence.js","sourceRoot":"","sources":["containerContentsPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { eq } from \"drizzle-orm\";\nimport {\n enqueueDocumentPendingUpdate,\n ensureDocumentProjectionTables,\n ensureDocumentTables,\n} from \"../../sqlite/documentPersistence\";\nimport {\n hasQueuedDocumentUpdates,\n settleQueuedDocumentUpdatesInTransaction,\n} from \"../../sqlite/documentPersistence/documentPendingUpdatePersistence\";\nimport {\n containerHydrationTombstones,\n containers,\n documentContainerProjectionTables,\n localIntentTables,\n} from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n ensureSqlTables,\n runOncePerConnection,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport { getLatestTimestamp } from \"../latestTimestamp\";\nimport {\n ensureContainerTables,\n loadContainerById,\n loadContainers as loadContainerRecords,\n} from \"./containerPersistence\";\nimport { sqlContainerSyncWatermarkPersistence } from \"./containerSyncWatermarkPersistence\";\nimport {\n claimDormantMetadataSweepAttempt,\n completeDormantMetadataSweepRequest,\n listDormantMetadataSweepRequests,\n} from \"./dormantMetadataSweep\";\n\nexport type {\n ContainerContentsPersistence,\n ContainerCreateIntentRecord,\n ContainerHydrationTombstone,\n ContainerMetadataRecord,\n ContainerMoveIntentRecord,\n LocalRootDescendantReparentInput,\n StoredContainerState,\n} from \"./containerContentsPersistenceTypes\";\nexport { CONTAINER_METADATA_APP_KIND } from \"./dormantContainerMetadata\";\n\nimport type {\n ContainerContentsPersistence,\n ContainerMetadataRecord,\n SaveContainerOptions,\n SaveContainerWithPendingUpdateOptions,\n} from \"./containerContentsPersistenceTypes\";\nimport { deleteStoredContainers } from \"./containerDeletionPersistence\";\nimport {\n commitStoredHydratedContainer,\n loadStoredHeldContainerBinding,\n} from \"./containerHydrationPersistence\";\nimport { containerIntentPersistence } from \"./containerIntentPersistence\";\nimport {\n commitStoredMetadataMutation,\n settleStoredMetadataPendingUpdates,\n} from \"./containerMetadataMutationPersistence\";\nimport { containerMetadataPullContinuationPersistence } from \"./containerMetadataPullContinuationPersistence\";\nimport {\n getContainerMetadataScope,\n saveContainerContentsContainerRows,\n selectContainerMetadataRecord,\n} from \"./containerMetadataRows\";\nimport { abandonStoredMoveIntentRevision } from \"./containerMoveAbandonPersistence\";\nimport { containerPendingUpdatePersistence } from \"./containerPendingUpdatePersistence\";\nimport { rebindStoredHeldContainer } from \"./containerRebindPersistence\";\nimport { containerReconcilePersistence } from \"./containerReconcilePersistence\";\nimport { isSupersededContainerBinding } from \"./supersededContainerBindings\";\n\nasync function saveStoredContainer(\n execSql: Parameters<ContainerContentsPersistence[\"saveContainer\"]>[0],\n container: Parameters<ContainerContentsPersistence[\"saveContainer\"]>[1],\n record: ContainerMetadataRecord | null,\n options?:\n | SaveContainerOptions\n | SaveContainerWithPendingUpdateOptions\n | undefined,\n) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n const localUpdatedAt =\n options?.localUpdatedAt ?? options?.updatedAt ?? new Date().toISOString();\n const runtime = getClientSQLitePersistenceRuntime(lockedExecSql);\n const save = async (tx: ClientSQLiteTransactionScope) => {\n const saved = await saveContainerContentsContainerRows({\n container,\n createIntent: options?.createIntent,\n moveIntent: options?.moveIntent,\n record,\n serverTimestamps: options?.serverTimestamps,\n tx,\n localUpdatedAt,\n });\n if (options && \"pendingUpdate\" in options) {\n await enqueueDocumentPendingUpdate(\n lockedExecSql,\n getContainerMetadataScope(container.id),\n options.pendingUpdate,\n );\n }\n return saved;\n };\n if (!options?.stillCurrent) {\n return runtime.transaction(save);\n }\n const outcome = await runtime.guardedTransaction(\n save,\n options.stillCurrent,\n { behavior: \"immediate\" },\n );\n // Guarded callers recheck the same monotonic generation immediately after\n // this await. Returning the untouched candidate keeps the long-standing\n // non-null save contract while the transaction itself remains rolled back.\n return outcome.result ?? container;\n });\n}\n\nexport const sqlContainerContentsPersistence: ContainerContentsPersistence = {\n ...containerIntentPersistence,\n ...containerMetadataPullContinuationPersistence,\n commitHydratedContainer: commitStoredHydratedContainer,\n commitMetadataMutation: commitStoredMetadataMutation,\n settleAcceptedMetadataPendingUpdates: settleStoredMetadataPendingUpdates,\n listDormantMetadataSweepRequests,\n ...containerPendingUpdatePersistence,\n ...containerReconcilePersistence,\n claimDormantMetadataSweepAttempt,\n completeDormantMetadataSweepRequest,\n async containerExists(execSql, containerId) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({ id: containers.id })\n .from(containers)\n .where(eq(containers.id, containerId))\n .limit(1);\n return rows.length > 0;\n },\n async deleteContainer(execSql, containerId, options) {\n await sqlContainerContentsPersistence.deleteContainers(execSql, [\n {\n containerId,\n reason: options?.reason ?? \"deleted\",\n updatedAt: options?.updatedAt ?? new Date().toISOString(),\n },\n ]);\n },\n async deleteContainers(execSql, removals, options) {\n return deleteStoredContainers(execSql, removals, options);\n },\n async ensureSchema(execSql) {\n // Once ensured on this connection, skip the outer mutation lock entirely:\n // ensureSchema runs on every query path, and re-acquiring the lock just to\n // no-op would queue reads behind unrelated writes.\n await runOncePerConnection(execSql, \"ensure:container-contents\", () =>\n runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureContainerTables(lockedExecSql);\n await ensureDocumentTables(lockedExecSql);\n await ensureSqlTables(lockedExecSql, localIntentTables);\n await ensureSqlTables(lockedExecSql, documentContainerProjectionTables);\n await ensureDocumentProjectionTables(lockedExecSql);\n await sqlContainerSyncWatermarkPersistence.ensureSchema(lockedExecSql);\n }),\n );\n },\n async loadHeldContainerBinding(execSql, containerId) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n return loadStoredHeldContainerBinding(execSql, containerId);\n },\n async isSupersededContainerBinding(execSql, input) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n return isSupersededContainerBinding(execSql, input);\n },\n async abandonMoveIntentRevision(execSql, input) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n return abandonStoredMoveIntentRevision(execSql, input);\n },\n async rebindHeldContainer(execSql, input) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n return rebindStoredHeldContainer(execSql, input);\n },\n async loadContainerMetadataRecord(execSql, containerId) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n return selectContainerMetadataRecord(execSql, containerId);\n },\n async loadContainerHydrationTombstones(execSql) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n containerId: containerHydrationTombstones.containerId,\n generation: containerHydrationTombstones.generation,\n reason: containerHydrationTombstones.reason,\n updatedAt: containerHydrationTombstones.updatedAt,\n })\n .from(containerHydrationTombstones)\n .where(eq(containerHydrationTombstones.cleared, false));\n return rows.flatMap((row) =>\n row.reason === \"access_revoked\" || row.reason === \"deleted\"\n ? [{ ...row, reason: row.reason }]\n : [],\n );\n },\n async loadContainers(execSql) {\n const containers = await loadContainerRecords(execSql);\n const storedContainers = await Promise.all(\n containers.map(async (container) => ({\n container,\n record: await selectContainerMetadataRecord(execSql, container.id),\n })),\n );\n\n return storedContainers;\n },\n async loadContainerMetadataState(execSql, containerId) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const container = await loadContainerById(execSql, containerId);\n if (!container) {\n return null;\n }\n return {\n container,\n record: await selectContainerMetadataRecord(execSql, containerId),\n };\n },\n async saveContainer(execSql, container, record, options) {\n return saveStoredContainer(execSql, container, record, options);\n },\n async saveContainerWithPendingUpdate(execSql, container, record, options) {\n return saveStoredContainer(execSql, container, record, options);\n },\n async saveContainerAndDeletePendingUpdates(\n execSql,\n container,\n record,\n pendingUpdateIds,\n ) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n const localUpdatedAt = new Date().toISOString();\n return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const scope = getContainerMetadataScope(container.id);\n await settleQueuedDocumentUpdatesInTransaction(\n tx,\n scope,\n pendingUpdateIds,\n );\n const hasRemainingPendingUpdates = await hasQueuedDocumentUpdates(\n tx,\n scope,\n );\n const containerToSave = hasRemainingPendingUpdates\n ? container\n : {\n ...container,\n serverUpdatedAt: getLatestTimestamp(\n container.serverUpdatedAt,\n localUpdatedAt,\n ),\n };\n\n return saveContainerContentsContainerRows({\n container: containerToSave,\n record,\n tx,\n localUpdatedAt,\n });\n },\n );\n });\n },\n};\n"]}
1
+ {"version":3,"file":"containerContentsPersistence.js","sourceRoot":"","sources":["containerContentsPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { eq } from \"drizzle-orm\";\nimport {\n containerHydrationTombstones,\n containers,\n documentContainerProjectionTables,\n localIntentTables,\n} from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n ensureSqlTables,\n runOncePerConnection,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport {\n enqueueDocumentPendingUpdate,\n ensureDocumentProjectionTables,\n ensureDocumentTables,\n} from \"../documents/records\";\nimport {\n hasQueuedDocumentUpdates,\n settleQueuedDocumentUpdatesInTransaction,\n} from \"../documents/records/documentPendingUpdatePersistence\";\nimport { getLatestTimestamp } from \"../latestTimestamp\";\nimport {\n ensureContainerTables,\n loadContainerById,\n loadContainers as loadContainerRecords,\n} from \"./containerPersistence\";\nimport { sqlContainerSyncWatermarkPersistence } from \"./containerSyncWatermarkPersistence\";\nimport {\n claimDormantMetadataSweepAttempt,\n completeDormantMetadataSweepRequest,\n listDormantMetadataSweepRequests,\n} from \"./dormantMetadataSweep\";\n\nexport type {\n ContainerContentsPersistence,\n ContainerCreateIntentRecord,\n ContainerHydrationTombstone,\n ContainerMetadataRecord,\n ContainerMoveIntentRecord,\n LocalRootDescendantReparentInput,\n StoredContainerState,\n} from \"./containerContentsPersistenceTypes\";\nexport { CONTAINER_METADATA_APP_KIND } from \"./dormantContainerMetadata\";\n\nimport type {\n ContainerContentsPersistence,\n ContainerMetadataRecord,\n SaveContainerOptions,\n SaveContainerWithPendingUpdateOptions,\n} from \"./containerContentsPersistenceTypes\";\nimport { deleteStoredContainers } from \"./containerDeletionPersistence\";\nimport {\n commitStoredHydratedContainer,\n loadStoredHeldContainerBinding,\n} from \"./containerHydrationPersistence\";\nimport { containerIntentPersistence } from \"./containerIntentPersistence\";\nimport {\n commitStoredMetadataMutation,\n settleStoredMetadataPendingUpdates,\n} from \"./containerMetadataMutationPersistence\";\nimport { containerMetadataPullContinuationPersistence } from \"./containerMetadataPullContinuationPersistence\";\nimport {\n getContainerMetadataScope,\n saveContainerContentsContainerRows,\n selectContainerMetadataRecord,\n} from \"./containerMetadataRows\";\nimport { abandonStoredMoveIntentRevision } from \"./containerMoveAbandonPersistence\";\nimport { containerPendingUpdatePersistence } from \"./containerPendingUpdatePersistence\";\nimport { rebindStoredHeldContainer } from \"./containerRebindPersistence\";\nimport { containerReconcilePersistence } from \"./containerReconcilePersistence\";\nimport { isSupersededContainerBinding } from \"./supersededContainerBindings\";\n\nasync function saveStoredContainer(\n execSql: Parameters<ContainerContentsPersistence[\"saveContainer\"]>[0],\n container: Parameters<ContainerContentsPersistence[\"saveContainer\"]>[1],\n record: ContainerMetadataRecord | null,\n options?:\n | SaveContainerOptions\n | SaveContainerWithPendingUpdateOptions\n | undefined,\n) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n const localUpdatedAt =\n options?.localUpdatedAt ?? options?.updatedAt ?? new Date().toISOString();\n const runtime = getClientSQLitePersistenceRuntime(lockedExecSql);\n const save = async (tx: ClientSQLiteTransactionScope) => {\n const saved = await saveContainerContentsContainerRows({\n container,\n createIntent: options?.createIntent,\n moveIntent: options?.moveIntent,\n record,\n serverTimestamps: options?.serverTimestamps,\n tx,\n localUpdatedAt,\n });\n if (options && \"pendingUpdate\" in options) {\n await enqueueDocumentPendingUpdate(\n lockedExecSql,\n getContainerMetadataScope(container.id),\n options.pendingUpdate,\n );\n }\n return saved;\n };\n if (!options?.stillCurrent) {\n return runtime.transaction(save);\n }\n const outcome = await runtime.guardedTransaction(\n save,\n options.stillCurrent,\n { behavior: \"immediate\" },\n );\n // Guarded callers recheck the same monotonic generation immediately after\n // this await. Returning the untouched candidate keeps the long-standing\n // non-null save contract while the transaction itself remains rolled back.\n return outcome.result ?? container;\n });\n}\n\nexport const sqlContainerContentsPersistence: ContainerContentsPersistence = {\n ...containerIntentPersistence,\n ...containerMetadataPullContinuationPersistence,\n commitHydratedContainer: commitStoredHydratedContainer,\n commitMetadataMutation: commitStoredMetadataMutation,\n settleAcceptedMetadataPendingUpdates: settleStoredMetadataPendingUpdates,\n listDormantMetadataSweepRequests,\n ...containerPendingUpdatePersistence,\n ...containerReconcilePersistence,\n claimDormantMetadataSweepAttempt,\n completeDormantMetadataSweepRequest,\n async containerExists(execSql, containerId) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({ id: containers.id })\n .from(containers)\n .where(eq(containers.id, containerId))\n .limit(1);\n return rows.length > 0;\n },\n async deleteContainer(execSql, containerId, options) {\n await sqlContainerContentsPersistence.deleteContainers(execSql, [\n {\n containerId,\n reason: options?.reason ?? \"deleted\",\n updatedAt: options?.updatedAt ?? new Date().toISOString(),\n },\n ]);\n },\n async deleteContainers(execSql, removals, options) {\n return deleteStoredContainers(execSql, removals, options);\n },\n async ensureSchema(execSql) {\n // Once ensured on this connection, skip the outer mutation lock entirely:\n // ensureSchema runs on every query path, and re-acquiring the lock just to\n // no-op would queue reads behind unrelated writes.\n await runOncePerConnection(execSql, \"ensure:container-contents\", () =>\n runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureContainerTables(lockedExecSql);\n await ensureDocumentTables(lockedExecSql);\n await ensureSqlTables(lockedExecSql, localIntentTables);\n await ensureSqlTables(lockedExecSql, documentContainerProjectionTables);\n await ensureDocumentProjectionTables(lockedExecSql);\n await sqlContainerSyncWatermarkPersistence.ensureSchema(lockedExecSql);\n }),\n );\n },\n async loadHeldContainerBinding(execSql, containerId) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n return loadStoredHeldContainerBinding(execSql, containerId);\n },\n async isSupersededContainerBinding(execSql, input) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n return isSupersededContainerBinding(execSql, input);\n },\n async abandonMoveIntentRevision(execSql, input) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n return abandonStoredMoveIntentRevision(execSql, input);\n },\n async rebindHeldContainer(execSql, input) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n return rebindStoredHeldContainer(execSql, input);\n },\n async loadContainerMetadataRecord(execSql, containerId) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n return selectContainerMetadataRecord(execSql, containerId);\n },\n async loadContainerHydrationTombstones(execSql) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n containerId: containerHydrationTombstones.containerId,\n generation: containerHydrationTombstones.generation,\n reason: containerHydrationTombstones.reason,\n updatedAt: containerHydrationTombstones.updatedAt,\n })\n .from(containerHydrationTombstones)\n .where(eq(containerHydrationTombstones.cleared, false));\n return rows.flatMap((row) =>\n row.reason === \"access_revoked\" || row.reason === \"deleted\"\n ? [{ ...row, reason: row.reason }]\n : [],\n );\n },\n async loadContainers(execSql) {\n const containers = await loadContainerRecords(execSql);\n const storedContainers = await Promise.all(\n containers.map(async (container) => ({\n container,\n record: await selectContainerMetadataRecord(execSql, container.id),\n })),\n );\n\n return storedContainers;\n },\n async loadContainerMetadataState(execSql, containerId) {\n await sqlContainerContentsPersistence.ensureSchema(execSql);\n const container = await loadContainerById(execSql, containerId);\n if (!container) {\n return null;\n }\n return {\n container,\n record: await selectContainerMetadataRecord(execSql, containerId),\n };\n },\n async saveContainer(execSql, container, record, options) {\n return saveStoredContainer(execSql, container, record, options);\n },\n async saveContainerWithPendingUpdate(execSql, container, record, options) {\n return saveStoredContainer(execSql, container, record, options);\n },\n async saveContainerAndDeletePendingUpdates(\n execSql,\n container,\n record,\n pendingUpdateIds,\n ) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n const localUpdatedAt = new Date().toISOString();\n return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const scope = getContainerMetadataScope(container.id);\n await settleQueuedDocumentUpdatesInTransaction(\n tx,\n scope,\n pendingUpdateIds,\n );\n const hasRemainingPendingUpdates = await hasQueuedDocumentUpdates(\n tx,\n scope,\n );\n const containerToSave = hasRemainingPendingUpdates\n ? container\n : {\n ...container,\n serverUpdatedAt: getLatestTimestamp(\n container.serverUpdatedAt,\n localUpdatedAt,\n ),\n };\n\n return saveContainerContentsContainerRows({\n container: containerToSave,\n record,\n tx,\n localUpdatedAt,\n });\n },\n );\n });\n },\n};\n"]}
@@ -1,7 +1,7 @@
1
1
  import type { AccessManifestCheckpoint } from "../../../internal/crypto/index.js";
2
2
  import type { DocumentSyncPullContinuation } from "../../documents/shared/sync/pullContinuation.js";
3
- import type { DocumentRecord, PendingUpdateFields, PendingUpdateRecord } from "../../sqlite/documentPersistence/index.js";
4
3
  import type { ExecSql } from "../../sqlite/sqlSchema.js";
4
+ import type { DocumentRecord, PendingUpdateFields, PendingUpdateRecord } from "../documents/records/index.js";
5
5
  import type { HeldContainerBinding } from "./containerBinding.js";
6
6
  import type { ContainerRecord } from "./containerPersistence.js";
7
7
  import type { DormantMetadataSweepPersistence } from "./dormantMetadataSweep.js";
@@ -1 +1 @@
1
- {"version":3,"file":"containerContentsPersistenceTypes.js","sourceRoot":"","sources":["containerContentsPersistenceTypes.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,MAAM,4BAA4B,GAAG,kBAAkB,CAAC;AAC/D,MAAM,CAAC,MAAM,0BAA0B,GAAG,gBAAgB,CAAC","sourcesContent":["import type { AccessManifestCheckpoint } from \"@tearleads/crypto\";\nimport type { DocumentSyncPullContinuation } from \"../../documents/shared/sync/pullContinuation\";\nimport type {\n DocumentRecord,\n PendingUpdateFields,\n PendingUpdateRecord,\n} from \"../../sqlite/documentPersistence\";\nimport type { ExecSql } from \"../../sqlite/sqlSchema\";\nimport type { HeldContainerBinding } from \"./containerBinding\";\nimport type { ContainerRecord } from \"./containerPersistence\";\nimport type { DormantMetadataSweepPersistence } from \"./dormantMetadataSweep\";\n\nexport const CONTAINER_CREATE_INTENT_TYPE = \"container.create\";\nexport const CONTAINER_MOVE_INTENT_TYPE = \"container.move\";\n\nexport type ContainerCreateIntentSyncStatus = \"pending\" | \"synced\";\nexport type ContainerMoveIntentSyncStatus = \"pending\" | \"blocked\";\n\nexport interface ContainerCreateIntentRecord {\n id: string;\n containerId: string;\n parentContainerId: string;\n intentType: typeof CONTAINER_CREATE_INTENT_TYPE;\n syncStatus: ContainerCreateIntentSyncStatus;\n remoteContainerId: string | null;\n remoteMetadataDocumentId: string | null;\n remoteMetadataAccessStateHash: string | null;\n lastError: string | null;\n lastAttemptedAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface ContainerMoveIntentRecord {\n id: string;\n containerId: string;\n parentContainerId: string;\n previousParentContainerId: string | null;\n intentType: typeof CONTAINER_MOVE_INTENT_TYPE;\n syncStatus: ContainerMoveIntentSyncStatus;\n lastError: string | null;\n lastAttemptedAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface ContainerCreateIntentInput {\n id?: string;\n parentContainerId: string;\n}\n\nexport interface ContainerMoveIntentInput {\n id?: string;\n parentContainerId: string;\n previousParentContainerId?: string | null | undefined;\n}\n\nexport interface ContainerCreateIntentErrorInput {\n containerId: string;\n expectedIntentId: string;\n expectedUpdatedAt: string;\n message: string;\n stillCurrent?: (() => boolean) | undefined;\n}\n\nexport type ContainerCreateIntentErrorInputRecorder = (\n execSql: ExecSql,\n input: ContainerCreateIntentErrorInput,\n) => Promise<void>;\n\nexport interface ContainerMoveIntentRevisionInput {\n containerId: string;\n expectedIntentId: string;\n expectedUpdatedAt: string;\n stillCurrent: () => boolean;\n}\n\nexport interface ContainerCreateIntentRevisionInput\n extends ContainerMoveIntentRevisionInput {\n remoteContainerId: string;\n remoteMetadataAccessStateHash: string;\n remoteMetadataDocumentId: string;\n /** The remote parent now; a move settlement queues cites it as previous. */\n supersededMovePreviousParentId?: string | undefined;\n /**\n * The parent the create committed under. A desired parent equal to it, or\n * to the current one, owes no move: the user never moved the folder away,\n * and a later remote move by another writer stands.\n */\n createdParentContainerId?: string | undefined;\n /** The parent the settled intent wants. */\n desiredParentContainerId?: string | undefined;\n}\n\nexport interface LocalRootDescendantReparentInput {\n containerId: string;\n parentContainerId: string | null;\n updateCreateIntent?: boolean | undefined;\n}\n\n/**\n * A container's metadata document record plus its durable content: the full\n * Loro updates export of the metadata document, stored in the shared\n * history-checkpoint table under the container-metadata app kind. Metadata\n * documents are tiny registries, so the whole update log is the checkpoint.\n */\nexport interface ContainerMetadataRecord extends DocumentRecord {\n metadataUpdates: string;\n}\n\nexport interface StoredContainerState {\n container: ContainerRecord;\n record: ContainerMetadataRecord | null;\n}\n\nexport interface ContainerRemoval {\n containerId: string;\n reason: \"access_revoked\" | \"deleted\";\n updatedAt: string;\n}\n\nexport interface ContainerHydrationTombstone extends ContainerRemoval {\n generation: number;\n}\n\nexport interface ContainerDeletionGuard {\n containerId: string;\n expectedContainer: ContainerRecord | null;\n}\n\nexport interface SaveContainerOptions {\n createIntent?: ContainerCreateIntentInput;\n stillCurrent?: (() => boolean) | undefined;\n localUpdatedAt?: string;\n moveIntent?: ContainerMoveIntentInput | undefined;\n serverTimestamps?:\n | {\n createdAt?: string | null;\n updatedAt?: string | null;\n }\n | undefined;\n updatedAt?: string;\n}\n\nexport interface SaveContainerWithPendingUpdateOptions\n extends SaveContainerOptions {\n pendingUpdate: PendingUpdateFields;\n}\n\nexport interface ContainerContentsPersistence\n extends DormantMetadataSweepPersistence {\n containerExists: (execSql: ExecSql, containerId: string) => Promise<boolean>;\n /**\n * Create an absent remotely hydrated container only when no newer durable\n * tombstone or concurrent container/dormant-metadata mutation won first.\n */\n commitHydratedContainer: (\n execSql: ExecSql,\n input: {\n container: ContainerRecord;\n expectedDormantRecord: ContainerMetadataRecord | null;\n /** Tombstone observed before the remote request began, or null. */\n expectedHydrationTombstone?:\n | ContainerHydrationTombstone\n | null\n | undefined;\n /** Current placement verified and pinned before recovery insertion. */\n expectedPlacementCheckpoint?: AccessManifestCheckpoint | undefined;\n record: ContainerMetadataRecord;\n remoteUpdatedAt: string;\n saveOptions: {\n localUpdatedAt?: string;\n serverTimestamps?:\n | {\n createdAt?: string | null;\n updatedAt?: string | null;\n }\n | undefined;\n };\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<\n { committed: true; container: ContainerRecord } | { committed: false }\n >;\n /** Capture anti-resurrection fences immediately before a remote fetch. */\n loadContainerHydrationTombstones: (\n execSql: ExecSql,\n ) => Promise<ReadonlyArray<ContainerHydrationTombstone>>;\n deleteContainer: (\n execSql: ExecSql,\n containerId: string,\n options?: {\n reason?: ContainerRemoval[\"reason\"];\n updatedAt?: string;\n },\n ) => Promise<void>;\n deleteContainers: (\n execSql: ExecSql,\n removals: ReadonlyArray<ContainerRemoval>,\n options?: {\n /** Must run inside the deletion transaction before any row is removed. */\n beforeDeleteInTransaction?:\n | ((transactionExecSql: ExecSql) => Promise<boolean>)\n | undefined;\n /** Unsigned discovery removes listings but preserves metadata and local intent. */\n discoveryOnly?: boolean;\n /**\n * Request-time states rechecked inside the deletion transaction. An\n * absent or changed row means another pane won and aborts the cascade.\n */\n expectedContainers?: ReadonlyArray<ContainerDeletionGuard>;\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<ReadonlyArray<string>>;\n deletePendingUpdates: (\n execSql: ExecSql,\n containerId: string,\n ) => Promise<void>;\n deletePendingUpdate: (execSql: ExecSql, id: string) => Promise<void>;\n ensureSchema: (execSql: ExecSql) => Promise<void>;\n enqueuePendingUpdate: (\n execSql: ExecSql,\n input: PendingUpdateFields & { containerId: string },\n ) => Promise<string>;\n listPendingCreateIntents: (\n execSql: ExecSql,\n ) => Promise<ContainerCreateIntentRecord[]>;\n // Every move intent that has not yet synced, regardless of syncStatus —\n // a blocked move (destination parent not synced yet) is still unsynced, and\n // synced moves are deleted (see markMoveIntentRevisionSynced), so every surviving\n // row qualifies. Blocked intents replay too: \"blocked\" names the reason the\n // last attempt could not proceed, not a terminal verdict.\n listUnsyncedMoveIntents: (\n execSql: ExecSql,\n ) => Promise<ContainerMoveIntentRecord[]>;\n listContainerIdsWithPendingUpdates: (\n execSql: ExecSql,\n containerIds: ReadonlyArray<string>,\n ) => Promise<string[]>;\n listContainerIdsWithPullContinuations: (\n execSql: ExecSql,\n containerIds: ReadonlyArray<string>,\n ) => Promise<string[]>;\n listPendingUpdates: (\n execSql: ExecSql,\n containerId: string,\n ) => Promise<PendingUpdateRecord[]>;\n rekeyPendingUpdate: (execSql: ExecSql, id: string) => Promise<string | null>;\n /** Revision-CAS error recorder used by asynchronous create replay. */\n recordCreateIntentRevisionError: ContainerCreateIntentErrorInputRecorder;\n recordMoveIntentError: (\n execSql: ExecSql,\n input: {\n blocked?: boolean | undefined;\n containerId: string;\n expectedIntentId?: string | undefined;\n expectedUpdatedAt?: string | undefined;\n message: string;\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<void>;\n reassignContainerDocuments: (\n execSql: ExecSql,\n input: {\n fromContainerId: string;\n stillCurrent?: (() => boolean) | undefined;\n toContainerId: string;\n updatedAt?: string | undefined;\n },\n ) => Promise<void>;\n reconcileLocalRootContainer: (\n execSql: ExecSql,\n input: {\n descendantReparents: ReadonlyArray<LocalRootDescendantReparentInput>;\n localRootContainerId: string;\n remoteOrganizationId: string;\n remoteRootContainerId: string;\n stillCurrent?: (() => boolean) | undefined;\n updatedAt?: string | undefined;\n },\n ) => Promise<void>;\n reconcileLocalSystemContainer: (\n execSql: ExecSql,\n input: {\n localContainerId: string;\n remoteContainerId: string;\n remoteOrganizationId: string;\n stillCurrent?: (() => boolean) | undefined;\n updatedAt?: string | undefined;\n },\n ) => Promise<void>;\n loadContainers: (\n execSql: ExecSql,\n ) => Promise<ReadonlyArray<StoredContainerState>>;\n loadContainerMetadataState: (\n execSql: ExecSql,\n containerId: string,\n ) => Promise<StoredContainerState | null>;\n /**\n * The organization and metadata target this device holds for a container,\n * from its live row or retained dormant metadata; null when never held.\n */\n loadHeldContainerBinding: (\n execSql: ExecSql,\n containerId: string,\n ) => Promise<HeldContainerBinding | null>;\n /** Whether a held folder was re-homed away from this organization. */\n isSupersededContainerBinding: (\n execSql: ExecSql,\n input: { containerId: string; organizationId: string },\n ) => Promise<boolean>;\n /**\n * Move a held folder to the binding its own user re-created it under,\n * resetting the metadata record's remote stream; false if the held binding\n * changed since it was read.\n */\n rebindHeldContainer: (\n execSql: ExecSql,\n input: {\n containerId: string;\n expected: HeldContainerBinding;\n next: { metadataDocumentId: string; organizationId: string };\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<boolean>;\n /**\n * Load a container-metadata record by container id alone, without\n * requiring a containers row — the dormant shape row 4's access_revoked\n * branch leaves behind, re-attached on rehydration.\n */\n loadContainerMetadataRecord: (\n execSql: ExecSql,\n containerId: string,\n ) => Promise<ContainerMetadataRecord | null>;\n /**\n * Atomically replace the exact rejected metadata pull continuation with its\n * durable recovery marker and return the authoritative current record.\n */\n invalidateMetadataPullContinuation: (\n execSql: ExecSql,\n input: {\n accessEpoch: number;\n accessStateHash: string | null;\n continuation: DocumentSyncPullContinuation;\n containerId: string;\n contentKeyBundle: string | null;\n documentId: string;\n documentKekTargets: string | null;\n documentManifestBundle: string | null;\n lastCommitLsn: string | null;\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<ContainerMetadataRecord | null>;\n /**\n * Conditionally commit metadata content, its outgoing row, accepted queue\n * settlement, container fields, and the canonical record in one transaction.\n */\n commitMetadataMutation: (\n execSql: ExecSql,\n input: {\n acceptedPendingUpdateIds: readonly string[];\n clearSyncFailure?: boolean | undefined;\n container: ContainerRecord;\n expectedContainer: ContainerRecord;\n expectedRecord: ContainerMetadataRecord;\n createIntentSettlement?:\n | {\n containerId: string;\n expectedIntentId: string;\n expectedUpdatedAt: string;\n remoteContainerId: string;\n remoteMetadataAccessStateHash: string;\n remoteMetadataDocumentId: string;\n /**\n * When present, an overtaking create revision is adopted as the\n * desired local parent and converted to a move from this remotely\n * committed parent instead of rejecting the remote identity.\n */\n supersededMovePreviousParentId?: string | undefined;\n }\n | undefined;\n moveIntentSettlement?:\n | {\n containerId: string;\n expectedIntentId: string;\n expectedUpdatedAt: string;\n }\n | undefined;\n pendingUpdate?: PendingUpdateFields | undefined;\n preserveDurableStructureWhenPending?: boolean | undefined;\n record: ContainerMetadataRecord;\n saveOptions?:\n | {\n createIntent?: ContainerCreateIntentInput;\n localUpdatedAt?: string;\n moveIntent?: ContainerMoveIntentInput | undefined;\n serverTimestamps?:\n | {\n createdAt?: string | null;\n updatedAt?: string | null;\n }\n | undefined;\n updatedAt?: string;\n }\n | undefined;\n settleAcceptedPendingOnConflict: boolean;\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<\n | {\n committed: true;\n container: ContainerRecord;\n createIntentSettled?: true | undefined;\n moveIntentSettled?: true | undefined;\n }\n | {\n committed: false;\n currentState: StoredContainerState | null;\n staleServerState?: true;\n }\n >;\n settleAcceptedMetadataPendingUpdates: (\n execSql: ExecSql,\n input: {\n containerId: string;\n expectedRecord: ContainerMetadataRecord;\n pendingUpdateIds: readonly string[];\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<StoredContainerState | null>;\n saveContainer: (\n execSql: ExecSql,\n container: ContainerRecord,\n record: ContainerMetadataRecord | null,\n options?: SaveContainerOptions,\n ) => Promise<ContainerRecord>;\n /**\n * Atomically saves a container and enqueues its first metadata update.\n * Host adapters must implement the compound write in their own transaction.\n */\n saveContainerWithPendingUpdate: (\n execSql: ExecSql,\n container: ContainerRecord,\n record: ContainerMetadataRecord,\n options: SaveContainerWithPendingUpdateOptions,\n ) => Promise<ContainerRecord>;\n saveContainerAndDeletePendingUpdates: (\n execSql: ExecSql,\n container: ContainerRecord,\n record: ContainerMetadataRecord,\n pendingUpdateIds: readonly string[],\n ) => Promise<ContainerRecord>;\n /**\n * Revision-CAS settlement used by asynchronous create replay. Invalid\n * adapters are rejected before the remote mutation begins.\n */\n markCreateIntentRevisionSynced: (\n execSql: ExecSql,\n input: ContainerCreateIntentRevisionInput,\n ) => Promise<boolean>;\n /**\n * Explicit revision-CAS settlement. Move replay instead settles inside\n * commitMetadataMutation and requires its atomic acknowledgment.\n */\n markMoveIntentRevisionSynced: (\n execSql: ExecSql,\n input: ContainerMoveIntentRevisionInput,\n ) => Promise<boolean>;\n /**\n * Drop a move revision the server refuses for good and restore the\n * folder's previous parent in the same transaction; false if superseded.\n */\n abandonMoveIntentRevision: (\n execSql: ExecSql,\n input: ContainerMoveIntentRevisionInput & {\n previousParentContainerId: string;\n },\n ) => Promise<boolean>;\n}\n"]}
1
+ {"version":3,"file":"containerContentsPersistenceTypes.js","sourceRoot":"","sources":["containerContentsPersistenceTypes.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,MAAM,4BAA4B,GAAG,kBAAkB,CAAC;AAC/D,MAAM,CAAC,MAAM,0BAA0B,GAAG,gBAAgB,CAAC","sourcesContent":["import type { AccessManifestCheckpoint } from \"@tearleads/crypto\";\nimport type { DocumentSyncPullContinuation } from \"../../documents/shared/sync/pullContinuation\";\nimport type { ExecSql } from \"../../sqlite/sqlSchema\";\nimport type {\n DocumentRecord,\n PendingUpdateFields,\n PendingUpdateRecord,\n} from \"../documents/records\";\nimport type { HeldContainerBinding } from \"./containerBinding\";\nimport type { ContainerRecord } from \"./containerPersistence\";\nimport type { DormantMetadataSweepPersistence } from \"./dormantMetadataSweep\";\n\nexport const CONTAINER_CREATE_INTENT_TYPE = \"container.create\";\nexport const CONTAINER_MOVE_INTENT_TYPE = \"container.move\";\n\nexport type ContainerCreateIntentSyncStatus = \"pending\" | \"synced\";\nexport type ContainerMoveIntentSyncStatus = \"pending\" | \"blocked\";\n\nexport interface ContainerCreateIntentRecord {\n id: string;\n containerId: string;\n parentContainerId: string;\n intentType: typeof CONTAINER_CREATE_INTENT_TYPE;\n syncStatus: ContainerCreateIntentSyncStatus;\n remoteContainerId: string | null;\n remoteMetadataDocumentId: string | null;\n remoteMetadataAccessStateHash: string | null;\n lastError: string | null;\n lastAttemptedAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface ContainerMoveIntentRecord {\n id: string;\n containerId: string;\n parentContainerId: string;\n previousParentContainerId: string | null;\n intentType: typeof CONTAINER_MOVE_INTENT_TYPE;\n syncStatus: ContainerMoveIntentSyncStatus;\n lastError: string | null;\n lastAttemptedAt: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface ContainerCreateIntentInput {\n id?: string;\n parentContainerId: string;\n}\n\nexport interface ContainerMoveIntentInput {\n id?: string;\n parentContainerId: string;\n previousParentContainerId?: string | null | undefined;\n}\n\nexport interface ContainerCreateIntentErrorInput {\n containerId: string;\n expectedIntentId: string;\n expectedUpdatedAt: string;\n message: string;\n stillCurrent?: (() => boolean) | undefined;\n}\n\nexport type ContainerCreateIntentErrorInputRecorder = (\n execSql: ExecSql,\n input: ContainerCreateIntentErrorInput,\n) => Promise<void>;\n\nexport interface ContainerMoveIntentRevisionInput {\n containerId: string;\n expectedIntentId: string;\n expectedUpdatedAt: string;\n stillCurrent: () => boolean;\n}\n\nexport interface ContainerCreateIntentRevisionInput\n extends ContainerMoveIntentRevisionInput {\n remoteContainerId: string;\n remoteMetadataAccessStateHash: string;\n remoteMetadataDocumentId: string;\n /** The remote parent now; a move settlement queues cites it as previous. */\n supersededMovePreviousParentId?: string | undefined;\n /**\n * The parent the create committed under. A desired parent equal to it, or\n * to the current one, owes no move: the user never moved the folder away,\n * and a later remote move by another writer stands.\n */\n createdParentContainerId?: string | undefined;\n /** The parent the settled intent wants. */\n desiredParentContainerId?: string | undefined;\n}\n\nexport interface LocalRootDescendantReparentInput {\n containerId: string;\n parentContainerId: string | null;\n updateCreateIntent?: boolean | undefined;\n}\n\n/**\n * A container's metadata document record plus its durable content: the full\n * Loro updates export of the metadata document, stored in the shared\n * history-checkpoint table under the container-metadata app kind. Metadata\n * documents are tiny registries, so the whole update log is the checkpoint.\n */\nexport interface ContainerMetadataRecord extends DocumentRecord {\n metadataUpdates: string;\n}\n\nexport interface StoredContainerState {\n container: ContainerRecord;\n record: ContainerMetadataRecord | null;\n}\n\nexport interface ContainerRemoval {\n containerId: string;\n reason: \"access_revoked\" | \"deleted\";\n updatedAt: string;\n}\n\nexport interface ContainerHydrationTombstone extends ContainerRemoval {\n generation: number;\n}\n\nexport interface ContainerDeletionGuard {\n containerId: string;\n expectedContainer: ContainerRecord | null;\n}\n\nexport interface SaveContainerOptions {\n createIntent?: ContainerCreateIntentInput;\n stillCurrent?: (() => boolean) | undefined;\n localUpdatedAt?: string;\n moveIntent?: ContainerMoveIntentInput | undefined;\n serverTimestamps?:\n | {\n createdAt?: string | null;\n updatedAt?: string | null;\n }\n | undefined;\n updatedAt?: string;\n}\n\nexport interface SaveContainerWithPendingUpdateOptions\n extends SaveContainerOptions {\n pendingUpdate: PendingUpdateFields;\n}\n\nexport interface ContainerContentsPersistence\n extends DormantMetadataSweepPersistence {\n containerExists: (execSql: ExecSql, containerId: string) => Promise<boolean>;\n /**\n * Create an absent remotely hydrated container only when no newer durable\n * tombstone or concurrent container/dormant-metadata mutation won first.\n */\n commitHydratedContainer: (\n execSql: ExecSql,\n input: {\n container: ContainerRecord;\n expectedDormantRecord: ContainerMetadataRecord | null;\n /** Tombstone observed before the remote request began, or null. */\n expectedHydrationTombstone?:\n | ContainerHydrationTombstone\n | null\n | undefined;\n /** Current placement verified and pinned before recovery insertion. */\n expectedPlacementCheckpoint?: AccessManifestCheckpoint | undefined;\n record: ContainerMetadataRecord;\n remoteUpdatedAt: string;\n saveOptions: {\n localUpdatedAt?: string;\n serverTimestamps?:\n | {\n createdAt?: string | null;\n updatedAt?: string | null;\n }\n | undefined;\n };\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<\n { committed: true; container: ContainerRecord } | { committed: false }\n >;\n /** Capture anti-resurrection fences immediately before a remote fetch. */\n loadContainerHydrationTombstones: (\n execSql: ExecSql,\n ) => Promise<ReadonlyArray<ContainerHydrationTombstone>>;\n deleteContainer: (\n execSql: ExecSql,\n containerId: string,\n options?: {\n reason?: ContainerRemoval[\"reason\"];\n updatedAt?: string;\n },\n ) => Promise<void>;\n deleteContainers: (\n execSql: ExecSql,\n removals: ReadonlyArray<ContainerRemoval>,\n options?: {\n /** Must run inside the deletion transaction before any row is removed. */\n beforeDeleteInTransaction?:\n | ((transactionExecSql: ExecSql) => Promise<boolean>)\n | undefined;\n /** Unsigned discovery removes listings but preserves metadata and local intent. */\n discoveryOnly?: boolean;\n /**\n * Request-time states rechecked inside the deletion transaction. An\n * absent or changed row means another pane won and aborts the cascade.\n */\n expectedContainers?: ReadonlyArray<ContainerDeletionGuard>;\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<ReadonlyArray<string>>;\n deletePendingUpdates: (\n execSql: ExecSql,\n containerId: string,\n ) => Promise<void>;\n deletePendingUpdate: (execSql: ExecSql, id: string) => Promise<void>;\n ensureSchema: (execSql: ExecSql) => Promise<void>;\n enqueuePendingUpdate: (\n execSql: ExecSql,\n input: PendingUpdateFields & { containerId: string },\n ) => Promise<string>;\n listPendingCreateIntents: (\n execSql: ExecSql,\n ) => Promise<ContainerCreateIntentRecord[]>;\n // Every move intent that has not yet synced, regardless of syncStatus —\n // a blocked move (destination parent not synced yet) is still unsynced, and\n // synced moves are deleted (see markMoveIntentRevisionSynced), so every surviving\n // row qualifies. Blocked intents replay too: \"blocked\" names the reason the\n // last attempt could not proceed, not a terminal verdict.\n listUnsyncedMoveIntents: (\n execSql: ExecSql,\n ) => Promise<ContainerMoveIntentRecord[]>;\n listContainerIdsWithPendingUpdates: (\n execSql: ExecSql,\n containerIds: ReadonlyArray<string>,\n ) => Promise<string[]>;\n listContainerIdsWithPullContinuations: (\n execSql: ExecSql,\n containerIds: ReadonlyArray<string>,\n ) => Promise<string[]>;\n listPendingUpdates: (\n execSql: ExecSql,\n containerId: string,\n ) => Promise<PendingUpdateRecord[]>;\n rekeyPendingUpdate: (execSql: ExecSql, id: string) => Promise<string | null>;\n /** Revision-CAS error recorder used by asynchronous create replay. */\n recordCreateIntentRevisionError: ContainerCreateIntentErrorInputRecorder;\n recordMoveIntentError: (\n execSql: ExecSql,\n input: {\n blocked?: boolean | undefined;\n containerId: string;\n expectedIntentId?: string | undefined;\n expectedUpdatedAt?: string | undefined;\n message: string;\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<void>;\n reassignContainerDocuments: (\n execSql: ExecSql,\n input: {\n fromContainerId: string;\n stillCurrent?: (() => boolean) | undefined;\n toContainerId: string;\n updatedAt?: string | undefined;\n },\n ) => Promise<void>;\n reconcileLocalRootContainer: (\n execSql: ExecSql,\n input: {\n descendantReparents: ReadonlyArray<LocalRootDescendantReparentInput>;\n localRootContainerId: string;\n remoteOrganizationId: string;\n remoteRootContainerId: string;\n stillCurrent?: (() => boolean) | undefined;\n updatedAt?: string | undefined;\n },\n ) => Promise<void>;\n reconcileLocalSystemContainer: (\n execSql: ExecSql,\n input: {\n localContainerId: string;\n remoteContainerId: string;\n remoteOrganizationId: string;\n stillCurrent?: (() => boolean) | undefined;\n updatedAt?: string | undefined;\n },\n ) => Promise<void>;\n loadContainers: (\n execSql: ExecSql,\n ) => Promise<ReadonlyArray<StoredContainerState>>;\n loadContainerMetadataState: (\n execSql: ExecSql,\n containerId: string,\n ) => Promise<StoredContainerState | null>;\n /**\n * The organization and metadata target this device holds for a container,\n * from its live row or retained dormant metadata; null when never held.\n */\n loadHeldContainerBinding: (\n execSql: ExecSql,\n containerId: string,\n ) => Promise<HeldContainerBinding | null>;\n /** Whether a held folder was re-homed away from this organization. */\n isSupersededContainerBinding: (\n execSql: ExecSql,\n input: { containerId: string; organizationId: string },\n ) => Promise<boolean>;\n /**\n * Move a held folder to the binding its own user re-created it under,\n * resetting the metadata record's remote stream; false if the held binding\n * changed since it was read.\n */\n rebindHeldContainer: (\n execSql: ExecSql,\n input: {\n containerId: string;\n expected: HeldContainerBinding;\n next: { metadataDocumentId: string; organizationId: string };\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<boolean>;\n /**\n * Load a container-metadata record by container id alone, without\n * requiring a containers row — the dormant shape row 4's access_revoked\n * branch leaves behind, re-attached on rehydration.\n */\n loadContainerMetadataRecord: (\n execSql: ExecSql,\n containerId: string,\n ) => Promise<ContainerMetadataRecord | null>;\n /**\n * Atomically replace the exact rejected metadata pull continuation with its\n * durable recovery marker and return the authoritative current record.\n */\n invalidateMetadataPullContinuation: (\n execSql: ExecSql,\n input: {\n accessEpoch: number;\n accessStateHash: string | null;\n continuation: DocumentSyncPullContinuation;\n containerId: string;\n contentKeyBundle: string | null;\n documentId: string;\n documentKekTargets: string | null;\n documentManifestBundle: string | null;\n lastCommitLsn: string | null;\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<ContainerMetadataRecord | null>;\n /**\n * Conditionally commit metadata content, its outgoing row, accepted queue\n * settlement, container fields, and the canonical record in one transaction.\n */\n commitMetadataMutation: (\n execSql: ExecSql,\n input: {\n acceptedPendingUpdateIds: readonly string[];\n clearSyncFailure?: boolean | undefined;\n container: ContainerRecord;\n expectedContainer: ContainerRecord;\n expectedRecord: ContainerMetadataRecord;\n createIntentSettlement?:\n | {\n containerId: string;\n expectedIntentId: string;\n expectedUpdatedAt: string;\n remoteContainerId: string;\n remoteMetadataAccessStateHash: string;\n remoteMetadataDocumentId: string;\n /**\n * When present, an overtaking create revision is adopted as the\n * desired local parent and converted to a move from this remotely\n * committed parent instead of rejecting the remote identity.\n */\n supersededMovePreviousParentId?: string | undefined;\n }\n | undefined;\n moveIntentSettlement?:\n | {\n containerId: string;\n expectedIntentId: string;\n expectedUpdatedAt: string;\n }\n | undefined;\n pendingUpdate?: PendingUpdateFields | undefined;\n preserveDurableStructureWhenPending?: boolean | undefined;\n record: ContainerMetadataRecord;\n saveOptions?:\n | {\n createIntent?: ContainerCreateIntentInput;\n localUpdatedAt?: string;\n moveIntent?: ContainerMoveIntentInput | undefined;\n serverTimestamps?:\n | {\n createdAt?: string | null;\n updatedAt?: string | null;\n }\n | undefined;\n updatedAt?: string;\n }\n | undefined;\n settleAcceptedPendingOnConflict: boolean;\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<\n | {\n committed: true;\n container: ContainerRecord;\n createIntentSettled?: true | undefined;\n moveIntentSettled?: true | undefined;\n }\n | {\n committed: false;\n currentState: StoredContainerState | null;\n staleServerState?: true;\n }\n >;\n settleAcceptedMetadataPendingUpdates: (\n execSql: ExecSql,\n input: {\n containerId: string;\n expectedRecord: ContainerMetadataRecord;\n pendingUpdateIds: readonly string[];\n stillCurrent?: (() => boolean) | undefined;\n },\n ) => Promise<StoredContainerState | null>;\n saveContainer: (\n execSql: ExecSql,\n container: ContainerRecord,\n record: ContainerMetadataRecord | null,\n options?: SaveContainerOptions,\n ) => Promise<ContainerRecord>;\n /**\n * Atomically saves a container and enqueues its first metadata update.\n * Host adapters must implement the compound write in their own transaction.\n */\n saveContainerWithPendingUpdate: (\n execSql: ExecSql,\n container: ContainerRecord,\n record: ContainerMetadataRecord,\n options: SaveContainerWithPendingUpdateOptions,\n ) => Promise<ContainerRecord>;\n saveContainerAndDeletePendingUpdates: (\n execSql: ExecSql,\n container: ContainerRecord,\n record: ContainerMetadataRecord,\n pendingUpdateIds: readonly string[],\n ) => Promise<ContainerRecord>;\n /**\n * Revision-CAS settlement used by asynchronous create replay. Invalid\n * adapters are rejected before the remote mutation begins.\n */\n markCreateIntentRevisionSynced: (\n execSql: ExecSql,\n input: ContainerCreateIntentRevisionInput,\n ) => Promise<boolean>;\n /**\n * Explicit revision-CAS settlement. Move replay instead settles inside\n * commitMetadataMutation and requires its atomic acknowledgment.\n */\n markMoveIntentRevisionSynced: (\n execSql: ExecSql,\n input: ContainerMoveIntentRevisionInput,\n ) => Promise<boolean>;\n /**\n * Drop a move revision the server refuses for good and restore the\n * folder's previous parent in the same transaction; false if superseded.\n */\n abandonMoveIntentRevision: (\n execSql: ExecSql,\n input: ContainerMoveIntentRevisionInput & {\n previousParentContainerId: string;\n },\n ) => Promise<boolean>;\n}\n"]}
@@ -1,8 +1,8 @@
1
1
  import { and, inArray } from "drizzle-orm";
2
- import { ensureDocumentProjectionTables } from "../../sqlite/documentPersistence/index.js";
3
2
  import { containers, documentContainerProjectionTables, documentPlacementIntentTables, inContainerIntentLanes, localIntents, } from "../../sqlite/schema/index.js";
4
3
  import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
5
4
  import { ensureSqlTables, runSerializedSqlMutation, } from "../../sqlite/sqlSchema.js";
5
+ import { ensureDocumentProjectionTables } from "../documents/records/index.js";
6
6
  import { recordContainerHydrationTombstones } from "./containerHydrationPersistence.js";
7
7
  import { deleteContainerRowsInTransaction, ensureContainerTables, } from "./containerPersistence.js";
8
8
  import { containerIdsWithRemovalWork } from "./containerRemovalWork.js";