@tearleads/client-sdk 0.1.25 → 0.1.27

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 (47) 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/documents/internal/attachmentStagingPersistence.js +3 -1
  5. package/data/persistence/documents/internal/attachmentStagingPersistence.js.map +1 -1
  6. package/data/persistence/documents/internal/syncQueuePersistence.js +8 -5
  7. package/data/persistence/documents/internal/syncQueuePersistence.js.map +1 -1
  8. package/internal/validators/operation/blobBytes.d.ts +1 -1
  9. package/internal/validators/util/blob.d.ts +6 -1
  10. package/internal/validators/util/blob.js +7 -2
  11. package/internal/validators/util/blob.js.map +1 -1
  12. package/internal/validators/util/containerKekKeyringWire.d.ts +1 -1
  13. package/internal/validators/util/containerKekKeyringWire.js +1 -1
  14. package/internal/validators/util/containerKekKeyringWire.js.map +1 -1
  15. package/internal/validators/util/documentSyncLimits.js +6 -4
  16. package/internal/validators/util/documentSyncLimits.js.map +1 -1
  17. package/package.json +1 -1
  18. package/stores/documents/documentStore/attachmentStaging.js +24 -13
  19. package/stores/documents/documentStore/attachmentStaging.js.map +1 -1
  20. package/stores/documents/documentStore/attachments.js +6 -4
  21. package/stores/documents/documentStore/attachments.js.map +1 -1
  22. package/workflows/container-contents/container-state/purgeTree.js +18 -6
  23. package/workflows/container-contents/container-state/purgeTree.js.map +1 -1
  24. package/workflows/container-contents/container-state/purgeTreeContainerDeletion.js +6 -1
  25. package/workflows/container-contents/container-state/purgeTreeContainerDeletion.js.map +1 -1
  26. package/workflows/container-contents/container-state/purgeTreeScope.js +20 -3
  27. package/workflows/container-contents/container-state/purgeTreeScope.js.map +1 -1
  28. package/workflows/container-contents/documentLinks.d.ts +3 -2
  29. package/workflows/container-contents/documentLinks.js +1 -1
  30. package/workflows/container-contents/documentLinks.js.map +1 -1
  31. package/workflows/container-contents/documentPurge.d.ts +2 -1
  32. package/workflows/container-contents/documentPurge.js +1 -1
  33. package/workflows/container-contents/documentPurge.js.map +1 -1
  34. package/workflows/documents/linkSet.d.ts +2 -1
  35. package/workflows/documents/linkSet.js +2 -2
  36. package/workflows/documents/linkSet.js.map +1 -1
  37. package/workflows/documents/linkSetRemote.d.ts +2 -1
  38. package/workflows/documents/linkSetRemote.js +1 -1
  39. package/workflows/documents/linkSetRemote.js.map +1 -1
  40. package/workflows/documents/purge.d.ts +3 -2
  41. package/workflows/documents/purge.js +3 -3
  42. package/workflows/documents/purge.js.map +1 -1
  43. package/workflows/documents/subtreePurgeScope.d.ts +8 -0
  44. package/workflows/documents/subtreePurgeScope.js +12 -5
  45. package/workflows/documents/subtreePurgeScope.js.map +1 -1
  46. package/workflows/organizations/reconcileAcknowledgedPrincipalCurrent.js +22 -17
  47. package/workflows/organizations/reconcileAcknowledgedPrincipalCurrent.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"]}
@@ -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":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACL,gCAAgC,EAChC,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,kCAAkC,EAAE,MAAM,kBAAkB,CAAC;AAatE,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,KAIlD;IACC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC;IACvC,MAAM,YAAY,GAAG,GAAG,CACtB,EAAE,CAAC,0BAA0B,CAAC,OAAO,EAAE,OAAO,CAAC,EAC/C,EAAE,CAAC,0BAA0B,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,EACrD,EAAE,CAAC,0BAA0B,CAAC,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,CAC9D,CAAC;IACF,MAAM,UAAU,GAAG,GAAG,CACpB,EAAE,CAAC,gCAAgC,CAAC,OAAO,EAAE,OAAO,CAAC,EACrD,EAAE,CAAC,gCAAgC,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,EAC3D,EAAE,CAAC,gCAAgC,CAAC,UAAU,EAAE,OAAO,CAAC,UAAU,CAAC,CACpE,CAAC;IACF,MAAM,EAAE,CAAC,MAAM,CAAC,0BAA0B,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,CAAC;IACtE,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,EAAE;aACL,MAAM,CAAC,gCAAgC,CAAC;aACxC,GAAG,CAAC,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;aAC7C,KAAK,CACJ,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,gCAAgC,CAAC,UAAU,CAAC,CAAC,CACrE;aACA,GAAG,EAAE,CAAC;QACT,OAAO;IACT,CAAC;IACD,MAAM,kCAAkC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IACnE,MAAM,EAAE,CAAC,MAAM,CAAC,gCAAgC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,CAAC;AAC5E,CAAC;AAED,SAAS,qBAAqB,CAC5B,OAAe,EACf,OAA0B;IAE1B,MAAM,2BAA2B,GAAG;QAClC,GAAG,OAAO,CAAC,gBAAgB;QAC3B,GAAG,OAAO,CAAC,kBAAkB;KAC9B,CAAC,KAAK,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;IACxD,IAAI,CAAC,2BAA2B,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC3E,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iCAAiC,CAAC,KAKvD;IACC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC;IAClD,qBAAqB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAExC,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC;QACpD,MAAM,GAAG,GAAG,yBAAyB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAC7D,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE;aACxB,MAAM,CAAC,EAAE,UAAU,EAAE,0BAA0B,CAAC,UAAU,EAAE,CAAC;aAC7D,IAAI,CAAC,0BAA0B,CAAC;aAChC,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,0BAA0B,CAAC,OAAO,EAAE,OAAO,CAAC,EAC/C,EAAE,CAAC,0BAA0B,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CACzD,CACF;aACA,KAAK,CAAC,CAAC,CAAC,CAAC;QACZ,IAAI,QAAQ,IAAI,QAAQ,CAAC,UAAU,KAAK,GAAG,CAAC,UAAU,EAAE,CAAC;YACvD,MAAM,kCAAkC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;QACtE,CAAC;QACD,MAAM,EAAE;aACL,MAAM,CAAC,0BAA0B,CAAC;aAClC,MAAM,CAAC,GAAG,CAAC;aACX,kBAAkB,CAAC;YAClB,MAAM,EAAE;gBACN,0BAA0B,CAAC,OAAO;gBAClC,0BAA0B,CAAC,MAAM;aAClC;YACD,GAAG,EAAE;gBACH,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,aAAa,EAAE,GAAG,CAAC,aAAa;gBAChC,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,YAAY,EAAE,GAAG,CAAC,YAAY;gBAC9B,gBAAgB,EAAE,GAAG,CAAC,gBAAgB;gBACtC,qBAAqB,EAAE,GAAG,CAAC,qBAAqB;gBAChD,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,cAAc,EAAE,GAAG,CAAC,cAAc;gBAClC,qBAAqB,EAAE,GAAG,CAAC,qBAAqB;gBAChD,aAAa,EAAE,GAAG,CAAC,aAAa;aACjC;SACF,CAAC;aACD,GAAG,EAAE,CAAC;IACX,CAAC;IAED,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;QAClD,MAAM,GAAG,GAAG,EAAE,GAAG,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;QACpD,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE;aACxB,MAAM,CAAC,EAAE,UAAU,EAAE,gCAAgC,CAAC,UAAU,EAAE,CAAC;aACnE,IAAI,CAAC,gCAAgC,CAAC;aACtC,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,gCAAgC,CAAC,OAAO,EAAE,OAAO,CAAC,EACrD,EAAE,CAAC,gCAAgC,CAAC,MAAM,EAAE,UAAU,CAAC,MAAM,CAAC,CAC/D,CACF;aACA,KAAK,CAAC,CAAC,CAAC,CAAC;QACZ,IAAI,QAAQ,IAAI,QAAQ,CAAC,UAAU,KAAK,GAAG,CAAC,UAAU,EAAE,CAAC;YACvD,MAAM,kCAAkC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;QACtE,CAAC;QACD,MAAM,EAAE;aACL,MAAM,CAAC,gCAAgC,CAAC;aACxC,MAAM,CAAC,GAAG,CAAC;aACX,kBAAkB,CAAC;YAClB,MAAM,EAAE;gBACN,gCAAgC,CAAC,OAAO;gBACxC,gCAAgC,CAAC,MAAM;aACxC;YACD,GAAG,EAAE;gBACH,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,aAAa,EAAE,GAAG,CAAC,aAAa;gBAChC,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,QAAQ,EAAE,GAAG,CAAC,QAAQ;gBACtB,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,SAAS,EAAE,GAAG,CAAC,SAAS;aACzB;SACF,CAAC;aACD,GAAG,EAAE,CAAC;IACX,CAAC;AACH,CAAC","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"]}
@@ -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
  };
@@ -1 +1 @@
1
- {"version":3,"file":"syncQueuePersistence.js","sourceRoot":"","sources":["syncQueuePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, isNull } from \"drizzle-orm\";\nimport {\n appendDocumentHistoryUpdates,\n listDocumentHistoryTailEntries,\n loadDocumentHistoryRestoreState,\n readDocumentHistoryTailSize,\n replaceDocumentHistoryCheckpoint,\n} from \"../../../sqlite/documentHistoryPersistence\";\nimport {\n deleteDocumentPendingUpdate,\n deleteDocumentPendingUpdates,\n enqueueDocumentPendingUpdateWithHistory,\n listDocumentPendingUpdates,\n rekeyDocumentPendingUpdate,\n} from \"../../../sqlite/documentPersistence\";\nimport {\n documentAttachmentBlobProjection,\n documentPendingAttachments,\n} from \"../../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport { runSerializedSqlMutation } from \"../../../sqlite/sqlSchema\";\nimport type { DocumentsPersistence } from \"../types\";\nimport {\n buildPendingAttachmentRow,\n mapLocalAttachmentRecord,\n mapPendingAttachmentRecord,\n} from \"./attachmentRows\";\nimport { getDocumentScope } from \"./documentRows\";\nimport { queueDocumentAttachmentStorageKeys } from \"./orphanSideRows\";\n\ntype DocumentSyncQueuePersistence = Pick<\n DocumentsPersistence,\n | \"listPendingUpdates\"\n | \"rekeyPendingUpdate\"\n | \"listPendingAttachments\"\n | \"listLocalAttachments\"\n | \"appendHistoryUpdates\"\n | \"loadHistoryRestoreState\"\n | \"readHistoryTailSize\"\n | \"listHistoryTailEntries\"\n | \"replaceHistoryCheckpoint\"\n | \"enqueuePendingUpdate\"\n | \"saveLocalAttachment\"\n | \"deleteLocalAttachment\"\n | \"markLocalAttachmentDetached\"\n | \"savePendingAttachment\"\n | \"deletePendingUpdate\"\n | \"deletePendingUpdates\"\n | \"deletePendingAttachment\"\n | \"deletePendingAttachments\"\n>;\n\nexport const documentSyncQueuePersistence: DocumentSyncQueuePersistence = {\n async listPendingUpdates(execSql, localId) {\n return listDocumentPendingUpdates(execSql, getDocumentScope(localId));\n },\n async rekeyPendingUpdate(execSql, id) {\n return rekeyDocumentPendingUpdate(execSql, id);\n },\n async listPendingAttachments(execSql, localId) {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n localId: documentPendingAttachments.localId,\n slotId: documentPendingAttachments.slotId,\n name: documentPendingAttachments.name,\n mimeType: documentPendingAttachments.mimeType,\n storageKey: documentPendingAttachments.storageKey,\n byteLength: documentPendingAttachments.byteLength,\n contentSha256: documentPendingAttachments.contentSha256,\n uploadBlobId: documentPendingAttachments.uploadBlobId,\n uploadContentKey: documentPendingAttachments.uploadContentKey,\n uploadIv: documentPendingAttachments.uploadIv,\n uploadContentKeyEpoch: documentPendingAttachments.uploadContentKeyEpoch,\n uploadPartSize: documentPendingAttachments.uploadPartSize,\n uploadPlaintextSha256: documentPendingAttachments.uploadPlaintextSha256,\n uploadStageId: documentPendingAttachments.uploadStageId,\n })\n .from(documentPendingAttachments)\n .where(eq(documentPendingAttachments.localId, localId))\n .orderBy(\n documentPendingAttachments.createdAt,\n documentPendingAttachments.slotId,\n );\n\n return rows.map(mapPendingAttachmentRecord);\n },\n // Detached rows are included on purpose: they are the durable markers the\n // next sync uses to detach the remote binding, so a restart has to restore\n // them alongside the live slots.\n async listLocalAttachments(execSql, localId) {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n localId: documentAttachmentBlobProjection.localId,\n slotId: documentAttachmentBlobProjection.slotId,\n blobId: documentAttachmentBlobProjection.blobId,\n storageKey: documentAttachmentBlobProjection.storageKey,\n mimeType: documentAttachmentBlobProjection.mimeType,\n byteLength: documentAttachmentBlobProjection.byteLength,\n contentSha256: documentAttachmentBlobProjection.contentSha256,\n detachedAt: documentAttachmentBlobProjection.detachedAt,\n })\n .from(documentAttachmentBlobProjection)\n .where(eq(documentAttachmentBlobProjection.localId, localId));\n\n return rows.map(mapLocalAttachmentRecord);\n },\n async appendHistoryUpdates(execSql, input) {\n await appendDocumentHistoryUpdates(\n execSql,\n getDocumentScope(input.localId),\n input.updates,\n input.origin,\n );\n },\n async loadHistoryRestoreState(execSql, localId) {\n return loadDocumentHistoryRestoreState(execSql, getDocumentScope(localId));\n },\n async readHistoryTailSize(execSql, localId) {\n return readDocumentHistoryTailSize(execSql, getDocumentScope(localId));\n },\n async listHistoryTailEntries(execSql, localId) {\n return listDocumentHistoryTailEntries(execSql, getDocumentScope(localId));\n },\n async replaceHistoryCheckpoint(execSql, input) {\n await replaceDocumentHistoryCheckpoint(\n execSql,\n getDocumentScope(input.localId),\n {\n coveredTailIds: input.coveredTailIds,\n endVersionVector: input.endVersionVector,\n ...(input.force === undefined ? {} : { force: input.force }),\n snapshot: input.snapshot,\n ...(input.stillCurrent === undefined\n ? {}\n : { stillCurrent: input.stillCurrent }),\n },\n );\n },\n async enqueuePendingUpdate(execSql, pendingUpdate, options) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n const pendingUpdateId = await enqueueDocumentPendingUpdateWithHistory(\n lockedExecSql,\n getDocumentScope(pendingUpdate.localId),\n pendingUpdate,\n options,\n );\n return pendingUpdateId !== null;\n });\n },\n async saveLocalAttachment(execSql, attachment) {\n const updatedAt = new Date().toISOString();\n\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n const attachmentRow = {\n localId: attachment.localId,\n slotId: attachment.slotId,\n blobId: attachment.blobId,\n storageKey: attachment.storageKey,\n mimeType: attachment.mimeType,\n byteLength: attachment.byteLength,\n contentSha256: attachment.contentSha256,\n updatedAt,\n // Written on every save so re-filling a slot clears a stale detach\n // marker instead of inheriting it from the row it replaces.\n detachedAt: attachment.detachedAt,\n };\n await db\n .insert(documentAttachmentBlobProjection)\n .values(attachmentRow)\n .onConflictDoUpdate({\n target: [\n documentAttachmentBlobProjection.localId,\n documentAttachmentBlobProjection.slotId,\n ],\n set: attachmentRow,\n })\n .run();\n });\n },\n async deleteLocalAttachment(execSql, localId, slotId, storageKey) {\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n await db\n .delete(documentAttachmentBlobProjection)\n .where(\n and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, slotId),\n eq(documentAttachmentBlobProjection.storageKey, storageKey),\n ),\n )\n .run();\n // Another slot may hold the same hydrated copy; the reclaim deletes\n // the bytes only once no row references them.\n await queueDocumentAttachmentStorageKeys(db, [storageKey]);\n });\n },\n async markLocalAttachmentDetached(execSql, localId, slotId, storageKey) {\n const detachedAt = new Date().toISOString();\n\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n await db\n .update(documentAttachmentBlobProjection)\n .set({ detachedAt })\n .where(\n and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, slotId),\n eq(documentAttachmentBlobProjection.storageKey, storageKey),\n isNull(documentAttachmentBlobProjection.detachedAt),\n ),\n )\n .run();\n });\n },\n async savePendingAttachment(execSql, attachment) {\n const createdAt = new Date().toISOString();\n\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n const attachmentRow = buildPendingAttachmentRow(attachment, createdAt);\n await db\n .insert(documentPendingAttachments)\n .values(attachmentRow)\n .onConflictDoUpdate({\n target: [\n documentPendingAttachments.localId,\n documentPendingAttachments.slotId,\n ],\n set: {\n name: attachmentRow.name,\n mimeType: attachmentRow.mimeType,\n storageKey: attachmentRow.storageKey,\n byteLength: attachmentRow.byteLength,\n contentSha256: attachmentRow.contentSha256,\n uploadBlobId: attachmentRow.uploadBlobId,\n uploadContentKey: attachmentRow.uploadContentKey,\n uploadIv: attachmentRow.uploadIv,\n uploadContentKeyEpoch: attachmentRow.uploadContentKeyEpoch,\n uploadPartSize: attachmentRow.uploadPartSize,\n uploadPlaintextSha256: attachmentRow.uploadPlaintextSha256,\n uploadStageId: attachmentRow.uploadStageId,\n },\n })\n .run();\n });\n },\n async deletePendingUpdate(execSql, id) {\n await runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await deleteDocumentPendingUpdate(lockedExecSql, id);\n });\n },\n async deletePendingUpdates(execSql, localId) {\n await runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await deleteDocumentPendingUpdates(\n lockedExecSql,\n getDocumentScope(localId),\n );\n });\n },\n async deletePendingAttachment(execSql, localId, slotId, storageKey) {\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n await db\n .delete(documentPendingAttachments)\n .where(\n and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, slotId),\n eq(documentPendingAttachments.storageKey, storageKey),\n ),\n )\n .run();\n });\n },\n async deletePendingAttachments(execSql, localId) {\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n await db\n .delete(documentPendingAttachments)\n .where(eq(documentPendingAttachments.localId, localId))\n .run();\n });\n },\n};\n"]}
1
+ {"version":3,"file":"syncQueuePersistence.js","sourceRoot":"","sources":["syncQueuePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, isNull } from \"drizzle-orm\";\nimport {\n appendDocumentHistoryUpdates,\n listDocumentHistoryTailEntries,\n loadDocumentHistoryRestoreState,\n readDocumentHistoryTailSize,\n replaceDocumentHistoryCheckpoint,\n} from \"../../../sqlite/documentHistoryPersistence\";\nimport {\n deleteDocumentPendingUpdate,\n deleteDocumentPendingUpdates,\n enqueueDocumentPendingUpdateWithHistory,\n listDocumentPendingUpdates,\n rekeyDocumentPendingUpdate,\n} from \"../../../sqlite/documentPersistence\";\nimport {\n documentAttachmentBlobProjection,\n documentPendingAttachments,\n} from \"../../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport { runSerializedSqlMutation } from \"../../../sqlite/sqlSchema\";\nimport type { DocumentsPersistence } from \"../types\";\nimport {\n buildPendingAttachmentRow,\n mapLocalAttachmentRecord,\n mapPendingAttachmentRecord,\n} from \"./attachmentRows\";\nimport { getDocumentScope } from \"./documentRows\";\nimport { queueDocumentAttachmentStorageKeys } from \"./orphanSideRows\";\n\ntype DocumentSyncQueuePersistence = Pick<\n DocumentsPersistence,\n | \"listPendingUpdates\"\n | \"rekeyPendingUpdate\"\n | \"listPendingAttachments\"\n | \"listLocalAttachments\"\n | \"appendHistoryUpdates\"\n | \"loadHistoryRestoreState\"\n | \"readHistoryTailSize\"\n | \"listHistoryTailEntries\"\n | \"replaceHistoryCheckpoint\"\n | \"enqueuePendingUpdate\"\n | \"saveLocalAttachment\"\n | \"deleteLocalAttachment\"\n | \"markLocalAttachmentDetached\"\n | \"savePendingAttachment\"\n | \"deletePendingUpdate\"\n | \"deletePendingUpdates\"\n | \"deletePendingAttachment\"\n | \"deletePendingAttachments\"\n>;\n\nexport const documentSyncQueuePersistence: DocumentSyncQueuePersistence = {\n async listPendingUpdates(execSql, localId) {\n return listDocumentPendingUpdates(execSql, getDocumentScope(localId));\n },\n async rekeyPendingUpdate(execSql, id) {\n return rekeyDocumentPendingUpdate(execSql, id);\n },\n async listPendingAttachments(execSql, localId) {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n localId: documentPendingAttachments.localId,\n slotId: documentPendingAttachments.slotId,\n name: documentPendingAttachments.name,\n mimeType: documentPendingAttachments.mimeType,\n storageKey: documentPendingAttachments.storageKey,\n byteLength: documentPendingAttachments.byteLength,\n contentSha256: documentPendingAttachments.contentSha256,\n uploadBlobId: documentPendingAttachments.uploadBlobId,\n uploadContentKey: documentPendingAttachments.uploadContentKey,\n uploadIv: documentPendingAttachments.uploadIv,\n uploadContentKeyEpoch: documentPendingAttachments.uploadContentKeyEpoch,\n uploadPartSize: documentPendingAttachments.uploadPartSize,\n uploadPlaintextSha256: documentPendingAttachments.uploadPlaintextSha256,\n uploadStageId: documentPendingAttachments.uploadStageId,\n })\n .from(documentPendingAttachments)\n .where(eq(documentPendingAttachments.localId, localId))\n .orderBy(\n documentPendingAttachments.createdAt,\n documentPendingAttachments.slotId,\n );\n\n return rows.map(mapPendingAttachmentRecord);\n },\n // Detached rows are included on purpose: they are the durable markers the\n // next sync uses to detach the remote binding, so a restart has to restore\n // them alongside the live slots.\n async listLocalAttachments(execSql, localId) {\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const rows = await db\n .select({\n localId: documentAttachmentBlobProjection.localId,\n slotId: documentAttachmentBlobProjection.slotId,\n blobId: documentAttachmentBlobProjection.blobId,\n storageKey: documentAttachmentBlobProjection.storageKey,\n mimeType: documentAttachmentBlobProjection.mimeType,\n byteLength: documentAttachmentBlobProjection.byteLength,\n contentSha256: documentAttachmentBlobProjection.contentSha256,\n detachedAt: documentAttachmentBlobProjection.detachedAt,\n })\n .from(documentAttachmentBlobProjection)\n .where(eq(documentAttachmentBlobProjection.localId, localId));\n\n return rows.map(mapLocalAttachmentRecord);\n },\n async appendHistoryUpdates(execSql, input) {\n await appendDocumentHistoryUpdates(\n execSql,\n getDocumentScope(input.localId),\n input.updates,\n input.origin,\n );\n },\n async loadHistoryRestoreState(execSql, localId) {\n return loadDocumentHistoryRestoreState(execSql, getDocumentScope(localId));\n },\n async readHistoryTailSize(execSql, localId) {\n return readDocumentHistoryTailSize(execSql, getDocumentScope(localId));\n },\n async listHistoryTailEntries(execSql, localId) {\n return listDocumentHistoryTailEntries(execSql, getDocumentScope(localId));\n },\n async replaceHistoryCheckpoint(execSql, input) {\n await replaceDocumentHistoryCheckpoint(\n execSql,\n getDocumentScope(input.localId),\n {\n coveredTailIds: input.coveredTailIds,\n endVersionVector: input.endVersionVector,\n ...(input.force === undefined ? {} : { force: input.force }),\n snapshot: input.snapshot,\n ...(input.stillCurrent === undefined\n ? {}\n : { stillCurrent: input.stillCurrent }),\n },\n );\n },\n async enqueuePendingUpdate(execSql, pendingUpdate, options) {\n return runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n const pendingUpdateId = await enqueueDocumentPendingUpdateWithHistory(\n lockedExecSql,\n getDocumentScope(pendingUpdate.localId),\n pendingUpdate,\n options,\n );\n return pendingUpdateId !== null;\n });\n },\n async saveLocalAttachment(execSql, attachment) {\n const updatedAt = new Date().toISOString();\n\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n const attachmentRow = {\n localId: attachment.localId,\n slotId: attachment.slotId,\n blobId: attachment.blobId,\n storageKey: attachment.storageKey,\n mimeType: attachment.mimeType,\n byteLength: attachment.byteLength,\n contentSha256: attachment.contentSha256,\n updatedAt,\n // Written on every save so re-filling a slot clears a stale detach\n // marker instead of inheriting it from the row it replaces.\n detachedAt: attachment.detachedAt,\n };\n await db\n .insert(documentAttachmentBlobProjection)\n .values(attachmentRow)\n .onConflictDoUpdate({\n target: [\n documentAttachmentBlobProjection.localId,\n documentAttachmentBlobProjection.slotId,\n ],\n set: attachmentRow,\n })\n .run();\n });\n },\n async deleteLocalAttachment(execSql, localId, slotId, storageKey) {\n await getClientSQLitePersistenceRuntime(execSql).transaction(async (db) => {\n await db\n .delete(documentAttachmentBlobProjection)\n .where(\n and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, slotId),\n eq(documentAttachmentBlobProjection.storageKey, storageKey),\n ),\n )\n .run();\n // Another slot may hold the same hydrated copy; the reclaim deletes\n // the bytes only once no row references them.\n await queueDocumentAttachmentStorageKeys(db, [storageKey]);\n });\n },\n async markLocalAttachmentDetached(execSql, localId, slotId, storageKey) {\n const detachedAt = new Date().toISOString();\n\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n await db\n .update(documentAttachmentBlobProjection)\n .set({ detachedAt })\n .where(\n and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, slotId),\n eq(documentAttachmentBlobProjection.storageKey, storageKey),\n isNull(documentAttachmentBlobProjection.detachedAt),\n ),\n )\n .run();\n });\n },\n async savePendingAttachment(execSql, attachment) {\n const createdAt = new Date().toISOString();\n\n await getClientSQLitePersistenceRuntime(execSql).runMutation(async (db) => {\n const attachmentRow = buildPendingAttachmentRow(attachment, createdAt);\n await db\n .insert(documentPendingAttachments)\n .values(attachmentRow)\n .onConflictDoUpdate({\n target: [\n documentPendingAttachments.localId,\n documentPendingAttachments.slotId,\n ],\n set: {\n name: attachmentRow.name,\n mimeType: attachmentRow.mimeType,\n storageKey: attachmentRow.storageKey,\n byteLength: attachmentRow.byteLength,\n contentSha256: attachmentRow.contentSha256,\n uploadBlobId: attachmentRow.uploadBlobId,\n uploadContentKey: attachmentRow.uploadContentKey,\n uploadIv: attachmentRow.uploadIv,\n uploadContentKeyEpoch: attachmentRow.uploadContentKeyEpoch,\n uploadPartSize: attachmentRow.uploadPartSize,\n uploadPlaintextSha256: attachmentRow.uploadPlaintextSha256,\n uploadStageId: attachmentRow.uploadStageId,\n },\n })\n .run();\n });\n },\n async deletePendingUpdate(execSql, id) {\n await runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await deleteDocumentPendingUpdate(lockedExecSql, id);\n });\n },\n async deletePendingUpdates(execSql, localId) {\n await runSerializedSqlMutation(execSql, async (lockedExecSql) => {\n await deleteDocumentPendingUpdates(\n lockedExecSql,\n getDocumentScope(localId),\n );\n });\n },\n async deletePendingAttachment(execSql, localId, slotId, storageKey) {\n await getClientSQLitePersistenceRuntime(execSql).transaction(async (db) => {\n await db\n .delete(documentPendingAttachments)\n .where(\n and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, slotId),\n eq(documentPendingAttachments.storageKey, storageKey),\n ),\n )\n .run();\n // A dropped upload may hold the last reference to its staged bytes.\n await queueDocumentAttachmentStorageKeys(db, [storageKey]);\n });\n },\n async deletePendingAttachments(execSql, localId) {\n await getClientSQLitePersistenceRuntime(execSql).transaction(async (db) => {\n const deleted = await db\n .delete(documentPendingAttachments)\n .where(eq(documentPendingAttachments.localId, localId))\n .returning({ storageKey: documentPendingAttachments.storageKey });\n await queueDocumentAttachmentStorageKeys(\n db,\n deleted.map(({ storageKey }) => storageKey),\n );\n });\n },\n};\n"]}
@@ -306,7 +306,7 @@ export declare const uploadMultipartBlobPartBytesOperation: Omit<{
306
306
  }>>;
307
307
  };
308
308
  readonly runtimeRefinements: readonly [{
309
- readonly description: "Declared multipart part byte length is at most 104857600";
309
+ readonly description: "Declared multipart part byte length is at most 33554432";
310
310
  readonly id: "blobs.multipartPart.declaredByteLengthMax";
311
311
  }];
312
312
  }, "failureResponses" | "failureStatuses"> & {
@@ -1,2 +1,7 @@
1
- /** Protocol ceiling for one buffered multipart blob part. */
1
+ /**
2
+ * Protocol ceiling for one buffered multipart blob part. The API buffers a
3
+ * part before uploading it, and the Workers isolate that serves uploads has
4
+ * 128 MB of memory, so the ceiling leaves room for the copy the digest check
5
+ * makes.
6
+ */
2
7
  export declare const MAX_MULTIPART_BLOB_PART_BYTES: number;
@@ -1,3 +1,8 @@
1
- /** Protocol ceiling for one buffered multipart blob part. */
2
- export const MAX_MULTIPART_BLOB_PART_BYTES = 100 * 1024 * 1024;
1
+ /**
2
+ * Protocol ceiling for one buffered multipart blob part. The API buffers a
3
+ * part before uploading it, and the Workers isolate that serves uploads has
4
+ * 128 MB of memory, so the ceiling leaves room for the copy the digest check
5
+ * makes.
6
+ */
7
+ export const MAX_MULTIPART_BLOB_PART_BYTES = 32 * 1024 * 1024;
3
8
  //# sourceMappingURL=blob.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"blob.js","sourceRoot":"","sources":["blob.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,MAAM,CAAC,MAAM,6BAA6B,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC","sourcesContent":["/** Protocol ceiling for one buffered multipart blob part. */\nexport const MAX_MULTIPART_BLOB_PART_BYTES = 100 * 1024 * 1024;\n"]}
1
+ {"version":3,"file":"blob.js","sourceRoot":"","sources":["blob.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC","sourcesContent":["/**\n * Protocol ceiling for one buffered multipart blob part. The API buffers a\n * part before uploading it, and the Workers isolate that serves uploads has\n * 128 MB of memory, so the ceiling leaves room for the copy the digest check\n * makes.\n */\nexport const MAX_MULTIPART_BLOB_PART_BYTES = 32 * 1024 * 1024;\n"]}
@@ -74,7 +74,7 @@ export declare const MAX_INLINE_CONTAINER_REKEYS = 16;
74
74
  * exceeding it: beyond the cap the remainder repairs lazily.
75
75
  *
76
76
  * A sealed keyring is 64 bytes per retained epoch, so a full batch stays far
77
- * inside the API's 100 MiB request limit unless its containers average tens of
77
+ * inside the API's 32 MiB request limit unless its containers average tens of
78
78
  * thousands of rotations each, which `MAX_CONTAINER_KEY_EPOCH` already treats
79
79
  * as a runaway rather than a use.
80
80
  */
@@ -66,7 +66,7 @@ export const MAX_INLINE_CONTAINER_REKEYS = 16;
66
66
  * exceeding it: beyond the cap the remainder repairs lazily.
67
67
  *
68
68
  * A sealed keyring is 64 bytes per retained epoch, so a full batch stays far
69
- * inside the API's 100 MiB request limit unless its containers average tens of
69
+ * inside the API's 32 MiB request limit unless its containers average tens of
70
70
  * thousands of rotations each, which `MAX_CONTAINER_KEY_EPOCH` already treats
71
71
  * as a runaway rather than a use.
72
72
  */
@@ -1 +1 @@
1
- {"version":3,"file":"containerKekKeyringWire.js","sourceRoot":"","sources":["containerKekKeyringWire.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,CAAC;AAC7C,MAAM,CAAC,MAAM,yCAAyC,GAAG,CAAC,CAAC;AAC3D,MAAM,CAAC,MAAM,wCAAwC,GAAG,EAAE,CAAC;AAC3D,MAAM,CAAC,MAAM,gDAAgD,GAAG,EAAE,CAAC;AAEnE,MAAM,UAAU,8BAA8B,CAAC,QAAgB;IAC7D,OAAO,CACL,yCAAyC;QACzC,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,wCAAwC;QACzD,gDAAgD,CACjD,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,MAAM,8CAA8C,GAAG,YAAY,CACxE,8BAA8B,CAAC,uBAAuB,CAAC,CACxD,CAAC;AAEF,MAAM,wBAAwB,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;AAClD,MAAM,gCAAgC,GAAG,0BAA0B,CACjE,CAAC,CAAC,MAAM,CAAS,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC,EACxC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAC7B,CAAC;AAEF,MAAM,CAAC,MAAM,mCAAmC,GAAG,gBAAgB,CAAC;IAClE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE;IAC/B,EAAE,EAAE,mBAAmB,CAAC,wBAAwB,CAAC;IACjD,MAAM,EAAE,mBAAmB,CAAC,8CAA8C,CAAC;IAC3E,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,OAAO,EAAE,gCAAgC;CAC1C,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,UAAU,+BAA+B,CAC7C,KAAc;IAEd,OAAO,mCAAmC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC;AACtE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,EAAE,CAAC;AAE9C;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,CAAC;AAEhD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,mCAAmC,GAAG,EAAE,CAAC;AAEtD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uCAAuC,GAAG,EAAE,CAAC","sourcesContent":["import { z } from \"zod\";\nimport { registerJsonSchemaFragment } from \"../jsonSchema\";\nimport { boundedStringSchema, loosePlainObject } from \"../schema\";\n\n/**\n * Shared wire arithmetic for sealed container KEK keyrings.\n *\n * These numbers define the exact-length equality every layer enforces: the\n * server rejects a rotation whose sealed keyring ciphertext differs from\n * `sealedContainerKekKeyringBytes(keyEpoch)`, and response guards reject any\n * blob over the `MAX_CONTAINER_KEY_EPOCH` ceiling before cryptographic work.\n * `@tearleads/crypto` consumes these same constants for seal/open, so the\n * equation has one definition. The overhead constant is the AES-GCM tag.\n */\nexport const MAX_CONTAINER_KEY_EPOCH = 65536;\nexport const SEALED_CONTAINER_KEK_KEYRING_HEADER_BYTES = 8;\nexport const SEALED_CONTAINER_KEK_KEYRING_ENTRY_BYTES = 64;\nexport const SEALED_CONTAINER_KEK_KEYRING_AEAD_OVERHEAD_BYTES = 16;\n\nexport function sealedContainerKekKeyringBytes(keyEpoch: number): number {\n return (\n SEALED_CONTAINER_KEK_KEYRING_HEADER_BYTES +\n (keyEpoch - 1) * SEALED_CONTAINER_KEK_KEYRING_ENTRY_BYTES +\n SEALED_CONTAINER_KEK_KEYRING_AEAD_OVERHEAD_BYTES\n );\n}\n\nfunction base64Length(bytes: number): number {\n return Math.ceil(bytes / 3) * 4;\n}\n\n/** Parse ceiling: the base64 length of a keyring sealed at the epoch cap. */\nexport const MAX_SEALED_CONTAINER_KEK_KEYRING_BASE64_LENGTH = base64Length(\n sealedContainerKekKeyringBytes(MAX_CONTAINER_KEY_EPOCH),\n);\n\nconst AES_GCM_IV_BASE64_LENGTH = base64Length(12);\nconst ContainerKekKeyringVersionSchema = registerJsonSchemaFragment(\n z.custom<number>((value) => value === 1),\n { const: 1, type: \"number\" },\n);\n\nexport const ContainerKekKeyringWireRecordSchema = loosePlainObject({\n containerId: z.string(),\n containerKeyEpochId: z.string(),\n iv: boundedStringSchema(AES_GCM_IV_BASE64_LENGTH),\n sealed: boundedStringSchema(MAX_SEALED_CONTAINER_KEK_KEYRING_BASE64_LENGTH),\n sealingSuite: z.string(),\n version: ContainerKekKeyringVersionSchema,\n});\n\n/**\n * Structural bound applied before any allocation-heavy or cryptographic work.\n * Content verification (exact length for the epoch, AEAD, per-entry\n * commitments) happens in `@tearleads/crypto`; this guard only bounds a\n * hostile payload.\n */\nexport function isContainerKekKeyringWireRecord(\n value: unknown,\n): value is Record<string, unknown> {\n return ContainerKekKeyringWireRecordSchema.safeParse(value).success;\n}\n\n/**\n * Maximum epochs one kek-log page may serve. Recovery walks the log from the\n * newest page backward, so the page bound — not the container's lifetime\n * rotation count — determines a response's size.\n */\nexport const CONTAINER_KEK_LOG_PAGE_LIMIT = 256;\n\n/**\n * Maximum signed container rekeys one document/blob write may carry inline.\n * Each rotation ships a keyring, which is O(its epoch) bytes, so an unbounded\n * batch is an unbounded request body.\n */\nexport const MAX_INLINE_CONTAINER_REKEYS = 16;\n\n/**\n * Maximum descendant rekeys one container rotation may carry. A rotation must\n * re-key, in its own transaction, every descendant that sits above a directly\n * granted container, or a writer granted only further down would wait on\n * another device's write. The set is empty for almost every rotation, since\n * child containers carry no direct grant unless shared, so this bounds the\n * body rather than shaping ordinary traffic. A rotation is never refused for\n * exceeding it: beyond the cap the remainder repairs lazily.\n *\n * A sealed keyring is 64 bytes per retained epoch, so a full batch stays far\n * inside the API's 100 MiB request limit unless its containers average tens of\n * thousands of rotations each, which `MAX_CONTAINER_KEY_EPOCH` already treats\n * as a runaway rather than a use.\n */\nexport const MAX_ROTATION_CONTAINER_REKEYS = 64;\n\n/**\n * Maximum recipient envelopes one kek-log epoch may serve, applied PER EPOCH\n * so no epoch can be starved by another's width — a starved epoch is\n * indistinguishable from an unaddressed one and would surface as a false\n * `no-addressed-envelope` recovery failure.\n *\n * Recovery needs ONE openable anchor per epoch, not every envelope, and the\n * ranking puts the requester's own direct wrap first, so this cap never costs\n * a usable anchor. Together with the epoch page limit it bounds the response\n * at page x cap rows regardless of how many recipients a container has\n * accumulated.\n */\nexport const CONTAINER_KEK_WRAPS_PER_EPOCH_LIMIT = 16;\n\n/**\n * Maximum authorized principals a single kek-log read may scope wraps to.\n * A requester's principal set has no intrinsic ceiling and each entry adds a\n * clause, and bind parameters, to the statement.\n *\n * The requester's direct user envelope and their parent-container envelopes\n * are scoped outside this cap and rank ahead of principal wraps, so the bound\n * never costs the anchors openable without principal-policy state. Opening a\n * principal wrap needs that state regardless (see issue #1941).\n */\nexport const CONTAINER_KEK_LOG_PRINCIPAL_SCOPE_LIMIT = 64;\n"]}
1
+ {"version":3,"file":"containerKekKeyringWire.js","sourceRoot":"","sources":["containerKekKeyringWire.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAElE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,CAAC;AAC7C,MAAM,CAAC,MAAM,yCAAyC,GAAG,CAAC,CAAC;AAC3D,MAAM,CAAC,MAAM,wCAAwC,GAAG,EAAE,CAAC;AAC3D,MAAM,CAAC,MAAM,gDAAgD,GAAG,EAAE,CAAC;AAEnE,MAAM,UAAU,8BAA8B,CAAC,QAAgB;IAC7D,OAAO,CACL,yCAAyC;QACzC,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,wCAAwC;QACzD,gDAAgD,CACjD,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,6EAA6E;AAC7E,MAAM,CAAC,MAAM,8CAA8C,GAAG,YAAY,CACxE,8BAA8B,CAAC,uBAAuB,CAAC,CACxD,CAAC;AAEF,MAAM,wBAAwB,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;AAClD,MAAM,gCAAgC,GAAG,0BAA0B,CACjE,CAAC,CAAC,MAAM,CAAS,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC,EACxC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAC7B,CAAC;AAEF,MAAM,CAAC,MAAM,mCAAmC,GAAG,gBAAgB,CAAC;IAClE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE;IAC/B,EAAE,EAAE,mBAAmB,CAAC,wBAAwB,CAAC;IACjD,MAAM,EAAE,mBAAmB,CAAC,8CAA8C,CAAC;IAC3E,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,OAAO,EAAE,gCAAgC;CAC1C,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,UAAU,+BAA+B,CAC7C,KAAc;IAEd,OAAO,mCAAmC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC;AACtE,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,CAAC;AAEhD;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,EAAE,CAAC;AAE9C;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,CAAC;AAEhD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,mCAAmC,GAAG,EAAE,CAAC;AAEtD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,uCAAuC,GAAG,EAAE,CAAC","sourcesContent":["import { z } from \"zod\";\nimport { registerJsonSchemaFragment } from \"../jsonSchema\";\nimport { boundedStringSchema, loosePlainObject } from \"../schema\";\n\n/**\n * Shared wire arithmetic for sealed container KEK keyrings.\n *\n * These numbers define the exact-length equality every layer enforces: the\n * server rejects a rotation whose sealed keyring ciphertext differs from\n * `sealedContainerKekKeyringBytes(keyEpoch)`, and response guards reject any\n * blob over the `MAX_CONTAINER_KEY_EPOCH` ceiling before cryptographic work.\n * `@tearleads/crypto` consumes these same constants for seal/open, so the\n * equation has one definition. The overhead constant is the AES-GCM tag.\n */\nexport const MAX_CONTAINER_KEY_EPOCH = 65536;\nexport const SEALED_CONTAINER_KEK_KEYRING_HEADER_BYTES = 8;\nexport const SEALED_CONTAINER_KEK_KEYRING_ENTRY_BYTES = 64;\nexport const SEALED_CONTAINER_KEK_KEYRING_AEAD_OVERHEAD_BYTES = 16;\n\nexport function sealedContainerKekKeyringBytes(keyEpoch: number): number {\n return (\n SEALED_CONTAINER_KEK_KEYRING_HEADER_BYTES +\n (keyEpoch - 1) * SEALED_CONTAINER_KEK_KEYRING_ENTRY_BYTES +\n SEALED_CONTAINER_KEK_KEYRING_AEAD_OVERHEAD_BYTES\n );\n}\n\nfunction base64Length(bytes: number): number {\n return Math.ceil(bytes / 3) * 4;\n}\n\n/** Parse ceiling: the base64 length of a keyring sealed at the epoch cap. */\nexport const MAX_SEALED_CONTAINER_KEK_KEYRING_BASE64_LENGTH = base64Length(\n sealedContainerKekKeyringBytes(MAX_CONTAINER_KEY_EPOCH),\n);\n\nconst AES_GCM_IV_BASE64_LENGTH = base64Length(12);\nconst ContainerKekKeyringVersionSchema = registerJsonSchemaFragment(\n z.custom<number>((value) => value === 1),\n { const: 1, type: \"number\" },\n);\n\nexport const ContainerKekKeyringWireRecordSchema = loosePlainObject({\n containerId: z.string(),\n containerKeyEpochId: z.string(),\n iv: boundedStringSchema(AES_GCM_IV_BASE64_LENGTH),\n sealed: boundedStringSchema(MAX_SEALED_CONTAINER_KEK_KEYRING_BASE64_LENGTH),\n sealingSuite: z.string(),\n version: ContainerKekKeyringVersionSchema,\n});\n\n/**\n * Structural bound applied before any allocation-heavy or cryptographic work.\n * Content verification (exact length for the epoch, AEAD, per-entry\n * commitments) happens in `@tearleads/crypto`; this guard only bounds a\n * hostile payload.\n */\nexport function isContainerKekKeyringWireRecord(\n value: unknown,\n): value is Record<string, unknown> {\n return ContainerKekKeyringWireRecordSchema.safeParse(value).success;\n}\n\n/**\n * Maximum epochs one kek-log page may serve. Recovery walks the log from the\n * newest page backward, so the page bound — not the container's lifetime\n * rotation count — determines a response's size.\n */\nexport const CONTAINER_KEK_LOG_PAGE_LIMIT = 256;\n\n/**\n * Maximum signed container rekeys one document/blob write may carry inline.\n * Each rotation ships a keyring, which is O(its epoch) bytes, so an unbounded\n * batch is an unbounded request body.\n */\nexport const MAX_INLINE_CONTAINER_REKEYS = 16;\n\n/**\n * Maximum descendant rekeys one container rotation may carry. A rotation must\n * re-key, in its own transaction, every descendant that sits above a directly\n * granted container, or a writer granted only further down would wait on\n * another device's write. The set is empty for almost every rotation, since\n * child containers carry no direct grant unless shared, so this bounds the\n * body rather than shaping ordinary traffic. A rotation is never refused for\n * exceeding it: beyond the cap the remainder repairs lazily.\n *\n * A sealed keyring is 64 bytes per retained epoch, so a full batch stays far\n * inside the API's 32 MiB request limit unless its containers average tens of\n * thousands of rotations each, which `MAX_CONTAINER_KEY_EPOCH` already treats\n * as a runaway rather than a use.\n */\nexport const MAX_ROTATION_CONTAINER_REKEYS = 64;\n\n/**\n * Maximum recipient envelopes one kek-log epoch may serve, applied PER EPOCH\n * so no epoch can be starved by another's width — a starved epoch is\n * indistinguishable from an unaddressed one and would surface as a false\n * `no-addressed-envelope` recovery failure.\n *\n * Recovery needs ONE openable anchor per epoch, not every envelope, and the\n * ranking puts the requester's own direct wrap first, so this cap never costs\n * a usable anchor. Together with the epoch page limit it bounds the response\n * at page x cap rows regardless of how many recipients a container has\n * accumulated.\n */\nexport const CONTAINER_KEK_WRAPS_PER_EPOCH_LIMIT = 16;\n\n/**\n * Maximum authorized principals a single kek-log read may scope wraps to.\n * A requester's principal set has no intrinsic ceiling and each entry adds a\n * clause, and bind parameters, to the statement.\n *\n * The requester's direct user envelope and their parent-container envelopes\n * are scoped outside this cap and rank ahead of principal wraps, so the bound\n * never costs the anchors openable without principal-policy state. Opening a\n * principal wrap needs that state regardless (see issue #1941).\n */\nexport const CONTAINER_KEK_LOG_PRINCIPAL_SCOPE_LIMIT = 64;\n"]}
@@ -1,10 +1,12 @@
1
1
  import { MAX_CONTAINER_PATH_LENGTH } from "./containerLimits.js";
2
2
  export const MAX_DOCUMENT_SYNC_REQUEST_BYTES = 16 * 1024 * 1024;
3
3
  // A response page must be able to carry the largest accepted atomic rotation
4
- // baseline (the 100 MiB route ceiling) plus its signed metadata and key-bundle
5
- // envelope. Ordinary sync updates remain constrained by the smaller request
6
- // ceiling above.
7
- export const MAX_DOCUMENT_SYNC_RESPONSE_PAGE_BYTES = 128 * 1024 * 1024;
4
+ // baseline (the 32 MiB route ceiling) plus its signed metadata and key-bundle
5
+ // envelope, and no more: the API materializes a page and then serializes it,
6
+ // so the page and its JSON copy must both fit the 128 MB Workers isolate.
7
+ // Ordinary sync updates remain constrained by the smaller request ceiling
8
+ // above.
9
+ export const MAX_DOCUMENT_SYNC_RESPONSE_PAGE_BYTES = 48 * 1024 * 1024;
8
10
  export const MAX_DOCUMENT_SYNC_RESPONSE_ENVELOPE_BYTES = 16 * 1024 * 1024;
9
11
  export const MAX_DOCUMENT_SYNC_RESPONSE_UPDATE_PAGE_BYTES = MAX_DOCUMENT_SYNC_RESPONSE_PAGE_BYTES -
10
12
  MAX_DOCUMENT_SYNC_RESPONSE_ENVELOPE_BYTES;
@@ -1 +1 @@
1
- {"version":3,"file":"documentSyncLimits.js","sourceRoot":"","sources":["documentSyncLimits.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,MAAM,CAAC,MAAM,+BAA+B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhE,6EAA6E;AAC7E,+EAA+E;AAC/E,4EAA4E;AAC5E,iBAAiB;AACjB,MAAM,CAAC,MAAM,qCAAqC,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AACvE,MAAM,CAAC,MAAM,yCAAyC,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAC1E,MAAM,CAAC,MAAM,4CAA4C,GACvD,qCAAqC;IACrC,yCAAyC,CAAC;AAC5C,MAAM,CAAC,MAAM,uCAAuC,GAAG,EAAE,CAAC;AAC1D,MAAM,CAAC,MAAM,oCAAoC,GAAG,GAAG,CAAC;AAExD,MAAM,CAAC,MAAM,kCAAkC,GAAG,EAAE,CAAC;AACrD,6EAA6E;AAC7E,4EAA4E;AAC5E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,qCAAqC,GAAG,EAAE,CAAC;AACxD,8EAA8E;AAC9E,6EAA6E;AAC7E,2EAA2E;AAC3E,kBAAkB;AAClB,MAAM,CAAC,MAAM,yCAAyC,GACpD,qCAAqC,GAAG,yBAAyB,CAAC;AACpE,8EAA8E;AAC9E,8EAA8E;AAC9E,qEAAqE;AACrE,MAAM,CAAC,MAAM,qCAAqC,GAAG,EAAE,CAAC","sourcesContent":["import { MAX_CONTAINER_PATH_LENGTH } from \"./containerLimits\";\n\nexport const MAX_DOCUMENT_SYNC_REQUEST_BYTES = 16 * 1024 * 1024;\n\n// A response page must be able to carry the largest accepted atomic rotation\n// baseline (the 100 MiB route ceiling) plus its signed metadata and key-bundle\n// envelope. Ordinary sync updates remain constrained by the smaller request\n// ceiling above.\nexport const MAX_DOCUMENT_SYNC_RESPONSE_PAGE_BYTES = 128 * 1024 * 1024;\nexport const MAX_DOCUMENT_SYNC_RESPONSE_ENVELOPE_BYTES = 16 * 1024 * 1024;\nexport const MAX_DOCUMENT_SYNC_RESPONSE_UPDATE_PAGE_BYTES =\n MAX_DOCUMENT_SYNC_RESPONSE_PAGE_BYTES -\n MAX_DOCUMENT_SYNC_RESPONSE_ENVELOPE_BYTES;\nexport const MAX_DOCUMENT_SYNC_RESPONSE_PAGE_UPDATES = 64;\nexport const MAX_DOCUMENT_SYNC_PULL_CURSOR_LENGTH = 512;\n\nexport const MAX_DOCUMENT_SYNC_OUTGOING_UPDATES = 64;\n// Individual updates may use the request's remaining budget. The SDK applies\n// the exact serialized-byte ceiling after encryption and trims the batch; a\n// smaller estimate here would permanently strand an otherwise valid update.\nexport const MAX_DOCUMENT_SYNC_AUTHORIZATION_PATHS = 64;\n// Derive the aggregate ceiling from the structural limits so every valid link\n// set remains syncable after later container moves deepen its paths. This is\n// still a finite verification bound; the serialized request has a separate\n// 16 MiB ceiling.\nexport const MAX_DOCUMENT_SYNC_AUTHORIZATION_PATH_REFS =\n MAX_DOCUMENT_SYNC_AUTHORIZATION_PATHS * MAX_CONTAINER_PATH_LENGTH;\n// Flag-day product maximum for every document content-key bundle. Create/link\n// request validation and the client link preflight keep a greenfield database\n// from ever producing a document that cannot fit this sync contract.\nexport const MAX_DOCUMENT_SYNC_CONTENT_KEY_TARGETS = 64;\n"]}
1
+ {"version":3,"file":"documentSyncLimits.js","sourceRoot":"","sources":["documentSyncLimits.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,MAAM,CAAC,MAAM,+BAA+B,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEhE,6EAA6E;AAC7E,8EAA8E;AAC9E,6EAA6E;AAC7E,0EAA0E;AAC1E,0EAA0E;AAC1E,SAAS;AACT,MAAM,CAAC,MAAM,qCAAqC,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACtE,MAAM,CAAC,MAAM,yCAAyC,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAC1E,MAAM,CAAC,MAAM,4CAA4C,GACvD,qCAAqC;IACrC,yCAAyC,CAAC;AAC5C,MAAM,CAAC,MAAM,uCAAuC,GAAG,EAAE,CAAC;AAC1D,MAAM,CAAC,MAAM,oCAAoC,GAAG,GAAG,CAAC;AAExD,MAAM,CAAC,MAAM,kCAAkC,GAAG,EAAE,CAAC;AACrD,6EAA6E;AAC7E,4EAA4E;AAC5E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,qCAAqC,GAAG,EAAE,CAAC;AACxD,8EAA8E;AAC9E,6EAA6E;AAC7E,2EAA2E;AAC3E,kBAAkB;AAClB,MAAM,CAAC,MAAM,yCAAyC,GACpD,qCAAqC,GAAG,yBAAyB,CAAC;AACpE,8EAA8E;AAC9E,8EAA8E;AAC9E,qEAAqE;AACrE,MAAM,CAAC,MAAM,qCAAqC,GAAG,EAAE,CAAC","sourcesContent":["import { MAX_CONTAINER_PATH_LENGTH } from \"./containerLimits\";\n\nexport const MAX_DOCUMENT_SYNC_REQUEST_BYTES = 16 * 1024 * 1024;\n\n// A response page must be able to carry the largest accepted atomic rotation\n// baseline (the 32 MiB route ceiling) plus its signed metadata and key-bundle\n// envelope, and no more: the API materializes a page and then serializes it,\n// so the page and its JSON copy must both fit the 128 MB Workers isolate.\n// Ordinary sync updates remain constrained by the smaller request ceiling\n// above.\nexport const MAX_DOCUMENT_SYNC_RESPONSE_PAGE_BYTES = 48 * 1024 * 1024;\nexport const MAX_DOCUMENT_SYNC_RESPONSE_ENVELOPE_BYTES = 16 * 1024 * 1024;\nexport const MAX_DOCUMENT_SYNC_RESPONSE_UPDATE_PAGE_BYTES =\n MAX_DOCUMENT_SYNC_RESPONSE_PAGE_BYTES -\n MAX_DOCUMENT_SYNC_RESPONSE_ENVELOPE_BYTES;\nexport const MAX_DOCUMENT_SYNC_RESPONSE_PAGE_UPDATES = 64;\nexport const MAX_DOCUMENT_SYNC_PULL_CURSOR_LENGTH = 512;\n\nexport const MAX_DOCUMENT_SYNC_OUTGOING_UPDATES = 64;\n// Individual updates may use the request's remaining budget. The SDK applies\n// the exact serialized-byte ceiling after encryption and trims the batch; a\n// smaller estimate here would permanently strand an otherwise valid update.\nexport const MAX_DOCUMENT_SYNC_AUTHORIZATION_PATHS = 64;\n// Derive the aggregate ceiling from the structural limits so every valid link\n// set remains syncable after later container moves deepen its paths. This is\n// still a finite verification bound; the serialized request has a separate\n// 16 MiB ceiling.\nexport const MAX_DOCUMENT_SYNC_AUTHORIZATION_PATH_REFS =\n MAX_DOCUMENT_SYNC_AUTHORIZATION_PATHS * MAX_CONTAINER_PATH_LENGTH;\n// Flag-day product maximum for every document content-key bundle. Create/link\n// request validation and the client link preflight keep a greenfield database\n// from ever producing a document that cannot fit this sync contract.\nexport const MAX_DOCUMENT_SYNC_CONTENT_KEY_TARGETS = 64;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tearleads/client-sdk",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "React-free Tearleads client runtime: local SQLite persistence, keys, sync, encrypted blobs, and domain workflows.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -1,4 +1,5 @@
1
1
  import { createBlobByteSource } from "../../../data/blobContracts.js";
2
+ import { runSerializedDocumentBlobMutation, runSerializedSqlMutation, } from "../../../workflows/documents/index.js";
2
3
  import { assignLocalAttachmentSlots, upsertPendingAttachments, } from "./attachmentPersistence.js";
3
4
  import { setReadySnapshot } from "./state.js";
4
5
  import { isDocumentStoreSyncGenerationCurrent, } from "./syncGeneration.js";
@@ -22,21 +23,31 @@ async function deleteStagedAttachmentBytes(input) {
22
23
  state.runtime.util.log(`Documents: failed to roll back ${failedCleanupCount} staged attachment operation${failedCleanupCount === 1 ? "" : "s"}.`);
23
24
  }
24
25
  }
26
+ /**
27
+ * Roll back staged bytes that no committed row references. Like orphan
28
+ * reclaim, check every held and pending reference under the key's lock: an
29
+ * overlapping reset can leave a committed key referenced only by an upload.
30
+ */
25
31
  export async function deleteUnreferencedStagedAttachmentBytes(input) {
26
- let durableStorageKeys;
27
- try {
28
- const durableAttachments = await input.state.persistence.listLocalAttachments(input.generation.execSql, input.state.localId);
29
- durableStorageKeys = new Set(durableAttachments.map(({ storageKey }) => storageKey));
30
- }
31
- catch {
32
- // A failed probe cannot prove that the atomic mutation rolled back. Keep
33
- // the bytes: orphan maintenance can reclaim them once storage is readable.
34
- return;
32
+ const { execSql } = input.generation;
33
+ const { state } = input;
34
+ let failedCleanupCount = 0;
35
+ for (const { storageKey } of input.pendingAttachments) {
36
+ try {
37
+ await runSerializedDocumentBlobMutation(execSql, storageKey, async () => {
38
+ const referenced = await runSerializedSqlMutation(execSql, (lockedExecSql) => state.persistence.orphanBlobs.isReferenced(lockedExecSql, storageKey));
39
+ if (!referenced)
40
+ await state.runtime.infra.blobStore.deleteBytes(storageKey);
41
+ });
42
+ }
43
+ catch {
44
+ // A failed probe cannot prove that the atomic mutation rolled back, so
45
+ // the bytes stay; a failed delete leaves them for a later sweep.
46
+ failedCleanupCount += 1;
47
+ }
35
48
  }
36
- await deleteStagedAttachmentBytes({
37
- pendingAttachments: input.pendingAttachments.filter(({ storageKey }) => !durableStorageKeys.has(storageKey)),
38
- state: input.state,
39
- });
49
+ if (failedCleanupCount > 0)
50
+ state.runtime.util.log(`Documents: failed to roll back ${failedCleanupCount} staged attachment operation${failedCleanupCount === 1 ? "" : "s"}.`);
40
51
  }
41
52
  async function writePendingAttachmentBytes(input) {
42
53
  for (const [index, pendingAttachment] of input.pendingAttachments.entries()) {
@@ -1 +1 @@
1
- {"version":3,"file":"attachmentStaging.js","sourceRoot":"","sources":["attachmentStaging.ts"],"names":[],"mappings":"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","sourcesContent":["import { createBlobByteSource } from \"../../../data/blobContracts\";\nimport type {\n AttachmentStagingRows,\n LocalAttachmentRecord,\n PendingAttachmentRecord,\n} from \"../../../workflows/documents\";\nimport type { DocumentAttachmentUpload } from \"../types\";\nimport {\n assignLocalAttachmentSlots,\n upsertPendingAttachments,\n} from \"./attachmentPersistence\";\nimport { type DocumentStoreState, setReadySnapshot } from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\ninterface StagePendingAttachmentsInput {\n files: ReadonlyArray<DocumentAttachmentUpload>;\n generation: DocumentStoreSyncGeneration;\n pendingAttachments: PendingAttachmentRecord[];\n state: DocumentStoreState;\n}\n\nfunction localAttachmentRecords(\n localId: string,\n pendingAttachments: ReadonlyArray<PendingAttachmentRecord>,\n): LocalAttachmentRecord[] {\n return pendingAttachments.map((pendingAttachment) => ({\n blobId: null,\n byteLength: pendingAttachment.byteLength,\n contentSha256: pendingAttachment.contentSha256,\n detachedAt: null,\n localId,\n mimeType: pendingAttachment.mimeType,\n slotId: pendingAttachment.slotId,\n storageKey: pendingAttachment.storageKey,\n }));\n}\n\nasync function deleteStagedAttachmentBytes(input: {\n pendingAttachments: ReadonlyArray<PendingAttachmentRecord>;\n state: DocumentStoreState;\n}) {\n const { state } = input;\n const cleanupResults = await Promise.allSettled(\n input.pendingAttachments.map((attachment) =>\n state.runtime.infra.blobStore.deleteBytes(attachment.storageKey),\n ),\n );\n const failedCleanupCount = cleanupResults.filter(\n (result) => result.status === \"rejected\",\n ).length;\n if (failedCleanupCount > 0) {\n state.runtime.util.log(\n `Documents: failed to roll back ${failedCleanupCount} staged attachment operation${failedCleanupCount === 1 ? \"\" : \"s\"}.`,\n );\n }\n}\n\nexport async function deleteUnreferencedStagedAttachmentBytes(input: {\n generation: DocumentStoreSyncGeneration;\n pendingAttachments: ReadonlyArray<PendingAttachmentRecord>;\n state: DocumentStoreState;\n}): Promise<void> {\n let durableStorageKeys: Set<string>;\n try {\n const durableAttachments =\n await input.state.persistence.listLocalAttachments(\n input.generation.execSql,\n input.state.localId,\n );\n durableStorageKeys = new Set(\n durableAttachments.map(({ storageKey }) => storageKey),\n );\n } catch {\n // A failed probe cannot prove that the atomic mutation rolled back. Keep\n // the bytes: orphan maintenance can reclaim them once storage is readable.\n return;\n }\n await deleteStagedAttachmentBytes({\n pendingAttachments: input.pendingAttachments.filter(\n ({ storageKey }) => !durableStorageKeys.has(storageKey),\n ),\n state: input.state,\n });\n}\n\nasync function writePendingAttachmentBytes(\n input: StagePendingAttachmentsInput,\n): Promise<void> {\n for (const [index, pendingAttachment] of input.pendingAttachments.entries()) {\n const sourceFile = input.files[index];\n if (!sourceFile) continue;\n await input.state.runtime.infra.blobStore.writeByteSource(\n pendingAttachment.storageKey,\n createBlobByteSource(sourceFile.bytes),\n );\n }\n}\n\nexport function installPendingAttachmentRows(input: {\n localAttachments: ReadonlyArray<LocalAttachmentRecord>;\n pendingAttachments: ReadonlyArray<PendingAttachmentRecord>;\n state: DocumentStoreState;\n}): void {\n const { localAttachments, pendingAttachments, state } = input;\n assignLocalAttachmentSlots(state, localAttachments);\n upsertPendingAttachments(state, pendingAttachments);\n if (state.doc) {\n setReadySnapshot(\n state,\n state.doc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n }\n}\n\nexport async function stagePendingAttachments(\n input: StagePendingAttachmentsInput,\n): Promise<AttachmentStagingRows | null> {\n const { pendingAttachments, state } = input;\n const localAttachments = localAttachmentRecords(\n state.localId,\n pendingAttachments,\n );\n\n try {\n await writePendingAttachmentBytes(input);\n\n if (!isDocumentStoreSyncGenerationCurrent(state, input.generation)) {\n await deleteStagedAttachmentBytes({ pendingAttachments, state });\n return null;\n }\n return { localAttachments, pendingAttachments };\n } catch (error) {\n await deleteStagedAttachmentBytes({ pendingAttachments, state });\n throw error;\n }\n}\n"]}
1
+ {"version":3,"file":"attachmentStaging.js","sourceRoot":"","sources":["attachmentStaging.ts"],"names":[],"mappings":"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","sourcesContent":["import { createBlobByteSource } from \"../../../data/blobContracts\";\nimport {\n type AttachmentStagingRows,\n type LocalAttachmentRecord,\n type PendingAttachmentRecord,\n runSerializedDocumentBlobMutation,\n runSerializedSqlMutation,\n} from \"../../../workflows/documents\";\nimport type { DocumentAttachmentUpload } from \"../types\";\nimport {\n assignLocalAttachmentSlots,\n upsertPendingAttachments,\n} from \"./attachmentPersistence\";\nimport { type DocumentStoreState, setReadySnapshot } from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\ninterface StagePendingAttachmentsInput {\n files: ReadonlyArray<DocumentAttachmentUpload>;\n generation: DocumentStoreSyncGeneration;\n pendingAttachments: PendingAttachmentRecord[];\n state: DocumentStoreState;\n}\n\nfunction localAttachmentRecords(\n localId: string,\n pendingAttachments: ReadonlyArray<PendingAttachmentRecord>,\n): LocalAttachmentRecord[] {\n return pendingAttachments.map((pendingAttachment) => ({\n blobId: null,\n byteLength: pendingAttachment.byteLength,\n contentSha256: pendingAttachment.contentSha256,\n detachedAt: null,\n localId,\n mimeType: pendingAttachment.mimeType,\n slotId: pendingAttachment.slotId,\n storageKey: pendingAttachment.storageKey,\n }));\n}\n\nasync function deleteStagedAttachmentBytes(input: {\n pendingAttachments: ReadonlyArray<PendingAttachmentRecord>;\n state: DocumentStoreState;\n}) {\n const { state } = input;\n const cleanupResults = await Promise.allSettled(\n input.pendingAttachments.map((attachment) =>\n state.runtime.infra.blobStore.deleteBytes(attachment.storageKey),\n ),\n );\n const failedCleanupCount = cleanupResults.filter(\n (result) => result.status === \"rejected\",\n ).length;\n if (failedCleanupCount > 0) {\n state.runtime.util.log(\n `Documents: failed to roll back ${failedCleanupCount} staged attachment operation${failedCleanupCount === 1 ? \"\" : \"s\"}.`,\n );\n }\n}\n\n/**\n * Roll back staged bytes that no committed row references. Like orphan\n * reclaim, check every held and pending reference under the key's lock: an\n * overlapping reset can leave a committed key referenced only by an upload.\n */\nexport async function deleteUnreferencedStagedAttachmentBytes(input: {\n generation: DocumentStoreSyncGeneration;\n pendingAttachments: ReadonlyArray<PendingAttachmentRecord>;\n state: DocumentStoreState;\n}): Promise<void> {\n const { execSql } = input.generation;\n const { state } = input;\n let failedCleanupCount = 0;\n for (const { storageKey } of input.pendingAttachments) {\n try {\n await runSerializedDocumentBlobMutation(execSql, storageKey, async () => {\n const referenced = await runSerializedSqlMutation(\n execSql,\n (lockedExecSql) =>\n state.persistence.orphanBlobs.isReferenced(\n lockedExecSql,\n storageKey,\n ),\n );\n if (!referenced)\n await state.runtime.infra.blobStore.deleteBytes(storageKey);\n });\n } catch {\n // A failed probe cannot prove that the atomic mutation rolled back, so\n // the bytes stay; a failed delete leaves them for a later sweep.\n failedCleanupCount += 1;\n }\n }\n if (failedCleanupCount > 0)\n state.runtime.util.log(\n `Documents: failed to roll back ${failedCleanupCount} staged attachment operation${failedCleanupCount === 1 ? \"\" : \"s\"}.`,\n );\n}\n\nasync function writePendingAttachmentBytes(\n input: StagePendingAttachmentsInput,\n): Promise<void> {\n for (const [index, pendingAttachment] of input.pendingAttachments.entries()) {\n const sourceFile = input.files[index];\n if (!sourceFile) continue;\n await input.state.runtime.infra.blobStore.writeByteSource(\n pendingAttachment.storageKey,\n createBlobByteSource(sourceFile.bytes),\n );\n }\n}\n\nexport function installPendingAttachmentRows(input: {\n localAttachments: ReadonlyArray<LocalAttachmentRecord>;\n pendingAttachments: ReadonlyArray<PendingAttachmentRecord>;\n state: DocumentStoreState;\n}): void {\n const { localAttachments, pendingAttachments, state } = input;\n assignLocalAttachmentSlots(state, localAttachments);\n upsertPendingAttachments(state, pendingAttachments);\n if (state.doc) {\n setReadySnapshot(\n state,\n state.doc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n }\n}\n\nexport async function stagePendingAttachments(\n input: StagePendingAttachmentsInput,\n): Promise<AttachmentStagingRows | null> {\n const { pendingAttachments, state } = input;\n const localAttachments = localAttachmentRecords(\n state.localId,\n pendingAttachments,\n );\n\n try {\n await writePendingAttachmentBytes(input);\n\n if (!isDocumentStoreSyncGenerationCurrent(state, input.generation)) {\n await deleteStagedAttachmentBytes({ pendingAttachments, state });\n return null;\n }\n return { localAttachments, pendingAttachments };\n } catch (error) {\n await deleteStagedAttachmentBytes({ pendingAttachments, state });\n throw error;\n }\n}\n"]}