@tearleads/client-sdk 0.1.24 → 0.1.26

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 (97) hide show
  1. package/README.md +6 -0
  2. package/client/Tearleads.js +9 -1
  3. package/client/Tearleads.js.map +1 -1
  4. package/data/persistence/container-contents/containerContentsPersistenceTypes.d.ts +2 -0
  5. package/data/persistence/container-contents/containerContentsPersistenceTypes.js.map +1 -1
  6. package/data/persistence/container-contents/containerDeletionPersistence.js +3 -0
  7. package/data/persistence/container-contents/containerDeletionPersistence.js.map +1 -1
  8. package/data/persistence/container-contents/documentPurgePlacement.js +15 -0
  9. package/data/persistence/container-contents/documentPurgePlacement.js.map +1 -0
  10. package/data/persistence/documents/internal/attachmentStagingPersistence.js +3 -1
  11. package/data/persistence/documents/internal/attachmentStagingPersistence.js.map +1 -1
  12. package/data/persistence/documents/internal/documentDeletionPersistence.js +7 -0
  13. package/data/persistence/documents/internal/documentDeletionPersistence.js.map +1 -1
  14. package/data/persistence/documents/internal/syncQueuePersistence.js +8 -5
  15. package/data/persistence/documents/internal/syncQueuePersistence.js.map +1 -1
  16. package/data/persistence/documents/types.d.ts +2 -1
  17. package/data/persistence/documents/types.js.map +1 -1
  18. package/data/persistence/principalCurrentAcknowledgementPersistence.d.ts +13 -2
  19. package/data/persistence/principalCurrentAcknowledgementPersistence.js +36 -20
  20. package/data/persistence/principalCurrentAcknowledgementPersistence.js.map +1 -1
  21. package/index.d.ts +2 -2
  22. package/index.js +1 -1
  23. package/index.js.map +1 -1
  24. package/internal/api-client/ApiClient.d.ts +6 -2
  25. package/internal/api-client/ApiClient.js +4 -5
  26. package/internal/api-client/ApiClient.js.map +1 -1
  27. package/internal/api-client/apiRequestRuntime.d.ts +1 -2
  28. package/internal/api-client/apiRequestRuntime.js +12 -15
  29. package/internal/api-client/apiRequestRuntime.js.map +1 -1
  30. package/internal/api-client/routes/containers/mutations.js +2 -2
  31. package/internal/api-client/routes/containers/mutations.js.map +1 -1
  32. package/internal/api-client/sessionRefresh.js +14 -6
  33. package/internal/api-client/sessionRefresh.js.map +1 -1
  34. package/internal/api-client/sessionRenewal.js +54 -0
  35. package/internal/api-client/sessionRenewal.js.map +1 -0
  36. package/internal/validators/operation/containerMutations.d.ts +3 -0
  37. package/internal/validators/operation/containerMutations.js +1 -0
  38. package/internal/validators/operation/containerMutations.js.map +1 -1
  39. package/package.json +1 -1
  40. package/stores/documents/documentStore/attachmentStaging.js +24 -13
  41. package/stores/documents/documentStore/attachmentStaging.js.map +1 -1
  42. package/stores/documents/documentStore/attachments.js +6 -4
  43. package/stores/documents/documentStore/attachments.js.map +1 -1
  44. package/workflows/container-contents/container-state/delete.js +2 -0
  45. package/workflows/container-contents/container-state/delete.js.map +1 -1
  46. package/workflows/container-contents/container-state/localPurgeScope.js +49 -0
  47. package/workflows/container-contents/container-state/localPurgeScope.js.map +1 -0
  48. package/workflows/container-contents/container-state/purgeTree.js +54 -7
  49. package/workflows/container-contents/container-state/purgeTree.js.map +1 -1
  50. package/workflows/container-contents/container-state/purgeTreeCollection.js +11 -0
  51. package/workflows/container-contents/container-state/purgeTreeCollection.js.map +1 -1
  52. package/workflows/container-contents/container-state/purgeTreeContainerDeletion.js +9 -1
  53. package/workflows/container-contents/container-state/purgeTreeContainerDeletion.js.map +1 -1
  54. package/workflows/container-contents/container-state/purgeTreeScope.js +175 -0
  55. package/workflows/container-contents/container-state/purgeTreeScope.js.map +1 -0
  56. package/workflows/container-contents/container-state/readLocalPurgeScope.js +18 -0
  57. package/workflows/container-contents/container-state/readLocalPurgeScope.js.map +1 -0
  58. package/workflows/container-contents/container-state/remote.js +1 -0
  59. package/workflows/container-contents/container-state/remote.js.map +1 -1
  60. package/workflows/container-contents/container-state/types.js.map +1 -1
  61. package/workflows/container-contents/documentLinks.d.ts +5 -0
  62. package/workflows/container-contents/documentLinks.js +2 -0
  63. package/workflows/container-contents/documentLinks.js.map +1 -1
  64. package/workflows/container-contents/documentPurge.d.ts +7 -1
  65. package/workflows/container-contents/documentPurge.js +8 -1
  66. package/workflows/container-contents/documentPurge.js.map +1 -1
  67. package/workflows/documents/linkSet.d.ts +2 -0
  68. package/workflows/documents/linkSet.js +2 -0
  69. package/workflows/documents/linkSet.js.map +1 -1
  70. package/workflows/documents/linkSetRemote.d.ts +3 -0
  71. package/workflows/documents/linkSetRemote.js +3 -0
  72. package/workflows/documents/linkSetRemote.js.map +1 -1
  73. package/workflows/documents/purge.d.ts +4 -0
  74. package/workflows/documents/purge.js +7 -0
  75. package/workflows/documents/purge.js.map +1 -1
  76. package/workflows/documents/subtreePurgeScope.d.ts +8 -0
  77. package/workflows/documents/subtreePurgeScope.js +18 -0
  78. package/workflows/documents/subtreePurgeScope.js.map +1 -0
  79. package/workflows/organizations/acknowledgedPrincipalPredecessor.d.ts +39 -0
  80. package/workflows/organizations/acknowledgedPrincipalPredecessor.js +87 -0
  81. package/workflows/organizations/acknowledgedPrincipalPredecessor.js.map +1 -0
  82. package/workflows/organizations/currentOrganizationMutationRetention.js +13 -2
  83. package/workflows/organizations/currentOrganizationMutationRetention.js.map +1 -1
  84. package/workflows/organizations/index.d.ts +1 -0
  85. package/workflows/organizations/index.js +1 -0
  86. package/workflows/organizations/index.js.map +1 -1
  87. package/workflows/organizations/initialPrincipalCurrentPublication.js +3 -2
  88. package/workflows/organizations/initialPrincipalCurrentPublication.js.map +1 -1
  89. package/workflows/organizations/reconcileAcknowledgedPrincipalCurrent.js +68 -0
  90. package/workflows/organizations/reconcileAcknowledgedPrincipalCurrent.js.map +1 -0
  91. package/workflows/organizations/retainAcknowledgedPrincipalCurrents.d.ts +3 -0
  92. package/workflows/organizations/retainAcknowledgedPrincipalCurrents.js +33 -55
  93. package/workflows/organizations/retainAcknowledgedPrincipalCurrents.js.map +1 -1
  94. package/workflows/organizations/runtimeCurrentGroupMutation.js +13 -2
  95. package/workflows/organizations/runtimeCurrentGroupMutation.js.map +1 -1
  96. package/workflows/organizations/runtimeCurrentOrganizationMutation.js +2 -2
  97. package/workflows/organizations/runtimeCurrentOrganizationMutation.js.map +1 -1
package/README.md CHANGED
@@ -59,6 +59,12 @@ the page must be served from a secure context.
59
59
  Typecheck with `skipLibCheck: true`: the declarations of drizzle-orm and
60
60
  loro-crdt, which the SDK's types reference, do not typecheck without it.
61
61
 
62
+ When restoring a session with `tearleads.session.setContext`, restore its
63
+ `userId` together with its token. Automatic renewal replays an expired request
64
+ only when the original user is known and login authenticates that same user.
65
+ A login with a different or previously unknown user may install its new session,
66
+ but cannot authorize replay of the old request.
67
+
62
68
  The [developer guide](https://github.com/a2f0/tearleads/blob/main/docs/developer/client-sdk.md)
63
69
  covers constructor options, the host contract, the local keyring, and every
64
70
  public entry point.
@@ -220,7 +220,15 @@ export class Tearleads {
220
220
  this.session.logout();
221
221
  return Promise.resolve(false);
222
222
  }
223
- this.expiredSessionLoginPromise ??= this.session.login().finally(() => {
223
+ // The API client replays requests the expired session sent, so a login is
224
+ // their renewal only when the loaded keys authenticate that known user.
225
+ const expiredUserId = this.session.userId;
226
+ this.expiredSessionLoginPromise ??= this.session
227
+ .login()
228
+ .then((renewed) => renewed &&
229
+ expiredUserId !== null &&
230
+ this.session.userId === expiredUserId)
231
+ .finally(() => {
224
232
  this.expiredSessionLoginPromise = null;
225
233
  });
226
234
  return this.expiredSessionLoginPromise;
@@ -1 +1 @@
1
- {"version":3,"file":"Tearleads.js","sourceRoot":"","sources":["Tearleads.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiClient } from \"@tearleads/api-client\";\nimport type { BlobStore } from \"../data/blobContracts\";\nimport {\n type DocumentProjectorRegistry,\n type DocumentProjectorRegistryInput,\n resolveDocumentProjectorRegistry,\n} from \"../data/documents/documentKinds\";\nimport { createDomainScope, type DomainScope } from \"../data/domainScope\";\nimport type { SecurityIncidentReporter } from \"../data/securityIncidents\";\nimport { disposeDomainSyncCoordinator } from \"../data/sync/syncCoordinator\";\nimport { resolveIdentityTrustDomain } from \"../data/trustedUserIdentity\";\nimport type { ProvisionedSystemContainerSpec } from \"../workflows/registration\";\nimport { ApiVersion } from \"./apiVersion\";\nimport { type BlobStoreFactory, Blobs } from \"./blobs\";\nimport {\n type ContainerContents,\n createContainerContents,\n} from \"./containerContents\";\nimport { Database, type DatabaseOptions } from \"./database\";\nimport { createDeviceFirst, type DeviceFirst } from \"./deviceFirst\";\nimport { createDocuments, type Documents } from \"./documents\";\nimport { Events } from \"./events\";\nimport {\n createIdentity,\n type Identity,\n type IdentityOptions,\n} from \"./identity\";\nimport { type Logger, logErrorToConsole } from \"./logger\";\nimport { Network } from \"./network\";\nimport { createOrganizations, type Organizations } from \"./organizations\";\nimport type { PrincipalHistoryKeyProvider } from \"./principalHistoryProtection\";\nimport { createRoot, type Root, rootRuntimeOf } from \"./root\";\nimport {\n createSecurityIncidentService,\n type SecurityIncidentListener,\n type SecurityIncidents,\n} from \"./securityIncidents\";\nimport { createSession } from \"./session\";\nimport type { Session } from \"./session/sessionTypes\";\nimport { SyncBillingGate } from \"./syncBillingGate\";\nimport { createUserIdentities, type UserIdentities } from \"./userIdentities\";\nimport {\n createRuntime,\n type InternalRuntime,\n type Runtime,\n} from \"./workflowRuntime\";\n\nfunction createClientSecurityIncidentService(input: {\n readonly apiBaseUrl: string;\n readonly database: Database;\n readonly logError: (message: string | Error, cause?: unknown) => void;\n readonly options: ClientOptions;\n}) {\n const identityTrustDomain = resolveIdentityTrustDomain({\n apiBaseUrl: input.apiBaseUrl,\n identityTrustDomain: input.options.identityTrustDomain,\n });\n return {\n identityTrustDomain,\n service: createSecurityIncidentService({\n database: input.database,\n logError: input.logError,\n onIncident: input.options.onSecurityIncident,\n trustDomain: identityTrustDomain,\n }),\n };\n}\n\nexport type ClientDatabaseOptions = Omit<DatabaseOptions, \"status\">;\n\nexport interface ClientOptions {\n apiBaseUrl?: string | undefined;\n blobStore?: BlobStore | undefined;\n blobStoreFactory?: BlobStoreFactory | undefined;\n database?: ClientDatabaseOptions | undefined;\n documentProjectors?: DocumentProjectorRegistryInput | undefined;\n events?: ReadonlyArray<unknown> | undefined;\n identity?: IdentityOptions | undefined;\n /**\n * Absolute, host-controlled API namespace for durable user-identity pins.\n * Required only when apiBaseUrl is relative and no browser origin exists.\n */\n identityTrustDomain?: string | undefined;\n identityProvisioning?: \"auto\" | \"manual\" | undefined;\n logger?: Logger | undefined;\n online?: boolean | undefined;\n /** Called after a trust-boundary verification failure is durably recorded. */\n onSecurityIncident?: SecurityIncidentListener | undefined;\n /**\n * Per-pane discriminator so documents in distinct panes derive distinct Loro\n * peer ids (e.g. the pane's local identity namespace). Omit for single-pane.\n */\n peerScope?: string | undefined;\n /** Private host key for durable history progress; omitted uses disposable session keys. */\n principalHistoryKeyProvider?: PrincipalHistoryKeyProvider | undefined;\n /** Compound policy dispatch/recovery deadline in milliseconds; defaults to 60 seconds. */\n principalMutationTimeoutMs?: number | undefined;\n /**\n * App-owned system containers born with every new organization (both\n * registration and additional-org creation provision them atomically in the\n * same server call), e.g. a trash bin. The app owns these because their slot\n * namespaces are app concepts the SDK stays agnostic to.\n */\n provisionedSystemContainers?:\n | ReadonlyArray<ProvisionedSystemContainerSpec>\n | undefined;\n}\n\nexport class Tearleads {\n readonly apiVersion = new ApiVersion();\n readonly blobs: Blobs;\n readonly database: Database;\n readonly deviceFirst: DeviceFirst;\n readonly documents: Documents;\n readonly events: Events;\n readonly containerContents: ContainerContents;\n readonly identity: Identity;\n readonly network: Network;\n readonly organizations: Organizations;\n readonly root: Root;\n readonly runtime: Runtime;\n readonly securityIncidents: SecurityIncidents;\n readonly session: Session;\n readonly syncBillingGate: SyncBillingGate;\n readonly userIdentities: UserIdentities;\n\n private readonly apiClient: ApiClient;\n private readonly documentProjectors: DocumentProjectorRegistry;\n private readonly disposeSecurityIncidents: () => void;\n private retirePrincipalHistoryProtection = () => {};\n // Tracks the storage/identity pair that owns domain-scoped runtime state.\n private domainScopeKey: string | null = null;\n // Recreated when the storage database or signing identity changes.\n private domainScopeValue: DomainScope = createDomainScope();\n private readonly logErrorHandler: (\n message: string | Error,\n cause?: unknown,\n ) => void;\n private readonly logHandler: (message: string) => void;\n private autoIdentityProvisioned = false;\n private autoIdentityProvisioningPromise: Promise<void> | null = null;\n private expiredSessionLoginPromise: Promise<boolean> | null = null;\n // Rebound to the workflow runtime once it exists; the session is created first.\n private pinLocalUserIdentity: InternalRuntime[\"pinLocalUserIdentity\"] =\n async () => {\n throw new Error(\"Trusted user identity runtime is not initialized\");\n };\n private boundUserId: InternalRuntime[\"boundUserId\"] = async () => {\n throw new Error(\"Trusted user identity runtime is not initialized\");\n };\n\n constructor(options: ClientOptions = {}) {\n const apiBaseUrl = options.apiBaseUrl ?? \"\";\n this.apiClient = new ApiClient(apiBaseUrl);\n this.blobs = new Blobs(options.blobStore, options.blobStoreFactory);\n this.database = new Database(options.database);\n this.documentProjectors = resolveDocumentProjectorRegistry(\n options.documentProjectors,\n );\n this.events = new Events(options.events, () =>\n this.apiClient.clearWriterProjectionCaches(),\n );\n this.logHandler = options.logger?.log ?? (() => undefined);\n this.logErrorHandler = options.logger?.logError ?? logErrorToConsole;\n const security = createClientSecurityIncidentService({\n apiBaseUrl,\n database: this.database,\n logError: this.logError,\n options,\n });\n this.securityIncidents = security.service.incidents;\n this.disposeSecurityIncidents = security.service.dispose;\n this.network = new Network(options.online);\n this.syncBillingGate = new SyncBillingGate();\n let session: Session | null = null;\n this.identity = createIdentity(\n options.identity,\n (signingFingerprint) =>\n this.blobs.updateIdentityNamespace(signingFingerprint),\n this.log,\n {\n bootstrapLocalRootContainer: async () => {\n if (this.database.status !== \"ready\") {\n throw new Error(\n \"identity.generate requires a ready SQLite database.\",\n );\n }\n if (!session) {\n throw new Error(\n \"identity.generate requires an initialized session.\",\n );\n }\n\n return session.bootstrapLocalRootContainer();\n },\n getUserId: () => session?.userId ?? null,\n },\n );\n session = createSession({\n api: this.apiClient,\n database: this.database,\n documentProjectors: this.documentProjectors,\n identity: this.identity,\n log: this.log,\n logError: this.logError,\n boundUserIdForSigningKey: (signingKeyFingerprint) =>\n this.boundUserId(signingKeyFingerprint),\n onUserIdentityAvailable: (userId, candidate) =>\n this.pinLocalUserIdentity(userId, candidate),\n provisionedSystemContainers: options.provisionedSystemContainers,\n reportSecurityIncident: security.service.report,\n });\n this.session = session;\n const runtime = this.createWorkflowRuntime(\n security.identityTrustDomain,\n options,\n security.service.report,\n );\n this.bindTrustedIdentityRuntime(runtime);\n this.runtime = runtime.publicRuntime;\n this.documents = createDocuments({\n getDefaultContainerId: () => this.session.containerId,\n runtime,\n });\n this.containerContents = createContainerContents(runtime);\n this.deviceFirst = createDeviceFirst(runtime, this.containerContents);\n this.organizations = createOrganizations(runtime, this.containerContents);\n this.root = createRoot(rootRuntimeOf(runtime, this.session));\n this.userIdentities = createUserIdentities({\n log: this.log,\n resolveTrustedUserIdentity: (userId) =>\n runtime.workflowInput().resolveTrustedUserIdentity(userId),\n });\n\n this.configureApiClientCallbacks();\n\n if (options.identityProvisioning === \"auto\") {\n this.startAutomaticIdentityProvisioning();\n }\n }\n\n /** Rebinds the session's trust callbacks once the runtime exists. */\n private bindTrustedIdentityRuntime(runtime: InternalRuntime): void {\n this.retirePrincipalHistoryProtection =\n runtime.retirePrincipalHistoryProtection;\n this.pinLocalUserIdentity = (userId, candidate) =>\n runtime.pinLocalUserIdentity(userId, candidate);\n this.boundUserId = (signingKeyFingerprint) =>\n runtime.boundUserId(signingKeyFingerprint);\n }\n\n private configureApiClientCallbacks(): void {\n this.apiClient.setOnError((message) => this.logError(message));\n // An authoritative host source owns connectivity. Without one, request\n // outcomes remain reachability hints. Browser and native app bindings both\n // install authoritative sources so a backend outage cannot stop retries.\n this.apiClient.setOnNetworkError(() =>\n this.network.reportReachability(false),\n );\n this.apiClient.setOnNetworkSuccess(() =>\n this.network.reportReachability(true),\n );\n this.apiClient.setOnApiVersion((version) =>\n this.apiVersion.observe(version),\n );\n this.apiClient.setOnSessionExpired(() => this.loginAfterSessionExpired());\n this.apiClient.setOnPaymentRequired((organizationId) =>\n this.syncBillingGate.notifyPaymentRequired(organizationId),\n );\n }\n\n private createWorkflowRuntime(\n identityTrustDomain: string | null,\n options: ClientOptions,\n reportSecurityIncident: SecurityIncidentReporter,\n ): InternalRuntime {\n return createRuntime({\n api: this.apiClient,\n blobs: this.blobs,\n database: this.database,\n documentProjectors: this.documentProjectors,\n events: this.events,\n getDomainScope: () => this.domainScope,\n identity: this.identity,\n identityTrustDomain,\n log: this.log,\n logError: this.logError,\n network: this.network,\n peerScope: options.peerScope ?? null,\n principalHistoryKeyProvider: options.principalHistoryKeyProvider,\n principalMutationTimeoutMs: options.principalMutationTimeoutMs,\n reportSecurityIncident,\n session: this.session,\n syncBillingGate: this.syncBillingGate,\n });\n }\n\n log = (message: string): void => {\n this.logHandler(message);\n };\n\n // Preserve the host's return value so best-effort callers can also catch\n // rejected promises from async loggers accepted by the void callback type.\n logError = (message: string | Error, cause?: unknown): void =>\n this.logErrorHandler(message, cause);\n\n /**\n * Mint a single-use ticket to authenticate the server-events websocket\n * handshake (which cannot carry the bearer header). Returns null when no\n * session is active. The caller appends it to the websocket URL.\n */\n requestWebSocketTicket(): Promise<string | null> {\n return this.apiClient.requestWebSocketTicket();\n }\n\n get domainScope(): DomainScope {\n const nextScopeKey = JSON.stringify([\n this.database.id,\n this.identity.signingFingerprint,\n ]);\n if (this.domainScopeKey !== nextScopeKey) {\n this.domainScopeKey = nextScopeKey;\n this.domainScopeValue = createDomainScope();\n }\n\n return this.domainScopeValue;\n }\n\n /**\n * Tear down background work for the active domain scope: stop the reconciler\n * and force-stop the sync coordinator pump, then drop the coordinator from\n * its registry. Hosts call this from unmount/cleanup so a pump cannot outlive\n * the React tree that created it. A later access for the same scope rebuilds\n * fresh state.\n */\n dispose(): void {\n const domainScope = this.domainScope;\n this.disposeSecurityIncidents();\n this.retirePrincipalHistoryProtection();\n this.deviceFirst.dispose();\n disposeDomainSyncCoordinator(domainScope);\n }\n\n private loginAfterSessionExpired(): Promise<boolean> {\n if (!this.session.authToken || !this.identity.signingKeyPair) {\n this.session.logout();\n return Promise.resolve(false);\n }\n\n this.expiredSessionLoginPromise ??= this.session.login().finally(() => {\n this.expiredSessionLoginPromise = null;\n });\n return this.expiredSessionLoginPromise;\n }\n\n private startAutomaticIdentityProvisioning(): void {\n let unsubscribe: (() => void) | null = null;\n const stopProvisioningListener = () => {\n unsubscribe?.();\n unsubscribe = null;\n };\n const maybeProvisionIdentity = () => {\n if (this.autoIdentityProvisioned || this.identity.signingKeyPair) {\n stopProvisioningListener();\n return;\n }\n\n if (\n this.autoIdentityProvisioningPromise ||\n this.database.status !== \"ready\"\n ) {\n return;\n }\n\n this.autoIdentityProvisioningPromise = this.identity\n .generate()\n .then(() => {\n this.autoIdentityProvisioned = true;\n stopProvisioningListener();\n })\n .catch((error: unknown) => {\n try {\n this.logError(\"Automatic identity provisioning failed\", error);\n } catch {\n // Keep background provisioning failures from surfacing as unhandled rejections.\n }\n })\n .finally(() => {\n this.autoIdentityProvisioningPromise = null;\n });\n };\n\n unsubscribe = this.database.subscribe(maybeProvisionIdentity);\n void Promise.resolve().then(maybeProvisionIdentity);\n }\n}\n"]}
1
+ {"version":3,"file":"Tearleads.js","sourceRoot":"","sources":["Tearleads.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiClient } from \"@tearleads/api-client\";\nimport type { BlobStore } from \"../data/blobContracts\";\nimport {\n type DocumentProjectorRegistry,\n type DocumentProjectorRegistryInput,\n resolveDocumentProjectorRegistry,\n} from \"../data/documents/documentKinds\";\nimport { createDomainScope, type DomainScope } from \"../data/domainScope\";\nimport type { SecurityIncidentReporter } from \"../data/securityIncidents\";\nimport { disposeDomainSyncCoordinator } from \"../data/sync/syncCoordinator\";\nimport { resolveIdentityTrustDomain } from \"../data/trustedUserIdentity\";\nimport type { ProvisionedSystemContainerSpec } from \"../workflows/registration\";\nimport { ApiVersion } from \"./apiVersion\";\nimport { type BlobStoreFactory, Blobs } from \"./blobs\";\nimport {\n type ContainerContents,\n createContainerContents,\n} from \"./containerContents\";\nimport { Database, type DatabaseOptions } from \"./database\";\nimport { createDeviceFirst, type DeviceFirst } from \"./deviceFirst\";\nimport { createDocuments, type Documents } from \"./documents\";\nimport { Events } from \"./events\";\nimport {\n createIdentity,\n type Identity,\n type IdentityOptions,\n} from \"./identity\";\nimport { type Logger, logErrorToConsole } from \"./logger\";\nimport { Network } from \"./network\";\nimport { createOrganizations, type Organizations } from \"./organizations\";\nimport type { PrincipalHistoryKeyProvider } from \"./principalHistoryProtection\";\nimport { createRoot, type Root, rootRuntimeOf } from \"./root\";\nimport {\n createSecurityIncidentService,\n type SecurityIncidentListener,\n type SecurityIncidents,\n} from \"./securityIncidents\";\nimport { createSession } from \"./session\";\nimport type { Session } from \"./session/sessionTypes\";\nimport { SyncBillingGate } from \"./syncBillingGate\";\nimport { createUserIdentities, type UserIdentities } from \"./userIdentities\";\nimport {\n createRuntime,\n type InternalRuntime,\n type Runtime,\n} from \"./workflowRuntime\";\n\nfunction createClientSecurityIncidentService(input: {\n readonly apiBaseUrl: string;\n readonly database: Database;\n readonly logError: (message: string | Error, cause?: unknown) => void;\n readonly options: ClientOptions;\n}) {\n const identityTrustDomain = resolveIdentityTrustDomain({\n apiBaseUrl: input.apiBaseUrl,\n identityTrustDomain: input.options.identityTrustDomain,\n });\n return {\n identityTrustDomain,\n service: createSecurityIncidentService({\n database: input.database,\n logError: input.logError,\n onIncident: input.options.onSecurityIncident,\n trustDomain: identityTrustDomain,\n }),\n };\n}\n\nexport type ClientDatabaseOptions = Omit<DatabaseOptions, \"status\">;\n\nexport interface ClientOptions {\n apiBaseUrl?: string | undefined;\n blobStore?: BlobStore | undefined;\n blobStoreFactory?: BlobStoreFactory | undefined;\n database?: ClientDatabaseOptions | undefined;\n documentProjectors?: DocumentProjectorRegistryInput | undefined;\n events?: ReadonlyArray<unknown> | undefined;\n identity?: IdentityOptions | undefined;\n /**\n * Absolute, host-controlled API namespace for durable user-identity pins.\n * Required only when apiBaseUrl is relative and no browser origin exists.\n */\n identityTrustDomain?: string | undefined;\n identityProvisioning?: \"auto\" | \"manual\" | undefined;\n logger?: Logger | undefined;\n online?: boolean | undefined;\n /** Called after a trust-boundary verification failure is durably recorded. */\n onSecurityIncident?: SecurityIncidentListener | undefined;\n /**\n * Per-pane discriminator so documents in distinct panes derive distinct Loro\n * peer ids (e.g. the pane's local identity namespace). Omit for single-pane.\n */\n peerScope?: string | undefined;\n /** Private host key for durable history progress; omitted uses disposable session keys. */\n principalHistoryKeyProvider?: PrincipalHistoryKeyProvider | undefined;\n /** Compound policy dispatch/recovery deadline in milliseconds; defaults to 60 seconds. */\n principalMutationTimeoutMs?: number | undefined;\n /**\n * App-owned system containers born with every new organization (both\n * registration and additional-org creation provision them atomically in the\n * same server call), e.g. a trash bin. The app owns these because their slot\n * namespaces are app concepts the SDK stays agnostic to.\n */\n provisionedSystemContainers?:\n | ReadonlyArray<ProvisionedSystemContainerSpec>\n | undefined;\n}\n\nexport class Tearleads {\n readonly apiVersion = new ApiVersion();\n readonly blobs: Blobs;\n readonly database: Database;\n readonly deviceFirst: DeviceFirst;\n readonly documents: Documents;\n readonly events: Events;\n readonly containerContents: ContainerContents;\n readonly identity: Identity;\n readonly network: Network;\n readonly organizations: Organizations;\n readonly root: Root;\n readonly runtime: Runtime;\n readonly securityIncidents: SecurityIncidents;\n readonly session: Session;\n readonly syncBillingGate: SyncBillingGate;\n readonly userIdentities: UserIdentities;\n\n private readonly apiClient: ApiClient;\n private readonly documentProjectors: DocumentProjectorRegistry;\n private readonly disposeSecurityIncidents: () => void;\n private retirePrincipalHistoryProtection = () => {};\n // Tracks the storage/identity pair that owns domain-scoped runtime state.\n private domainScopeKey: string | null = null;\n // Recreated when the storage database or signing identity changes.\n private domainScopeValue: DomainScope = createDomainScope();\n private readonly logErrorHandler: (\n message: string | Error,\n cause?: unknown,\n ) => void;\n private readonly logHandler: (message: string) => void;\n private autoIdentityProvisioned = false;\n private autoIdentityProvisioningPromise: Promise<void> | null = null;\n private expiredSessionLoginPromise: Promise<boolean> | null = null;\n // Rebound to the workflow runtime once it exists; the session is created first.\n private pinLocalUserIdentity: InternalRuntime[\"pinLocalUserIdentity\"] =\n async () => {\n throw new Error(\"Trusted user identity runtime is not initialized\");\n };\n private boundUserId: InternalRuntime[\"boundUserId\"] = async () => {\n throw new Error(\"Trusted user identity runtime is not initialized\");\n };\n\n constructor(options: ClientOptions = {}) {\n const apiBaseUrl = options.apiBaseUrl ?? \"\";\n this.apiClient = new ApiClient(apiBaseUrl);\n this.blobs = new Blobs(options.blobStore, options.blobStoreFactory);\n this.database = new Database(options.database);\n this.documentProjectors = resolveDocumentProjectorRegistry(\n options.documentProjectors,\n );\n this.events = new Events(options.events, () =>\n this.apiClient.clearWriterProjectionCaches(),\n );\n this.logHandler = options.logger?.log ?? (() => undefined);\n this.logErrorHandler = options.logger?.logError ?? logErrorToConsole;\n const security = createClientSecurityIncidentService({\n apiBaseUrl,\n database: this.database,\n logError: this.logError,\n options,\n });\n this.securityIncidents = security.service.incidents;\n this.disposeSecurityIncidents = security.service.dispose;\n this.network = new Network(options.online);\n this.syncBillingGate = new SyncBillingGate();\n let session: Session | null = null;\n this.identity = createIdentity(\n options.identity,\n (signingFingerprint) =>\n this.blobs.updateIdentityNamespace(signingFingerprint),\n this.log,\n {\n bootstrapLocalRootContainer: async () => {\n if (this.database.status !== \"ready\") {\n throw new Error(\n \"identity.generate requires a ready SQLite database.\",\n );\n }\n if (!session) {\n throw new Error(\n \"identity.generate requires an initialized session.\",\n );\n }\n\n return session.bootstrapLocalRootContainer();\n },\n getUserId: () => session?.userId ?? null,\n },\n );\n session = createSession({\n api: this.apiClient,\n database: this.database,\n documentProjectors: this.documentProjectors,\n identity: this.identity,\n log: this.log,\n logError: this.logError,\n boundUserIdForSigningKey: (signingKeyFingerprint) =>\n this.boundUserId(signingKeyFingerprint),\n onUserIdentityAvailable: (userId, candidate) =>\n this.pinLocalUserIdentity(userId, candidate),\n provisionedSystemContainers: options.provisionedSystemContainers,\n reportSecurityIncident: security.service.report,\n });\n this.session = session;\n const runtime = this.createWorkflowRuntime(\n security.identityTrustDomain,\n options,\n security.service.report,\n );\n this.bindTrustedIdentityRuntime(runtime);\n this.runtime = runtime.publicRuntime;\n this.documents = createDocuments({\n getDefaultContainerId: () => this.session.containerId,\n runtime,\n });\n this.containerContents = createContainerContents(runtime);\n this.deviceFirst = createDeviceFirst(runtime, this.containerContents);\n this.organizations = createOrganizations(runtime, this.containerContents);\n this.root = createRoot(rootRuntimeOf(runtime, this.session));\n this.userIdentities = createUserIdentities({\n log: this.log,\n resolveTrustedUserIdentity: (userId) =>\n runtime.workflowInput().resolveTrustedUserIdentity(userId),\n });\n\n this.configureApiClientCallbacks();\n\n if (options.identityProvisioning === \"auto\") {\n this.startAutomaticIdentityProvisioning();\n }\n }\n\n /** Rebinds the session's trust callbacks once the runtime exists. */\n private bindTrustedIdentityRuntime(runtime: InternalRuntime): void {\n this.retirePrincipalHistoryProtection =\n runtime.retirePrincipalHistoryProtection;\n this.pinLocalUserIdentity = (userId, candidate) =>\n runtime.pinLocalUserIdentity(userId, candidate);\n this.boundUserId = (signingKeyFingerprint) =>\n runtime.boundUserId(signingKeyFingerprint);\n }\n\n private configureApiClientCallbacks(): void {\n this.apiClient.setOnError((message) => this.logError(message));\n // An authoritative host source owns connectivity. Without one, request\n // outcomes remain reachability hints. Browser and native app bindings both\n // install authoritative sources so a backend outage cannot stop retries.\n this.apiClient.setOnNetworkError(() =>\n this.network.reportReachability(false),\n );\n this.apiClient.setOnNetworkSuccess(() =>\n this.network.reportReachability(true),\n );\n this.apiClient.setOnApiVersion((version) =>\n this.apiVersion.observe(version),\n );\n this.apiClient.setOnSessionExpired(() => this.loginAfterSessionExpired());\n this.apiClient.setOnPaymentRequired((organizationId) =>\n this.syncBillingGate.notifyPaymentRequired(organizationId),\n );\n }\n\n private createWorkflowRuntime(\n identityTrustDomain: string | null,\n options: ClientOptions,\n reportSecurityIncident: SecurityIncidentReporter,\n ): InternalRuntime {\n return createRuntime({\n api: this.apiClient,\n blobs: this.blobs,\n database: this.database,\n documentProjectors: this.documentProjectors,\n events: this.events,\n getDomainScope: () => this.domainScope,\n identity: this.identity,\n identityTrustDomain,\n log: this.log,\n logError: this.logError,\n network: this.network,\n peerScope: options.peerScope ?? null,\n principalHistoryKeyProvider: options.principalHistoryKeyProvider,\n principalMutationTimeoutMs: options.principalMutationTimeoutMs,\n reportSecurityIncident,\n session: this.session,\n syncBillingGate: this.syncBillingGate,\n });\n }\n\n log = (message: string): void => {\n this.logHandler(message);\n };\n\n // Preserve the host's return value so best-effort callers can also catch\n // rejected promises from async loggers accepted by the void callback type.\n logError = (message: string | Error, cause?: unknown): void =>\n this.logErrorHandler(message, cause);\n\n /**\n * Mint a single-use ticket to authenticate the server-events websocket\n * handshake (which cannot carry the bearer header). Returns null when no\n * session is active. The caller appends it to the websocket URL.\n */\n requestWebSocketTicket(): Promise<string | null> {\n return this.apiClient.requestWebSocketTicket();\n }\n\n get domainScope(): DomainScope {\n const nextScopeKey = JSON.stringify([\n this.database.id,\n this.identity.signingFingerprint,\n ]);\n if (this.domainScopeKey !== nextScopeKey) {\n this.domainScopeKey = nextScopeKey;\n this.domainScopeValue = createDomainScope();\n }\n\n return this.domainScopeValue;\n }\n\n /**\n * Tear down background work for the active domain scope: stop the reconciler\n * and force-stop the sync coordinator pump, then drop the coordinator from\n * its registry. Hosts call this from unmount/cleanup so a pump cannot outlive\n * the React tree that created it. A later access for the same scope rebuilds\n * fresh state.\n */\n dispose(): void {\n const domainScope = this.domainScope;\n this.disposeSecurityIncidents();\n this.retirePrincipalHistoryProtection();\n this.deviceFirst.dispose();\n disposeDomainSyncCoordinator(domainScope);\n }\n\n private loginAfterSessionExpired(): Promise<boolean> {\n if (!this.session.authToken || !this.identity.signingKeyPair) {\n this.session.logout();\n return Promise.resolve(false);\n }\n\n // The API client replays requests the expired session sent, so a login is\n // their renewal only when the loaded keys authenticate that known user.\n const expiredUserId = this.session.userId;\n this.expiredSessionLoginPromise ??= this.session\n .login()\n .then(\n (renewed) =>\n renewed &&\n expiredUserId !== null &&\n this.session.userId === expiredUserId,\n )\n .finally(() => {\n this.expiredSessionLoginPromise = null;\n });\n return this.expiredSessionLoginPromise;\n }\n\n private startAutomaticIdentityProvisioning(): void {\n let unsubscribe: (() => void) | null = null;\n const stopProvisioningListener = () => {\n unsubscribe?.();\n unsubscribe = null;\n };\n const maybeProvisionIdentity = () => {\n if (this.autoIdentityProvisioned || this.identity.signingKeyPair) {\n stopProvisioningListener();\n return;\n }\n\n if (\n this.autoIdentityProvisioningPromise ||\n this.database.status !== \"ready\"\n ) {\n return;\n }\n\n this.autoIdentityProvisioningPromise = this.identity\n .generate()\n .then(() => {\n this.autoIdentityProvisioned = true;\n stopProvisioningListener();\n })\n .catch((error: unknown) => {\n try {\n this.logError(\"Automatic identity provisioning failed\", error);\n } catch {\n // Keep background provisioning failures from surfacing as unhandled rejections.\n }\n })\n .finally(() => {\n this.autoIdentityProvisioningPromise = null;\n });\n };\n\n unsubscribe = this.database.subscribe(maybeProvisionIdentity);\n void Promise.resolve().then(maybeProvisionIdentity);\n }\n}\n"]}
@@ -153,6 +153,8 @@ export interface ContainerContentsPersistence extends DormantMetadataSweepPersis
153
153
  updatedAt?: string;
154
154
  }) => Promise<void>;
155
155
  deleteContainers: (execSql: ExecSql, removals: ReadonlyArray<ContainerRemoval>, options?: {
156
+ /** Must run inside the deletion transaction before any row is removed. */
157
+ beforeDeleteInTransaction?: ((transactionExecSql: ExecSql) => Promise<boolean>) | undefined;
156
158
  /** Unsigned discovery removes listings but preserves metadata and local intent. */
157
159
  discoveryOnly?: boolean;
158
160
  /**
@@ -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 { HeldContainerBinding } from \"../../containers/containerBinding\";\nimport type { DocumentSyncPullContinuation } from \"../../documents/shared/pullContinuation\";\nimport type {\n DocumentRecord,\n PendingUpdateFields,\n PendingUpdateRecord,\n} from \"../../sqlite/documentPersistence\";\nimport type { ExecSql } from \"../../sqlite/sqlSchema\";\nimport type { ContainerRecord } from \"../containers/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 /** 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 { HeldContainerBinding } from \"../../containers/containerBinding\";\nimport type { DocumentSyncPullContinuation } from \"../../documents/shared/pullContinuation\";\nimport type {\n DocumentRecord,\n PendingUpdateFields,\n PendingUpdateRecord,\n} from \"../../sqlite/documentPersistence\";\nimport type { ExecSql } from \"../../sqlite/sqlSchema\";\nimport type { ContainerRecord } from \"../containers/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"]}
@@ -138,6 +138,9 @@ export async function deleteStoredContainers(execSql, removals, options) {
138
138
  await sqlContainerSyncWatermarkPersistence.ensureSchema(lockedExecSql);
139
139
  const runtime = getClientSQLitePersistenceRuntime(lockedExecSql);
140
140
  const remove = async (tx) => {
141
+ if (options?.beforeDeleteInTransaction &&
142
+ !(await options.beforeDeleteInTransaction(lockedExecSql)))
143
+ return [];
141
144
  if (!(await canApplyContainerRemovals({
142
145
  expectedContainers: options?.expectedContainers,
143
146
  removals: uniqueRemovals,
@@ -1 +1 @@
1
- {"version":3,"file":"containerDeletionPersistence.js","sourceRoot":"","sources":["containerDeletionPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { inArray } from \"drizzle-orm\";\nimport { ensureDocumentProjectionTables } from \"../../sqlite/documentPersistence\";\nimport {\n containerCreateIntents,\n containerCreateIntentTables,\n containerMoveIntents,\n containerMoveIntentTables,\n containers,\n documentContainerProjectionTables,\n documentMoveIntentTables,\n} from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport {\n deleteContainerRowsInTransaction,\n ensureContainerTables,\n} from \"../containers/containerPersistence\";\nimport {\n deleteContainerWatermarksInTransaction,\n sqlContainerSyncWatermarkPersistence,\n} from \"../containers/containerSyncWatermarkPersistence\";\nimport type {\n ContainerContentsPersistence,\n ContainerDeletionGuard,\n ContainerRemoval,\n} from \"./containerContentsPersistenceTypes\";\nimport { recordContainerHydrationTombstones } from \"./containerHydrationPersistence\";\nimport { containerIdsWithRemovalWork } from \"./containerRemovalWork\";\nimport { repairDocumentsForRemovedContainersInTransaction } from \"./containerStructuralRepair\";\nimport {\n refreshPlacementIntentsAfterRemoval,\n repairLinkIntentsForRemovedContainers,\n} from \"./documentLinkRemovalRepair\";\nimport {\n deleteContainerMetadataDocumentRowsInTransaction,\n retainDormantContainerMetadataInTransaction,\n} from \"./dormantContainerMetadata\";\n\ntype DeleteContainerOptions = NonNullable<\n Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[2]\n>;\n\nfunction normalizeContainerRemovals(\n removals: ReadonlyArray<ContainerRemoval>,\n): ContainerRemoval[] {\n const removalByContainerId = new Map<string, ContainerRemoval>();\n for (const removal of removals) {\n const current = removalByContainerId.get(removal.containerId);\n if (\n !current ||\n (current.reason === \"access_revoked\" && removal.reason === \"deleted\") ||\n (current.reason === removal.reason &&\n current.updatedAt < removal.updatedAt)\n ) {\n removalByContainerId.set(removal.containerId, removal);\n }\n }\n return Array.from(removalByContainerId.values());\n}\n\nfunction loadGuardedContainerRows(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n) {\n return tx\n .select({\n effectiveAccessLevel: containers.effectiveAccessLevel,\n id: containers.id,\n metadataDocumentId: containers.metadataDocumentId,\n organizationId: containers.organizationId,\n parentId: containers.parentId,\n serverCreatedAt: containers.serverCreatedAt,\n serverUpdatedAt: containers.serverUpdatedAt,\n systemSlot: containers.systemSlot,\n })\n .from(containers)\n .where(inArray(containers.id, [...containerIds]));\n}\n\ntype GuardedContainerRow = Awaited<\n ReturnType<typeof loadGuardedContainerRows>\n>[number];\n\nfunction sameGuardedContainerRow(\n row: GuardedContainerRow | undefined,\n guard: ContainerDeletionGuard,\n): boolean {\n const expected = guard.expectedContainer;\n if (!row || !expected) return row === undefined && expected === null;\n return (\n row.id === expected.id &&\n row.organizationId === expected.organizationId &&\n row.parentId === expected.parentId &&\n row.metadataDocumentId === expected.metadataDocumentId &&\n row.systemSlot === (expected.systemSlot ?? null) &&\n row.effectiveAccessLevel === (expected.effectiveAccessLevel ?? \"read\") &&\n row.serverCreatedAt === (expected.serverCreatedAt ?? null) &&\n row.serverUpdatedAt === (expected.serverUpdatedAt ?? null)\n );\n}\n\nasync function canApplyContainerRemovals(input: {\n expectedContainers: DeleteContainerOptions[\"expectedContainers\"];\n removals: ReadonlyArray<ContainerRemoval>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<boolean> {\n const currentRows = await loadGuardedContainerRows(\n input.tx,\n input.removals.map((removal) => removal.containerId),\n );\n const currentRowById = new Map(\n currentRows.flatMap((row) => (row.id ? ([[row.id, row]] as const) : [])),\n );\n const guardById = new Map(\n input.expectedContainers?.map((guard) => [guard.containerId, guard]) ?? [],\n );\n return input.removals.every((removal) => {\n const currentRow = currentRowById.get(removal.containerId);\n const guard = guardById.get(removal.containerId);\n return (\n (!guard || sameGuardedContainerRow(currentRow, guard)) &&\n (currentRow?.serverUpdatedAt == null ||\n currentRow.serverUpdatedAt <= removal.updatedAt)\n );\n });\n}\n\nasync function retainRemovedContainerMetadata(input: {\n containerIds: ReadonlyArray<string>;\n retainedAt: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n if (input.containerIds.length === 0) return;\n const retainedContainers = await input.tx\n .select({\n containerId: containers.id,\n organizationId: containers.organizationId,\n })\n .from(containers)\n .where(inArray(containers.id, [...input.containerIds]));\n await retainDormantContainerMetadataInTransaction(\n input.tx,\n retainedContainers.flatMap((container) =>\n container.containerId\n ? [\n {\n containerId: container.containerId,\n organizationId: container.organizationId,\n retainedAt: input.retainedAt,\n },\n ]\n : [],\n ),\n );\n}\n\nasync function applyContainerRemovals(input: {\n discoveryOnly: DeleteContainerOptions[\"discoveryOnly\"];\n metadataDeleteIds: ReadonlyArray<string>;\n removals: ReadonlyArray<ContainerRemoval>;\n retainedMetadataIds: ReadonlyArray<string>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<ReadonlyArray<string>> {\n const containerIds = input.removals.map((removal) => removal.containerId);\n await recordContainerHydrationTombstones({\n removals: input.removals,\n tx: input.tx,\n });\n await retainRemovedContainerMetadata({\n containerIds: input.retainedMetadataIds,\n retainedAt: new Date().toISOString(),\n tx: input.tx,\n });\n // Listing clocks are untrusted; they cannot advance local projection clocks.\n const localObservedAt = new Date().toISOString();\n await repairDocumentsForRemovedContainersInTransaction({\n removals: input.discoveryOnly\n ? input.removals.map((removal) => ({\n ...removal,\n updatedAt: localObservedAt,\n }))\n : input.removals,\n tx: input.tx,\n });\n if (input.discoveryOnly) {\n await refreshPlacementIntentsAfterRemoval({ containerIds, tx: input.tx });\n } else {\n await repairLinkIntentsForRemovedContainers({ containerIds, tx: input.tx });\n await input.tx\n .delete(containerCreateIntents)\n .where(inArray(containerCreateIntents.containerId, containerIds))\n .run();\n await input.tx\n .delete(containerMoveIntents)\n .where(inArray(containerMoveIntents.containerId, containerIds))\n .run();\n }\n await deleteContainerRowsInTransaction(input.tx, containerIds);\n await deleteContainerMetadataDocumentRowsInTransaction(\n input.tx,\n input.metadataDeleteIds,\n );\n await deleteContainerWatermarksInTransaction(input.tx, containerIds);\n return containerIds;\n}\n\nexport async function deleteStoredContainers(\n execSql: Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[0],\n removals: Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[1],\n options?: Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[2],\n): Promise<ReadonlyArray<string>> {\n const uniqueRemovals = normalizeContainerRemovals(removals);\n if (uniqueRemovals.length === 0) return [];\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureSqlTables(lockedExecSql, documentContainerProjectionTables);\n await ensureSqlTables(lockedExecSql, documentMoveIntentTables);\n await ensureContainerTables(lockedExecSql);\n await ensureSqlTables(lockedExecSql, containerCreateIntentTables);\n await ensureSqlTables(lockedExecSql, containerMoveIntentTables);\n await ensureDocumentProjectionTables(lockedExecSql);\n await sqlContainerSyncWatermarkPersistence.ensureSchema(lockedExecSql);\n const runtime = getClientSQLitePersistenceRuntime(lockedExecSql);\n const remove = async (tx: ClientSQLiteTransactionScope) => {\n if (\n !(await canApplyContainerRemovals({\n expectedContainers: options?.expectedContainers,\n removals: uniqueRemovals,\n tx,\n }))\n ) {\n return [];\n }\n const retainedMetadataIds = options?.discoveryOnly\n ? await containerIdsWithRemovalWork(\n tx,\n uniqueRemovals.map((removal) => removal.containerId),\n )\n : [];\n return applyContainerRemovals({\n discoveryOnly: options?.discoveryOnly,\n metadataDeleteIds: uniqueRemovals.flatMap((removal) =>\n retainedMetadataIds.includes(removal.containerId)\n ? []\n : [removal.containerId],\n ),\n removals: uniqueRemovals,\n retainedMetadataIds,\n tx,\n });\n };\n if (!options?.stillCurrent) {\n return runtime.transaction(remove, { behavior: \"immediate\" });\n }\n const outcome = await runtime.guardedTransaction(\n remove,\n options.stillCurrent,\n { behavior: \"immediate\" },\n );\n return outcome.result ?? [];\n });\n}\n"]}
1
+ {"version":3,"file":"containerDeletionPersistence.js","sourceRoot":"","sources":["containerDeletionPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { inArray } from \"drizzle-orm\";\nimport { ensureDocumentProjectionTables } from \"../../sqlite/documentPersistence\";\nimport {\n containerCreateIntents,\n containerCreateIntentTables,\n containerMoveIntents,\n containerMoveIntentTables,\n containers,\n documentContainerProjectionTables,\n documentMoveIntentTables,\n} from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport {\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../sqlite/sqlSchema\";\nimport {\n deleteContainerRowsInTransaction,\n ensureContainerTables,\n} from \"../containers/containerPersistence\";\nimport {\n deleteContainerWatermarksInTransaction,\n sqlContainerSyncWatermarkPersistence,\n} from \"../containers/containerSyncWatermarkPersistence\";\nimport type {\n ContainerContentsPersistence,\n ContainerDeletionGuard,\n ContainerRemoval,\n} from \"./containerContentsPersistenceTypes\";\nimport { recordContainerHydrationTombstones } from \"./containerHydrationPersistence\";\nimport { containerIdsWithRemovalWork } from \"./containerRemovalWork\";\nimport { repairDocumentsForRemovedContainersInTransaction } from \"./containerStructuralRepair\";\nimport {\n refreshPlacementIntentsAfterRemoval,\n repairLinkIntentsForRemovedContainers,\n} from \"./documentLinkRemovalRepair\";\nimport {\n deleteContainerMetadataDocumentRowsInTransaction,\n retainDormantContainerMetadataInTransaction,\n} from \"./dormantContainerMetadata\";\n\ntype DeleteContainerOptions = NonNullable<\n Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[2]\n>;\n\nfunction normalizeContainerRemovals(\n removals: ReadonlyArray<ContainerRemoval>,\n): ContainerRemoval[] {\n const removalByContainerId = new Map<string, ContainerRemoval>();\n for (const removal of removals) {\n const current = removalByContainerId.get(removal.containerId);\n if (\n !current ||\n (current.reason === \"access_revoked\" && removal.reason === \"deleted\") ||\n (current.reason === removal.reason &&\n current.updatedAt < removal.updatedAt)\n ) {\n removalByContainerId.set(removal.containerId, removal);\n }\n }\n return Array.from(removalByContainerId.values());\n}\n\nfunction loadGuardedContainerRows(\n tx: ClientSQLiteTransactionScope,\n containerIds: ReadonlyArray<string>,\n) {\n return tx\n .select({\n effectiveAccessLevel: containers.effectiveAccessLevel,\n id: containers.id,\n metadataDocumentId: containers.metadataDocumentId,\n organizationId: containers.organizationId,\n parentId: containers.parentId,\n serverCreatedAt: containers.serverCreatedAt,\n serverUpdatedAt: containers.serverUpdatedAt,\n systemSlot: containers.systemSlot,\n })\n .from(containers)\n .where(inArray(containers.id, [...containerIds]));\n}\n\ntype GuardedContainerRow = Awaited<\n ReturnType<typeof loadGuardedContainerRows>\n>[number];\n\nfunction sameGuardedContainerRow(\n row: GuardedContainerRow | undefined,\n guard: ContainerDeletionGuard,\n): boolean {\n const expected = guard.expectedContainer;\n if (!row || !expected) return row === undefined && expected === null;\n return (\n row.id === expected.id &&\n row.organizationId === expected.organizationId &&\n row.parentId === expected.parentId &&\n row.metadataDocumentId === expected.metadataDocumentId &&\n row.systemSlot === (expected.systemSlot ?? null) &&\n row.effectiveAccessLevel === (expected.effectiveAccessLevel ?? \"read\") &&\n row.serverCreatedAt === (expected.serverCreatedAt ?? null) &&\n row.serverUpdatedAt === (expected.serverUpdatedAt ?? null)\n );\n}\n\nasync function canApplyContainerRemovals(input: {\n expectedContainers: DeleteContainerOptions[\"expectedContainers\"];\n removals: ReadonlyArray<ContainerRemoval>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<boolean> {\n const currentRows = await loadGuardedContainerRows(\n input.tx,\n input.removals.map((removal) => removal.containerId),\n );\n const currentRowById = new Map(\n currentRows.flatMap((row) => (row.id ? ([[row.id, row]] as const) : [])),\n );\n const guardById = new Map(\n input.expectedContainers?.map((guard) => [guard.containerId, guard]) ?? [],\n );\n return input.removals.every((removal) => {\n const currentRow = currentRowById.get(removal.containerId);\n const guard = guardById.get(removal.containerId);\n return (\n (!guard || sameGuardedContainerRow(currentRow, guard)) &&\n (currentRow?.serverUpdatedAt == null ||\n currentRow.serverUpdatedAt <= removal.updatedAt)\n );\n });\n}\n\nasync function retainRemovedContainerMetadata(input: {\n containerIds: ReadonlyArray<string>;\n retainedAt: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n if (input.containerIds.length === 0) return;\n const retainedContainers = await input.tx\n .select({\n containerId: containers.id,\n organizationId: containers.organizationId,\n })\n .from(containers)\n .where(inArray(containers.id, [...input.containerIds]));\n await retainDormantContainerMetadataInTransaction(\n input.tx,\n retainedContainers.flatMap((container) =>\n container.containerId\n ? [\n {\n containerId: container.containerId,\n organizationId: container.organizationId,\n retainedAt: input.retainedAt,\n },\n ]\n : [],\n ),\n );\n}\n\nasync function applyContainerRemovals(input: {\n discoveryOnly: DeleteContainerOptions[\"discoveryOnly\"];\n metadataDeleteIds: ReadonlyArray<string>;\n removals: ReadonlyArray<ContainerRemoval>;\n retainedMetadataIds: ReadonlyArray<string>;\n tx: ClientSQLiteTransactionScope;\n}): Promise<ReadonlyArray<string>> {\n const containerIds = input.removals.map((removal) => removal.containerId);\n await recordContainerHydrationTombstones({\n removals: input.removals,\n tx: input.tx,\n });\n await retainRemovedContainerMetadata({\n containerIds: input.retainedMetadataIds,\n retainedAt: new Date().toISOString(),\n tx: input.tx,\n });\n // Listing clocks are untrusted; they cannot advance local projection clocks.\n const localObservedAt = new Date().toISOString();\n await repairDocumentsForRemovedContainersInTransaction({\n removals: input.discoveryOnly\n ? input.removals.map((removal) => ({\n ...removal,\n updatedAt: localObservedAt,\n }))\n : input.removals,\n tx: input.tx,\n });\n if (input.discoveryOnly) {\n await refreshPlacementIntentsAfterRemoval({ containerIds, tx: input.tx });\n } else {\n await repairLinkIntentsForRemovedContainers({ containerIds, tx: input.tx });\n await input.tx\n .delete(containerCreateIntents)\n .where(inArray(containerCreateIntents.containerId, containerIds))\n .run();\n await input.tx\n .delete(containerMoveIntents)\n .where(inArray(containerMoveIntents.containerId, containerIds))\n .run();\n }\n await deleteContainerRowsInTransaction(input.tx, containerIds);\n await deleteContainerMetadataDocumentRowsInTransaction(\n input.tx,\n input.metadataDeleteIds,\n );\n await deleteContainerWatermarksInTransaction(input.tx, containerIds);\n return containerIds;\n}\n\nexport async function deleteStoredContainers(\n execSql: Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[0],\n removals: Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[1],\n options?: Parameters<ContainerContentsPersistence[\"deleteContainers\"]>[2],\n): Promise<ReadonlyArray<string>> {\n const uniqueRemovals = normalizeContainerRemovals(removals);\n if (uniqueRemovals.length === 0) return [];\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureSqlTables(lockedExecSql, documentContainerProjectionTables);\n await ensureSqlTables(lockedExecSql, documentMoveIntentTables);\n await ensureContainerTables(lockedExecSql);\n await ensureSqlTables(lockedExecSql, containerCreateIntentTables);\n await ensureSqlTables(lockedExecSql, containerMoveIntentTables);\n await ensureDocumentProjectionTables(lockedExecSql);\n await sqlContainerSyncWatermarkPersistence.ensureSchema(lockedExecSql);\n const runtime = getClientSQLitePersistenceRuntime(lockedExecSql);\n const remove = async (tx: ClientSQLiteTransactionScope) => {\n if (\n options?.beforeDeleteInTransaction &&\n !(await options.beforeDeleteInTransaction(lockedExecSql))\n )\n return [];\n if (\n !(await canApplyContainerRemovals({\n expectedContainers: options?.expectedContainers,\n removals: uniqueRemovals,\n tx,\n }))\n ) {\n return [];\n }\n const retainedMetadataIds = options?.discoveryOnly\n ? await containerIdsWithRemovalWork(\n tx,\n uniqueRemovals.map((removal) => removal.containerId),\n )\n : [];\n return applyContainerRemovals({\n discoveryOnly: options?.discoveryOnly,\n metadataDeleteIds: uniqueRemovals.flatMap((removal) =>\n retainedMetadataIds.includes(removal.containerId)\n ? []\n : [removal.containerId],\n ),\n removals: uniqueRemovals,\n retainedMetadataIds,\n tx,\n });\n };\n if (!options?.stillCurrent) {\n return runtime.transaction(remove, { behavior: \"immediate\" });\n }\n const outcome = await runtime.guardedTransaction(\n remove,\n options.stillCurrent,\n { behavior: \"immediate\" },\n );\n return outcome.result ?? [];\n });\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import { eq } from "drizzle-orm";
2
+ import { documentMoveIntents, documentMoveIntentTables, } from "../../sqlite/schema.js";
3
+ import { getClientSQLitePersistenceRuntime } from "../../sqlite/sqlitePersistenceRuntime.js";
4
+ import { ensureSqlTables } from "../../sqlite/sqlSchema.js";
5
+ /** Even denied or unavailable placement intent belongs to the local user. */
6
+ export async function hasUnsettledDocumentPlacement(execSql, localId) {
7
+ await ensureSqlTables(execSql, documentMoveIntentTables);
8
+ const [intent] = await getClientSQLitePersistenceRuntime(execSql)
9
+ .db.select({ id: documentMoveIntents.id })
10
+ .from(documentMoveIntents)
11
+ .where(eq(documentMoveIntents.localId, localId))
12
+ .limit(1);
13
+ return intent !== undefined;
14
+ }
15
+ //# sourceMappingURL=documentPurgePlacement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documentPurgePlacement.js","sourceRoot":"","sources":["documentPurgePlacement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACjC,OAAO,EACL,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iCAAiC,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,EAAgB,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEvE,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,OAAgB,EAChB,OAAe;IAEf,MAAM,eAAe,CAAC,OAAO,EAAE,wBAAwB,CAAC,CAAC;IACzD,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,iCAAiC,CAAC,OAAO,CAAC;SAC9D,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,mBAAmB,CAAC,EAAE,EAAE,CAAC;SACzC,IAAI,CAAC,mBAAmB,CAAC;SACzB,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;SAC/C,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,OAAO,MAAM,KAAK,SAAS,CAAC;AAC9B,CAAC","sourcesContent":["import { eq } from \"drizzle-orm\";\nimport {\n documentMoveIntents,\n documentMoveIntentTables,\n} from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\n\n/** Even denied or unavailable placement intent belongs to the local user. */\nexport async function hasUnsettledDocumentPlacement(\n execSql: ExecSql,\n localId: string,\n): Promise<boolean> {\n await ensureSqlTables(execSql, documentMoveIntentTables);\n const [intent] = await getClientSQLitePersistenceRuntime(execSql)\n .db.select({ id: documentMoveIntents.id })\n .from(documentMoveIntents)\n .where(eq(documentMoveIntents.localId, localId))\n .limit(1);\n return intent !== undefined;\n}\n"]}
@@ -7,6 +7,9 @@ export async function applyStoredAttachmentRemoval(input) {
7
7
  const pendingMatch = and(eq(documentPendingAttachments.localId, localId), eq(documentPendingAttachments.slotId, removal.slotId), eq(documentPendingAttachments.storageKey, removal.storageKey));
8
8
  const localMatch = and(eq(documentAttachmentBlobProjection.localId, localId), eq(documentAttachmentBlobProjection.slotId, removal.slotId), eq(documentAttachmentBlobProjection.storageKey, removal.storageKey));
9
9
  await tx.delete(documentPendingAttachments).where(pendingMatch).run();
10
+ // A pending-only slot leaves no detached marker to queue its bytes later.
11
+ // Reclaim keeps any key that a marker or another row still references.
12
+ await queueDocumentAttachmentStorageKeys(tx, [removal.storageKey]);
10
13
  if (removal.mode === "detach") {
11
14
  await tx
12
15
  .update(documentAttachmentBlobProjection)
@@ -15,7 +18,6 @@ export async function applyStoredAttachmentRemoval(input) {
15
18
  .run();
16
19
  return;
17
20
  }
18
- await queueDocumentAttachmentStorageKeys(tx, [removal.storageKey]);
19
21
  await tx.delete(documentAttachmentBlobProjection).where(localMatch).run();
20
22
  }
21
23
  function assertAttachmentScope(localId, staging) {
@@ -1 +1 @@
1
- {"version":3,"file":"attachmentStagingPersistence.js","sourceRoot":"","sources":["attachmentStagingPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, isNull } from \"drizzle-orm\";\nimport {\n documentAttachmentBlobProjection,\n documentPendingAttachments,\n} from \"../../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport type { DocumentsPersistence } from \"../types\";\nimport { buildPendingAttachmentRow } from \"./attachmentRows\";\nimport { queueDocumentAttachmentStorageKeys } from \"./orphanSideRows\";\n\ntype AttachmentStaging = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentStaging\"]\n>;\ntype AttachmentRemoval = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentRemoval\"]\n>;\n\nexport async function applyStoredAttachmentRemoval(input: {\n localId: string;\n removal: AttachmentRemoval;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { localId, removal, tx } = input;\n const pendingMatch = and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, removal.slotId),\n eq(documentPendingAttachments.storageKey, removal.storageKey),\n );\n const localMatch = and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, removal.slotId),\n eq(documentAttachmentBlobProjection.storageKey, removal.storageKey),\n );\n await tx.delete(documentPendingAttachments).where(pendingMatch).run();\n if (removal.mode === \"detach\") {\n await tx\n .update(documentAttachmentBlobProjection)\n .set({ detachedAt: new Date().toISOString() })\n .where(\n and(localMatch, isNull(documentAttachmentBlobProjection.detachedAt)),\n )\n .run();\n return;\n }\n await queueDocumentAttachmentStorageKeys(tx, [removal.storageKey]);\n await tx.delete(documentAttachmentBlobProjection).where(localMatch).run();\n}\n\nfunction assertAttachmentScope(\n localId: string,\n staging: AttachmentStaging,\n): void {\n const everyAttachmentMatchesScope = [\n ...staging.localAttachments,\n ...staging.pendingAttachments,\n ].every((attachment) => attachment.localId === localId);\n if (!everyAttachmentMatchesScope) {\n throw new Error(\"Attachment staging rows must share one document scope\");\n }\n}\n\nexport async function upsertStoredAttachmentStagingRows(input: {\n createdAt: string;\n localId: string;\n staging: AttachmentStaging;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { createdAt, localId, staging, tx } = input;\n assertAttachmentScope(localId, staging);\n\n for (const attachment of staging.pendingAttachments) {\n const row = buildPendingAttachmentRow(attachment, createdAt);\n const [existing] = await tx\n .select({ storageKey: documentPendingAttachments.storageKey })\n .from(documentPendingAttachments)\n .where(\n and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, attachment.slotId),\n ),\n )\n .limit(1);\n if (existing && existing.storageKey !== row.storageKey) {\n await queueDocumentAttachmentStorageKeys(tx, [existing.storageKey]);\n }\n await tx\n .insert(documentPendingAttachments)\n .values(row)\n .onConflictDoUpdate({\n target: [\n documentPendingAttachments.localId,\n documentPendingAttachments.slotId,\n ],\n set: {\n byteLength: row.byteLength,\n contentSha256: row.contentSha256,\n mimeType: row.mimeType,\n name: row.name,\n storageKey: row.storageKey,\n uploadBlobId: row.uploadBlobId,\n uploadContentKey: row.uploadContentKey,\n uploadContentKeyEpoch: row.uploadContentKeyEpoch,\n uploadIv: row.uploadIv,\n uploadPartSize: row.uploadPartSize,\n uploadPlaintextSha256: row.uploadPlaintextSha256,\n uploadStageId: row.uploadStageId,\n },\n })\n .run();\n }\n\n for (const attachment of staging.localAttachments) {\n const row = { ...attachment, updatedAt: createdAt };\n const [existing] = await tx\n .select({ storageKey: documentAttachmentBlobProjection.storageKey })\n .from(documentAttachmentBlobProjection)\n .where(\n and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, attachment.slotId),\n ),\n )\n .limit(1);\n if (existing && existing.storageKey !== row.storageKey) {\n await queueDocumentAttachmentStorageKeys(tx, [existing.storageKey]);\n }\n await tx\n .insert(documentAttachmentBlobProjection)\n .values(row)\n .onConflictDoUpdate({\n target: [\n documentAttachmentBlobProjection.localId,\n documentAttachmentBlobProjection.slotId,\n ],\n set: {\n blobId: row.blobId,\n byteLength: row.byteLength,\n contentSha256: row.contentSha256,\n detachedAt: row.detachedAt,\n mimeType: row.mimeType,\n storageKey: row.storageKey,\n updatedAt: row.updatedAt,\n },\n })\n .run();\n }\n}\n"]}
1
+ {"version":3,"file":"attachmentStagingPersistence.js","sourceRoot":"","sources":["attachmentStagingPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, isNull } from \"drizzle-orm\";\nimport {\n documentAttachmentBlobProjection,\n documentPendingAttachments,\n} from \"../../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport type { DocumentsPersistence } from \"../types\";\nimport { buildPendingAttachmentRow } from \"./attachmentRows\";\nimport { queueDocumentAttachmentStorageKeys } from \"./orphanSideRows\";\n\ntype AttachmentStaging = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentStaging\"]\n>;\ntype AttachmentRemoval = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentRemoval\"]\n>;\n\nexport async function applyStoredAttachmentRemoval(input: {\n localId: string;\n removal: AttachmentRemoval;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { localId, removal, tx } = input;\n const pendingMatch = and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, removal.slotId),\n eq(documentPendingAttachments.storageKey, removal.storageKey),\n );\n const localMatch = and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, removal.slotId),\n eq(documentAttachmentBlobProjection.storageKey, removal.storageKey),\n );\n await tx.delete(documentPendingAttachments).where(pendingMatch).run();\n // A pending-only slot leaves no detached marker to queue its bytes later.\n // Reclaim keeps any key that a marker or another row still references.\n await queueDocumentAttachmentStorageKeys(tx, [removal.storageKey]);\n if (removal.mode === \"detach\") {\n await tx\n .update(documentAttachmentBlobProjection)\n .set({ detachedAt: new Date().toISOString() })\n .where(\n and(localMatch, isNull(documentAttachmentBlobProjection.detachedAt)),\n )\n .run();\n return;\n }\n await tx.delete(documentAttachmentBlobProjection).where(localMatch).run();\n}\n\nfunction assertAttachmentScope(\n localId: string,\n staging: AttachmentStaging,\n): void {\n const everyAttachmentMatchesScope = [\n ...staging.localAttachments,\n ...staging.pendingAttachments,\n ].every((attachment) => attachment.localId === localId);\n if (!everyAttachmentMatchesScope) {\n throw new Error(\"Attachment staging rows must share one document scope\");\n }\n}\n\nexport async function upsertStoredAttachmentStagingRows(input: {\n createdAt: string;\n localId: string;\n staging: AttachmentStaging;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { createdAt, localId, staging, tx } = input;\n assertAttachmentScope(localId, staging);\n\n for (const attachment of staging.pendingAttachments) {\n const row = buildPendingAttachmentRow(attachment, createdAt);\n const [existing] = await tx\n .select({ storageKey: documentPendingAttachments.storageKey })\n .from(documentPendingAttachments)\n .where(\n and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, attachment.slotId),\n ),\n )\n .limit(1);\n if (existing && existing.storageKey !== row.storageKey) {\n await queueDocumentAttachmentStorageKeys(tx, [existing.storageKey]);\n }\n await tx\n .insert(documentPendingAttachments)\n .values(row)\n .onConflictDoUpdate({\n target: [\n documentPendingAttachments.localId,\n documentPendingAttachments.slotId,\n ],\n set: {\n byteLength: row.byteLength,\n contentSha256: row.contentSha256,\n mimeType: row.mimeType,\n name: row.name,\n storageKey: row.storageKey,\n uploadBlobId: row.uploadBlobId,\n uploadContentKey: row.uploadContentKey,\n uploadContentKeyEpoch: row.uploadContentKeyEpoch,\n uploadIv: row.uploadIv,\n uploadPartSize: row.uploadPartSize,\n uploadPlaintextSha256: row.uploadPlaintextSha256,\n uploadStageId: row.uploadStageId,\n },\n })\n .run();\n }\n\n for (const attachment of staging.localAttachments) {\n const row = { ...attachment, updatedAt: createdAt };\n const [existing] = await tx\n .select({ storageKey: documentAttachmentBlobProjection.storageKey })\n .from(documentAttachmentBlobProjection)\n .where(\n and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, attachment.slotId),\n ),\n )\n .limit(1);\n if (existing && existing.storageKey !== row.storageKey) {\n await queueDocumentAttachmentStorageKeys(tx, [existing.storageKey]);\n }\n await tx\n .insert(documentAttachmentBlobProjection)\n .values(row)\n .onConflictDoUpdate({\n target: [\n documentAttachmentBlobProjection.localId,\n documentAttachmentBlobProjection.slotId,\n ],\n set: {\n blobId: row.blobId,\n byteLength: row.byteLength,\n contentSha256: row.contentSha256,\n detachedAt: row.detachedAt,\n mimeType: row.mimeType,\n storageKey: row.storageKey,\n updatedAt: row.updatedAt,\n },\n })\n .run();\n }\n}\n"]}
@@ -101,6 +101,13 @@ export async function deleteStoredDocumentIfMatches(execSql, expectedRecord, del
101
101
  !sameCanonicalDocumentSecurityIdentity(existingDocument, expectedRecord)) {
102
102
  return false;
103
103
  }
104
+ const [placement] = await tx
105
+ .select({ containerId: documentProjection.containerId })
106
+ .from(documentProjection)
107
+ .where(eq(documentProjection.localId, expectedRecord.id))
108
+ .limit(1);
109
+ if ((placement?.containerId ?? null) !== expectedRecord.containerId)
110
+ return false;
104
111
  if (existingDocument.documentId &&
105
112
  (await hasAnotherDocumentAlias({
106
113
  documentId: existingDocument.documentId,
@@ -1 +1 @@
1
- {"version":3,"file":"documentDeletionPersistence.js","sourceRoot":"","sources":["documentDeletionPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, inArray, ne } from \"drizzle-orm\";\nimport { deleteDocumentHistory } from \"../../../sqlite/documentHistoryPersistence\";\nimport {\n clearDocumentSyncFailure,\n deleteDocumentPendingUpdates,\n deleteDocumentRecord,\n loadDocumentRecord,\n} from \"../../../sqlite/documentPersistence\";\nimport { documentIntentLinkTargets } from \"../../../sqlite/documentPlacementIntentSchema\";\nimport {\n documentAttachmentBlobProjection,\n documentContainerProjection,\n documentMoveIntents,\n documentMoveIntentTables,\n documentPendingAttachments,\n documentProjection,\n documentProjectionText,\n documents,\n} from \"../../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../../sqlite/sqlSchema\";\nimport type { StoredDocumentRecord } from \"../types\";\nimport { DOCUMENTS_APP_KIND } from \"./constants\";\nimport { deleteContainerDocumentTombstoneHoldsForDocuments } from \"./containerDocumentTombstoneHolds\";\nimport { sameCanonicalDocumentSecurityIdentity } from \"./documentRecordIdentity\";\nimport { getDocumentScope } from \"./documentRows\";\nimport { queueDocumentAttachmentBlobReclaims } from \"./orphanSideRows\";\n\nasync function deleteStoredDocumentRows(input: {\n existingDocumentId: string | null;\n localId: string;\n lockedExecSql: ExecSql;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { existingDocumentId, localId, lockedExecSql, tx } = input;\n await queueDocumentAttachmentBlobReclaims({\n localWhere: eq(documentAttachmentBlobProjection.localId, localId),\n pendingWhere: eq(documentPendingAttachments.localId, localId),\n tx,\n });\n await tx\n .delete(documentProjection)\n .where(eq(documentProjection.localId, localId))\n .run();\n await tx\n .delete(documentProjectionText)\n .where(eq(documentProjectionText.localId, localId))\n .run();\n await tx\n .delete(documentPendingAttachments)\n .where(eq(documentPendingAttachments.localId, localId))\n .run();\n await tx\n .delete(documentAttachmentBlobProjection)\n .where(eq(documentAttachmentBlobProjection.localId, localId))\n .run();\n if (existingDocumentId) {\n await tx\n .delete(documentContainerProjection)\n .where(eq(documentContainerProjection.documentId, existingDocumentId))\n .run();\n await deleteContainerDocumentTombstoneHoldsForDocuments(tx, [\n existingDocumentId,\n ]);\n await tx\n .delete(documentIntentLinkTargets)\n .where(\n inArray(\n documentIntentLinkTargets.intentId,\n tx\n .select({ id: documentMoveIntents.id })\n .from(documentMoveIntents)\n .where(eq(documentMoveIntents.documentId, existingDocumentId)),\n ),\n )\n .run();\n await tx\n .delete(documentMoveIntents)\n .where(eq(documentMoveIntents.documentId, existingDocumentId))\n .run();\n }\n const scope = getDocumentScope(localId);\n await deleteDocumentPendingUpdates(lockedExecSql, scope);\n await deleteDocumentHistory(lockedExecSql, scope);\n await clearDocumentSyncFailure(lockedExecSql, scope);\n await deleteDocumentRecord(lockedExecSql, scope);\n}\n\nasync function ensureDocumentDeletionTables(execSql: ExecSql): Promise<void> {\n // Move intents live in the container-contents schema, which document-only\n // callers may not have initialized yet.\n await ensureSqlTables(execSql, documentMoveIntentTables);\n}\n\nasync function hasAnotherDocumentAlias(input: {\n documentId: string;\n localId: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<boolean> {\n const rows = await input.tx\n .select({ localId: documents.localId })\n .from(documents)\n .where(\n and(\n eq(documents.appKind, DOCUMENTS_APP_KIND),\n eq(documents.documentId, input.documentId),\n ne(documents.localId, input.localId),\n ),\n )\n .limit(1);\n return rows.length > 0;\n}\n\nexport async function deleteStoredDocument(\n execSql: ExecSql,\n localId: string,\n): Promise<void> {\n await runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureDocumentDeletionTables(lockedExecSql);\n await getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const existingDocument = await loadDocumentRecord(lockedExecSql, {\n appKind: DOCUMENTS_APP_KIND,\n localId,\n });\n await deleteStoredDocumentRows({\n existingDocumentId: existingDocument?.documentId ?? null,\n localId,\n lockedExecSql,\n tx,\n });\n },\n { behavior: \"immediate\" },\n );\n });\n}\n\nexport async function deleteStoredDocumentIfMatches(\n execSql: ExecSql,\n expectedRecord: StoredDocumentRecord,\n deleteClientProjection: (transactionExecSql: ExecSql) => Promise<void>,\n): Promise<boolean> {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureDocumentDeletionTables(lockedExecSql);\n return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const existingDocument = await loadDocumentRecord(lockedExecSql, {\n appKind: DOCUMENTS_APP_KIND,\n localId: expectedRecord.id,\n });\n if (\n !existingDocument ||\n !sameCanonicalDocumentSecurityIdentity(\n existingDocument,\n expectedRecord,\n )\n ) {\n return false;\n }\n if (\n existingDocument.documentId &&\n (await hasAnotherDocumentAlias({\n documentId: existingDocument.documentId,\n localId: expectedRecord.id,\n tx,\n }))\n ) {\n return false;\n }\n await deleteStoredDocumentRows({\n existingDocumentId: existingDocument.documentId ?? null,\n localId: expectedRecord.id,\n lockedExecSql,\n tx,\n });\n await deleteClientProjection(lockedExecSql);\n return true;\n },\n { behavior: \"immediate\" },\n );\n });\n}\n\nexport async function deleteStoredDocumentSideRowsIfAbsent(\n execSql: ExecSql,\n localId: string,\n expectedDocumentId: string | null,\n deleteClientProjection: (transactionExecSql: ExecSql) => Promise<void>,\n): Promise<boolean> {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureDocumentDeletionTables(lockedExecSql);\n return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const existingDocument = await loadDocumentRecord(lockedExecSql, {\n appKind: DOCUMENTS_APP_KIND,\n localId,\n });\n if (existingDocument) return false;\n if (\n expectedDocumentId &&\n (await hasAnotherDocumentAlias({\n documentId: expectedDocumentId,\n localId,\n tx,\n }))\n ) {\n return false;\n }\n\n await deleteStoredDocumentRows({\n existingDocumentId: expectedDocumentId,\n localId,\n lockedExecSql,\n tx,\n });\n await deleteClientProjection(lockedExecSql);\n return true;\n },\n { behavior: \"immediate\" },\n );\n });\n}\n"]}
1
+ {"version":3,"file":"documentDeletionPersistence.js","sourceRoot":"","sources":["documentDeletionPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, inArray, ne } from \"drizzle-orm\";\nimport { deleteDocumentHistory } from \"../../../sqlite/documentHistoryPersistence\";\nimport {\n clearDocumentSyncFailure,\n deleteDocumentPendingUpdates,\n deleteDocumentRecord,\n loadDocumentRecord,\n} from \"../../../sqlite/documentPersistence\";\nimport { documentIntentLinkTargets } from \"../../../sqlite/documentPlacementIntentSchema\";\nimport {\n documentAttachmentBlobProjection,\n documentContainerProjection,\n documentMoveIntents,\n documentMoveIntentTables,\n documentPendingAttachments,\n documentProjection,\n documentProjectionText,\n documents,\n} from \"../../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport {\n type ExecSql,\n ensureSqlTables,\n runSerializedSqlMutation,\n} from \"../../../sqlite/sqlSchema\";\nimport type { StoredDocumentRecord } from \"../types\";\nimport { DOCUMENTS_APP_KIND } from \"./constants\";\nimport { deleteContainerDocumentTombstoneHoldsForDocuments } from \"./containerDocumentTombstoneHolds\";\nimport { sameCanonicalDocumentSecurityIdentity } from \"./documentRecordIdentity\";\nimport { getDocumentScope } from \"./documentRows\";\nimport { queueDocumentAttachmentBlobReclaims } from \"./orphanSideRows\";\n\nasync function deleteStoredDocumentRows(input: {\n existingDocumentId: string | null;\n localId: string;\n lockedExecSql: ExecSql;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { existingDocumentId, localId, lockedExecSql, tx } = input;\n await queueDocumentAttachmentBlobReclaims({\n localWhere: eq(documentAttachmentBlobProjection.localId, localId),\n pendingWhere: eq(documentPendingAttachments.localId, localId),\n tx,\n });\n await tx\n .delete(documentProjection)\n .where(eq(documentProjection.localId, localId))\n .run();\n await tx\n .delete(documentProjectionText)\n .where(eq(documentProjectionText.localId, localId))\n .run();\n await tx\n .delete(documentPendingAttachments)\n .where(eq(documentPendingAttachments.localId, localId))\n .run();\n await tx\n .delete(documentAttachmentBlobProjection)\n .where(eq(documentAttachmentBlobProjection.localId, localId))\n .run();\n if (existingDocumentId) {\n await tx\n .delete(documentContainerProjection)\n .where(eq(documentContainerProjection.documentId, existingDocumentId))\n .run();\n await deleteContainerDocumentTombstoneHoldsForDocuments(tx, [\n existingDocumentId,\n ]);\n await tx\n .delete(documentIntentLinkTargets)\n .where(\n inArray(\n documentIntentLinkTargets.intentId,\n tx\n .select({ id: documentMoveIntents.id })\n .from(documentMoveIntents)\n .where(eq(documentMoveIntents.documentId, existingDocumentId)),\n ),\n )\n .run();\n await tx\n .delete(documentMoveIntents)\n .where(eq(documentMoveIntents.documentId, existingDocumentId))\n .run();\n }\n const scope = getDocumentScope(localId);\n await deleteDocumentPendingUpdates(lockedExecSql, scope);\n await deleteDocumentHistory(lockedExecSql, scope);\n await clearDocumentSyncFailure(lockedExecSql, scope);\n await deleteDocumentRecord(lockedExecSql, scope);\n}\n\nasync function ensureDocumentDeletionTables(execSql: ExecSql): Promise<void> {\n // Move intents live in the container-contents schema, which document-only\n // callers may not have initialized yet.\n await ensureSqlTables(execSql, documentMoveIntentTables);\n}\n\nasync function hasAnotherDocumentAlias(input: {\n documentId: string;\n localId: string;\n tx: ClientSQLiteTransactionScope;\n}): Promise<boolean> {\n const rows = await input.tx\n .select({ localId: documents.localId })\n .from(documents)\n .where(\n and(\n eq(documents.appKind, DOCUMENTS_APP_KIND),\n eq(documents.documentId, input.documentId),\n ne(documents.localId, input.localId),\n ),\n )\n .limit(1);\n return rows.length > 0;\n}\n\nexport async function deleteStoredDocument(\n execSql: ExecSql,\n localId: string,\n): Promise<void> {\n await runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureDocumentDeletionTables(lockedExecSql);\n await getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const existingDocument = await loadDocumentRecord(lockedExecSql, {\n appKind: DOCUMENTS_APP_KIND,\n localId,\n });\n await deleteStoredDocumentRows({\n existingDocumentId: existingDocument?.documentId ?? null,\n localId,\n lockedExecSql,\n tx,\n });\n },\n { behavior: \"immediate\" },\n );\n });\n}\n\nexport async function deleteStoredDocumentIfMatches(\n execSql: ExecSql,\n expectedRecord: StoredDocumentRecord,\n deleteClientProjection: (transactionExecSql: ExecSql) => Promise<void>,\n): Promise<boolean> {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureDocumentDeletionTables(lockedExecSql);\n return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const existingDocument = await loadDocumentRecord(lockedExecSql, {\n appKind: DOCUMENTS_APP_KIND,\n localId: expectedRecord.id,\n });\n if (\n !existingDocument ||\n !sameCanonicalDocumentSecurityIdentity(\n existingDocument,\n expectedRecord,\n )\n ) {\n return false;\n }\n const [placement] = await tx\n .select({ containerId: documentProjection.containerId })\n .from(documentProjection)\n .where(eq(documentProjection.localId, expectedRecord.id))\n .limit(1);\n if ((placement?.containerId ?? null) !== expectedRecord.containerId)\n return false;\n if (\n existingDocument.documentId &&\n (await hasAnotherDocumentAlias({\n documentId: existingDocument.documentId,\n localId: expectedRecord.id,\n tx,\n }))\n ) {\n return false;\n }\n await deleteStoredDocumentRows({\n existingDocumentId: existingDocument.documentId ?? null,\n localId: expectedRecord.id,\n lockedExecSql,\n tx,\n });\n await deleteClientProjection(lockedExecSql);\n return true;\n },\n { behavior: \"immediate\" },\n );\n });\n}\n\nexport async function deleteStoredDocumentSideRowsIfAbsent(\n execSql: ExecSql,\n localId: string,\n expectedDocumentId: string | null,\n deleteClientProjection: (transactionExecSql: ExecSql) => Promise<void>,\n): Promise<boolean> {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await ensureDocumentDeletionTables(lockedExecSql);\n return getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async (tx) => {\n const existingDocument = await loadDocumentRecord(lockedExecSql, {\n appKind: DOCUMENTS_APP_KIND,\n localId,\n });\n if (existingDocument) return false;\n if (\n expectedDocumentId &&\n (await hasAnotherDocumentAlias({\n documentId: expectedDocumentId,\n localId,\n tx,\n }))\n ) {\n return false;\n }\n\n await deleteStoredDocumentRows({\n existingDocumentId: expectedDocumentId,\n localId,\n lockedExecSql,\n tx,\n });\n await deleteClientProjection(lockedExecSql);\n return true;\n },\n { behavior: \"immediate\" },\n );\n });\n}\n"]}
@@ -117,7 +117,7 @@ export const documentSyncQueuePersistence = {
117
117
  });
118
118
  },
119
119
  async deleteLocalAttachment(execSql, localId, slotId, storageKey) {
120
- await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {
120
+ await getClientSQLitePersistenceRuntime(execSql).transaction(async (db) => {
121
121
  await db
122
122
  .delete(documentAttachmentBlobProjection)
123
123
  .where(and(eq(documentAttachmentBlobProjection.localId, localId), eq(documentAttachmentBlobProjection.slotId, slotId), eq(documentAttachmentBlobProjection.storageKey, storageKey)))
@@ -178,19 +178,22 @@ export const documentSyncQueuePersistence = {
178
178
  });
179
179
  },
180
180
  async deletePendingAttachment(execSql, localId, slotId, storageKey) {
181
- await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {
181
+ await getClientSQLitePersistenceRuntime(execSql).transaction(async (db) => {
182
182
  await db
183
183
  .delete(documentPendingAttachments)
184
184
  .where(and(eq(documentPendingAttachments.localId, localId), eq(documentPendingAttachments.slotId, slotId), eq(documentPendingAttachments.storageKey, storageKey)))
185
185
  .run();
186
+ // A dropped upload may hold the last reference to its staged bytes.
187
+ await queueDocumentAttachmentStorageKeys(db, [storageKey]);
186
188
  });
187
189
  },
188
190
  async deletePendingAttachments(execSql, localId) {
189
- await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {
190
- await db
191
+ await getClientSQLitePersistenceRuntime(execSql).transaction(async (db) => {
192
+ const deleted = await db
191
193
  .delete(documentPendingAttachments)
192
194
  .where(eq(documentPendingAttachments.localId, localId))
193
- .run();
195
+ .returning({ storageKey: documentPendingAttachments.storageKey });
196
+ await queueDocumentAttachmentStorageKeys(db, deleted.map(({ storageKey }) => storageKey));
194
197
  });
195
198
  },
196
199
  };