@tearleads/client-sdk 0.1.27 → 0.1.29

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 (27) hide show
  1. package/data/documents/blob/shared/blobRecordCrypto.js +2 -17
  2. package/data/documents/blob/shared/blobRecordCrypto.js.map +1 -1
  3. package/data/documents/blob/shared/chunkedBlobCrypto.js +21 -37
  4. package/data/documents/blob/shared/chunkedBlobCrypto.js.map +1 -1
  5. package/data/documents/blob/shared/crypto.js +1 -1
  6. package/data/documents/blob/shared/crypto.js.map +1 -1
  7. package/data/documents/blob/shared/encryptedUploadParts.js +42 -0
  8. package/data/documents/blob/shared/encryptedUploadParts.js.map +1 -0
  9. package/data/documents/blob/shared/types.d.ts +1 -2
  10. package/data/documents/blob/shared/types.js +0 -1
  11. package/data/documents/blob/shared/types.js.map +1 -1
  12. package/data/persistence/documents/internal/attachmentStagingPersistence.js +39 -3
  13. package/data/persistence/documents/internal/attachmentStagingPersistence.js.map +1 -1
  14. package/internal/crypto/blobContentMetadata.d.ts +7 -0
  15. package/internal/crypto/blobContentMetadata.js +21 -0
  16. package/internal/crypto/blobContentMetadata.js.map +1 -0
  17. package/internal/crypto/index.d.ts +1 -0
  18. package/internal/crypto/index.js +1 -0
  19. package/internal/crypto/index.js.map +1 -1
  20. package/package.json +1 -1
  21. package/stores/documents/documentStore/attachmentPersistence.js.map +1 -1
  22. package/stores/documents/documentStore/attachments.js +13 -2
  23. package/stores/documents/documentStore/attachments.js.map +1 -1
  24. package/workflows/blobs/decrypt.js +2 -2
  25. package/workflows/blobs/decrypt.js.map +1 -1
  26. package/workflows/blobs/multipartUpload.js +1 -1
  27. package/workflows/blobs/multipartUpload.js.map +1 -1
@@ -1,6 +1,6 @@
1
- import { AES_GCM_IV_BYTES, assertAesGcmIv, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, CONTENT_RECORD_ENCRYPTION_SUITE, serializeKeyingCanonicalJson, toFingerprint, } from "../../../../internal/crypto/index.js";
1
+ import { AES_GCM_IV_BYTES, assertAesGcmIv, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, CONTENT_RECORD_ENCRYPTION_SUITE, serializeKeyingCanonicalJson, } from "../../../../internal/crypto/index.js";
2
2
  import { bytesToBase64 } from "../../../../internal/encoding/index.js";
3
- import { BLOB_CONTENT_RECORD_AAD_DOMAIN, BLOB_CONTENT_RECORD_HKDF_SALT, BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN, BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN, BLOB_CONTENT_RECORD_NONCE_DOMAIN, TEXT_ENCODER, } from "./types.js";
3
+ import { BLOB_CONTENT_RECORD_AAD_DOMAIN, BLOB_CONTENT_RECORD_HKDF_SALT, BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN, BLOB_CONTENT_RECORD_NONCE_DOMAIN, TEXT_ENCODER, } from "./types.js";
4
4
  const SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
5
5
  // The chunk layout is part of the IV family: a persisted nonce seed can
6
6
  // outlive a chunk-size change (upload resume across an app update), and the
@@ -24,21 +24,6 @@ export async function deriveBlobBaseIv(input) {
24
24
  }))));
25
25
  return digest.slice(0, AES_GCM_IV_BYTES);
26
26
  }
27
- export async function blobContentMetadataHash(input) {
28
- return toFingerprint(TEXT_ENCODER.encode(serializeKeyingCanonicalJson({
29
- domain: BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN,
30
- payload: {
31
- version: BLOB_ENCRYPTED_BYTES_VERSION,
32
- recordKind: "blob_bytes",
33
- blobId: input.blobId,
34
- byteLength: input.byteLength,
35
- chunkCount: input.chunkCount,
36
- chunkSize: input.chunkSize,
37
- contentKeyEpoch: input.contentKeyEpoch,
38
- format: BLOB_ENCRYPTED_BYTES_FORMAT,
39
- },
40
- })));
41
- }
42
27
  function contentRecordDerivationPayload(input) {
43
28
  return {
44
29
  version: BLOB_ENCRYPTED_BYTES_VERSION,
@@ -1 +1 @@
1
- {"version":3,"file":"blobRecordCrypto.js","sourceRoot":"","sources":["blobRecordCrypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AES_GCM_IV_BYTES,\n assertAesGcmIv,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n type KeyingCanonicalJson,\n serializeKeyingCanonicalJson,\n toFingerprint,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport {\n BLOB_CONTENT_RECORD_AAD_DOMAIN,\n BLOB_CONTENT_RECORD_HKDF_SALT,\n BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN,\n BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN,\n BLOB_CONTENT_RECORD_NONCE_DOMAIN,\n TEXT_ENCODER,\n} from \"./types\";\n\nconst SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;\n\n// The chunk layout is part of the IV family: a persisted nonce seed can\n// outlive a chunk-size change (upload resume across an app update), and the\n// record key does not vary with the layout, so the same seed must never\n// yield the same per-chunk IVs over two different plaintext chunkings.\nexport async function deriveBlobBaseIv(input: {\n readonly chunkSize: number;\n readonly nonceSeed: Uint8Array;\n readonly plaintextSha256: string;\n}): Promise<Uint8Array<ArrayBuffer>> {\n assertAesGcmIv(input.nonceSeed, \"Blob nonce seed is invalid\");\n if (!SHA256_HEX_PATTERN.test(input.plaintextSha256)) {\n throw new Error(\"Blob plaintext SHA-256 is invalid\");\n }\n if (!Number.isInteger(input.chunkSize) || input.chunkSize <= 0) {\n throw new Error(\"Blob chunk size is invalid\");\n }\n\n const digest = new Uint8Array(\n await crypto.subtle.digest(\n \"SHA-256\",\n TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_NONCE_DOMAIN,\n payload: {\n chunkSize: input.chunkSize,\n nonceSeed: bytesToBase64(input.nonceSeed),\n plaintextSha256: input.plaintextSha256,\n },\n }),\n ),\n ),\n );\n return digest.slice(0, AES_GCM_IV_BYTES);\n}\n\nexport async function blobContentMetadataHash(input: {\n blobId: string;\n byteLength: number;\n chunkCount: number;\n chunkSize: number;\n contentKeyEpoch: number;\n}): Promise<string> {\n return toFingerprint(\n TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN,\n payload: {\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n recordKind: \"blob_bytes\",\n blobId: input.blobId,\n byteLength: input.byteLength,\n chunkCount: input.chunkCount,\n chunkSize: input.chunkSize,\n contentKeyEpoch: input.contentKeyEpoch,\n format: BLOB_ENCRYPTED_BYTES_FORMAT,\n },\n }),\n ),\n );\n}\n\nfunction contentRecordDerivationPayload(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentRecordId: string;\n organizationId: string;\n}): Record<string, KeyingCanonicalJson> {\n return {\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n organizationId: input.organizationId,\n objectKind: \"blob\",\n objectId: input.blobId,\n contentKeyEpoch: input.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId: input.contentRecordId,\n };\n}\n\nfunction contentRecordDerivationBytes(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentRecordId: string;\n organizationId: string;\n}): Uint8Array<ArrayBuffer> {\n return TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN,\n payload: contentRecordDerivationPayload(input),\n }),\n );\n}\n\nexport function contentRecordAdditionalDataBytes(input: {\n blobId: string;\n chunkCount: number;\n chunkIndex: number;\n chunkPlaintextByteLength: number;\n chunkSize: number;\n contentKeyEpoch: number;\n contentRecordId: string;\n metadataHash: string;\n nonceDomainHash: string;\n organizationId: string;\n plaintextByteLength: number;\n}): Uint8Array<ArrayBuffer> {\n return TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_AAD_DOMAIN,\n payload: {\n ...contentRecordDerivationPayload(input),\n blobFormat: BLOB_ENCRYPTED_BYTES_FORMAT,\n blobFormatVersion: BLOB_ENCRYPTED_BYTES_VERSION,\n chunkCount: input.chunkCount,\n chunkIndex: input.chunkIndex,\n chunkPlaintextByteLength: input.chunkPlaintextByteLength,\n chunkSize: input.chunkSize,\n metadataHash: input.metadataHash,\n nonceDomainHash: input.nonceDomainHash,\n plaintextByteLength: input.plaintextByteLength,\n },\n }),\n );\n}\n\nexport async function deriveBlobContentRecordKey(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentKeyMaterial: CryptoKey;\n contentRecordId: string;\n organizationId: string;\n usage: \"decrypt\" | \"encrypt\";\n}): Promise<CryptoKey> {\n return crypto.subtle.deriveKey(\n {\n name: \"HKDF\",\n hash: \"SHA-256\",\n salt: BLOB_CONTENT_RECORD_HKDF_SALT,\n info: contentRecordDerivationBytes(input),\n },\n input.contentKeyMaterial,\n { name: \"AES-GCM\", length: 256 },\n false,\n [input.usage],\n );\n}\n"]}
1
+ {"version":3,"file":"blobRecordCrypto.js","sourceRoot":"","sources":["blobRecordCrypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AES_GCM_IV_BYTES,\n assertAesGcmIv,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n type KeyingCanonicalJson,\n serializeKeyingCanonicalJson,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport {\n BLOB_CONTENT_RECORD_AAD_DOMAIN,\n BLOB_CONTENT_RECORD_HKDF_SALT,\n BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN,\n BLOB_CONTENT_RECORD_NONCE_DOMAIN,\n TEXT_ENCODER,\n} from \"./types\";\n\nconst SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;\n\n// The chunk layout is part of the IV family: a persisted nonce seed can\n// outlive a chunk-size change (upload resume across an app update), and the\n// record key does not vary with the layout, so the same seed must never\n// yield the same per-chunk IVs over two different plaintext chunkings.\nexport async function deriveBlobBaseIv(input: {\n readonly chunkSize: number;\n readonly nonceSeed: Uint8Array;\n readonly plaintextSha256: string;\n}): Promise<Uint8Array<ArrayBuffer>> {\n assertAesGcmIv(input.nonceSeed, \"Blob nonce seed is invalid\");\n if (!SHA256_HEX_PATTERN.test(input.plaintextSha256)) {\n throw new Error(\"Blob plaintext SHA-256 is invalid\");\n }\n if (!Number.isInteger(input.chunkSize) || input.chunkSize <= 0) {\n throw new Error(\"Blob chunk size is invalid\");\n }\n\n const digest = new Uint8Array(\n await crypto.subtle.digest(\n \"SHA-256\",\n TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_NONCE_DOMAIN,\n payload: {\n chunkSize: input.chunkSize,\n nonceSeed: bytesToBase64(input.nonceSeed),\n plaintextSha256: input.plaintextSha256,\n },\n }),\n ),\n ),\n );\n return digest.slice(0, AES_GCM_IV_BYTES);\n}\n\nfunction contentRecordDerivationPayload(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentRecordId: string;\n organizationId: string;\n}): Record<string, KeyingCanonicalJson> {\n return {\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n organizationId: input.organizationId,\n objectKind: \"blob\",\n objectId: input.blobId,\n contentKeyEpoch: input.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId: input.contentRecordId,\n };\n}\n\nfunction contentRecordDerivationBytes(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentRecordId: string;\n organizationId: string;\n}): Uint8Array<ArrayBuffer> {\n return TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN,\n payload: contentRecordDerivationPayload(input),\n }),\n );\n}\n\nexport function contentRecordAdditionalDataBytes(input: {\n blobId: string;\n chunkCount: number;\n chunkIndex: number;\n chunkPlaintextByteLength: number;\n chunkSize: number;\n contentKeyEpoch: number;\n contentRecordId: string;\n metadataHash: string;\n nonceDomainHash: string;\n organizationId: string;\n plaintextByteLength: number;\n}): Uint8Array<ArrayBuffer> {\n return TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_AAD_DOMAIN,\n payload: {\n ...contentRecordDerivationPayload(input),\n blobFormat: BLOB_ENCRYPTED_BYTES_FORMAT,\n blobFormatVersion: BLOB_ENCRYPTED_BYTES_VERSION,\n chunkCount: input.chunkCount,\n chunkIndex: input.chunkIndex,\n chunkPlaintextByteLength: input.chunkPlaintextByteLength,\n chunkSize: input.chunkSize,\n metadataHash: input.metadataHash,\n nonceDomainHash: input.nonceDomainHash,\n plaintextByteLength: input.plaintextByteLength,\n },\n }),\n );\n}\n\nexport async function deriveBlobContentRecordKey(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentKeyMaterial: CryptoKey;\n contentRecordId: string;\n organizationId: string;\n usage: \"decrypt\" | \"encrypt\";\n}): Promise<CryptoKey> {\n return crypto.subtle.deriveKey(\n {\n name: \"HKDF\",\n hash: \"SHA-256\",\n salt: BLOB_CONTENT_RECORD_HKDF_SALT,\n info: contentRecordDerivationBytes(input),\n },\n input.contentKeyMaterial,\n { name: \"AES-GCM\", length: 256 },\n false,\n [input.usage],\n );\n}\n"]}
@@ -1,14 +1,10 @@
1
- import { AES_256_KEY_BYTES, AES_GCM_TAG_BYTES, assertAesGcmIv, BLOB_CHUNK_SIZE_BYTES, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, blobChunkPlaintextByteLength, bytesToHex, CONTENT_RECORD_ENCRYPTION_SUITE, computeBlobChunkCount, computeBlobEncryptedByteLength, computeContentRecordNonceDomainHash, createAesGcmIv, createIncrementalSha256, deriveBlobChunkIv, encodeBlobEnvelopeV2Header, joinBlobPartBytes, normalizeBlobChunkSize, } from "../../../../internal/crypto/index.js";
1
+ import { AES_256_KEY_BYTES, AES_GCM_TAG_BYTES, assertAesGcmIv, BLOB_CHUNK_SIZE_BYTES, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, blobContentMetadataHash, bytesToHex, CONTENT_RECORD_ENCRYPTION_SUITE, computeBlobChunkCount, computeBlobEncryptedByteLength, computeContentRecordNonceDomainHash, createAesGcmIv, createIncrementalSha256, deriveBlobChunkIv, encodeBlobEnvelopeV2Header, normalizeBlobChunkSize, } from "../../../../internal/crypto/index.js";
2
2
  import { bytesToBase64 } from "../../../../internal/encoding/index.js";
3
3
  import { importContentKeyMaterial } from "../../shared/contentRecordKeys.js";
4
- import { blobContentMetadataHash, contentRecordAdditionalDataBytes, deriveBlobBaseIv, deriveBlobContentRecordKey, } from "./blobRecordCrypto.js";
4
+ import { contentRecordAdditionalDataBytes, deriveBlobBaseIv, deriveBlobContentRecordKey, } from "./blobRecordCrypto.js";
5
5
  import { inspectBlobSource, readVerifiedBlobSourceChunk, } from "./blobSourceSnapshot.js";
6
+ import { createEncryptedUploadParts } from "./encryptedUploadParts.js";
6
7
  export const DEFAULT_BLOB_CHUNK_SIZE_BYTES = BLOB_CHUNK_SIZE_BYTES;
7
- function assertPartIndex(index, partCount) {
8
- if (!Number.isInteger(index) || index < 0 || index >= partCount) {
9
- throw new Error("Blob encryption part index is out of range");
10
- }
11
- }
12
8
  async function createBlobEncryptionContext(input, snapshot) {
13
9
  const chunkSize = normalizeBlobChunkSize(input.chunkSize ?? DEFAULT_BLOB_CHUNK_SIZE_BYTES);
14
10
  const plaintextByteLength = input.source.byteLength;
@@ -108,45 +104,33 @@ async function runEncryptionHashPass(source, snapshot, context) {
108
104
  }
109
105
  return bytesToHex(objectHash.digest());
110
106
  }
111
- function planPartByteLength(context, index) {
112
- assertPartIndex(index, context.partCount);
113
- const plaintextLength = blobChunkPlaintextByteLength({
107
+ function createBlobEncryptionPlan(input) {
108
+ const { context, sha256, snapshot, source } = input;
109
+ const byteLength = computeBlobEncryptedByteLength({
114
110
  chunkCount: context.partCount,
115
- chunkIndex: index,
116
- chunkSize: context.chunkSize,
111
+ headerByteLength: context.headerBytes.byteLength,
117
112
  plaintextByteLength: context.plaintextByteLength,
118
113
  });
119
- return (plaintextLength +
120
- AES_GCM_TAG_BYTES +
121
- (index === 0 ? context.headerBytes.byteLength : 0));
122
- }
123
- function createBlobEncryptionPlan(input) {
124
- const { context, sha256, snapshot, source } = input;
125
114
  return {
126
- byteLength: computeBlobEncryptedByteLength({
127
- chunkCount: context.partCount,
128
- headerByteLength: context.headerBytes.byteLength,
129
- plaintextByteLength: context.plaintextByteLength,
130
- }),
115
+ byteLength,
131
116
  chunkSize: context.chunkSize,
132
117
  metadataHash: context.metadataHash,
133
- partCount: context.partCount,
134
118
  plaintextByteLength: context.plaintextByteLength,
135
119
  plaintextSha256: snapshot.sha256,
136
120
  sha256,
137
- getPartByteLength(index) {
138
- return planPartByteLength(context, index);
139
- },
140
- async encryptPart(index) {
141
- assertPartIndex(index, context.partCount);
142
- const plaintext = await readVerifiedBlobSourceChunk({
143
- chunkIndex: index,
144
- snapshot,
145
- source,
146
- });
147
- const ciphertext = await encryptBlobChunk(context, index, plaintext);
148
- return joinBlobPartBytes(index === 0 ? context.headerBytes : null, ciphertext);
149
- },
121
+ ...createEncryptedUploadParts({
122
+ byteLength,
123
+ chunkSize: context.chunkSize + AES_GCM_TAG_BYTES,
124
+ header: context.headerBytes,
125
+ async encryptChunk(chunkIndex) {
126
+ const plaintext = await readVerifiedBlobSourceChunk({
127
+ chunkIndex,
128
+ snapshot,
129
+ source,
130
+ });
131
+ return encryptBlobChunk(context, chunkIndex, plaintext);
132
+ },
133
+ }),
150
134
  };
151
135
  }
152
136
  export async function prepareBlobEncryption(input) {
@@ -1 +1 @@
1
- {"version":3,"file":"chunkedBlobCrypto.js","sourceRoot":"","sources":["chunkedBlobCrypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AES_256_KEY_BYTES,\n AES_GCM_TAG_BYTES,\n assertAesGcmIv,\n BLOB_CHUNK_SIZE_BYTES,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n blobChunkPlaintextByteLength,\n bytesToHex,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeBlobChunkCount,\n computeBlobEncryptedByteLength,\n computeContentRecordNonceDomainHash,\n createAesGcmIv,\n createIncrementalSha256,\n deriveBlobChunkIv,\n encodeBlobEnvelopeV2Header,\n joinBlobPartBytes,\n normalizeBlobChunkSize,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport type { BlobContentKeyBundleRequest } from \"@tearleads/validators/request\";\nimport type { BlobByteSource, BlobBytes } from \"../../../blobContracts\";\nimport { importContentKeyMaterial } from \"../../shared/contentRecordKeys\";\nimport {\n blobContentMetadataHash,\n contentRecordAdditionalDataBytes,\n deriveBlobBaseIv,\n deriveBlobContentRecordKey,\n} from \"./blobRecordCrypto\";\nimport {\n type BlobSourceSnapshot,\n inspectBlobSource,\n readVerifiedBlobSourceChunk,\n} from \"./blobSourceSnapshot\";\nimport type { BlobEncryptionPlan } from \"./types\";\n\nexport const DEFAULT_BLOB_CHUNK_SIZE_BYTES = BLOB_CHUNK_SIZE_BYTES;\n\ninterface PrepareBlobEncryptionInput {\n readonly blobId: string;\n readonly chunkSize?: number | undefined;\n readonly contentKey: Uint8Array;\n readonly contentKeyBundle: BlobContentKeyBundleRequest;\n readonly nonceSeed?: Uint8Array | undefined;\n readonly organizationId: string;\n readonly source: BlobByteSource;\n readonly sourceSnapshot?: BlobSourceSnapshot | undefined;\n}\n\nfunction assertPartIndex(index: number, partCount: number): void {\n if (!Number.isInteger(index) || index < 0 || index >= partCount) {\n throw new Error(\"Blob encryption part index is out of range\");\n }\n}\n\ninterface BlobEncryptionContext {\n readonly baseIv: BlobBytes;\n readonly blobId: string;\n readonly chunkSize: number;\n readonly contentKeyEpoch: number;\n readonly contentRecordId: string;\n readonly headerBytes: BlobBytes;\n readonly metadataHash: string;\n readonly nonceDomainHash: string;\n readonly organizationId: string;\n readonly partCount: number;\n readonly plaintextByteLength: number;\n readonly recordKey: CryptoKey;\n}\n\nasync function createBlobEncryptionContext(\n input: PrepareBlobEncryptionInput,\n snapshot: BlobSourceSnapshot,\n): Promise<BlobEncryptionContext> {\n const chunkSize = normalizeBlobChunkSize(\n input.chunkSize ?? DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n );\n const plaintextByteLength = input.source.byteLength;\n const partCount = computeBlobChunkCount(plaintextByteLength, chunkSize);\n const contentRecordId = input.blobId;\n const { contentKeyEpoch } = input.contentKeyBundle;\n const nonceSeed = new Uint8Array(input.nonceSeed ?? createAesGcmIv());\n const baseIv = await deriveBlobBaseIv({\n chunkSize,\n nonceSeed,\n plaintextSha256: snapshot.sha256,\n });\n\n const [nonceDomainHash, metadataHash] = await Promise.all([\n computeContentRecordNonceDomainHash({\n version: 1,\n organizationId: input.organizationId,\n objectKind: \"blob\",\n objectId: input.blobId,\n contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId,\n }),\n blobContentMetadataHash({\n blobId: input.blobId,\n byteLength: plaintextByteLength,\n chunkCount: partCount,\n chunkSize,\n contentKeyEpoch,\n }),\n ]);\n const contentKeyMaterial = await importContentKeyMaterial(input.contentKey);\n const recordKey = await deriveBlobContentRecordKey({\n blobId: input.blobId,\n contentKeyEpoch,\n contentKeyMaterial,\n contentRecordId,\n organizationId: input.organizationId,\n usage: \"encrypt\",\n });\n return {\n baseIv,\n blobId: input.blobId,\n chunkSize,\n contentKeyEpoch,\n contentRecordId,\n headerBytes: encodeBlobEnvelopeV2Header({\n blobId: input.blobId,\n byteLength: plaintextByteLength,\n chunkCount: partCount,\n chunkSize,\n contentKeyEpoch,\n contentRecordId,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n format: BLOB_ENCRYPTED_BYTES_FORMAT,\n iv: bytesToBase64(baseIv),\n metadataHash,\n nonceDomainHash,\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n }),\n metadataHash,\n nonceDomainHash,\n organizationId: input.organizationId,\n partCount,\n plaintextByteLength,\n recordKey,\n };\n}\n\nasync function encryptBlobChunk(\n context: BlobEncryptionContext,\n chunkIndex: number,\n plaintext: BlobBytes,\n): Promise<BlobBytes> {\n return new Uint8Array(\n await crypto.subtle.encrypt(\n {\n name: \"AES-GCM\",\n iv: deriveBlobChunkIv(context.baseIv, chunkIndex),\n additionalData: contentRecordAdditionalDataBytes({\n blobId: context.blobId,\n chunkCount: context.partCount,\n chunkIndex,\n chunkPlaintextByteLength: plaintext.byteLength,\n chunkSize: context.chunkSize,\n contentKeyEpoch: context.contentKeyEpoch,\n contentRecordId: context.contentRecordId,\n metadataHash: context.metadataHash,\n nonceDomainHash: context.nonceDomainHash,\n organizationId: context.organizationId,\n plaintextByteLength: context.plaintextByteLength,\n }),\n },\n context.recordKey,\n plaintext,\n ),\n );\n}\n\nasync function runEncryptionHashPass(\n source: BlobByteSource,\n snapshot: BlobSourceSnapshot,\n context: BlobEncryptionContext,\n): Promise<string> {\n const objectHash = createIncrementalSha256();\n objectHash.update(context.headerBytes);\n for (let chunkIndex = 0; chunkIndex < context.partCount; chunkIndex += 1) {\n const plaintext = await readVerifiedBlobSourceChunk({\n chunkIndex,\n snapshot,\n source,\n });\n objectHash.update(await encryptBlobChunk(context, chunkIndex, plaintext));\n }\n return bytesToHex(objectHash.digest());\n}\n\nfunction planPartByteLength(\n context: BlobEncryptionContext,\n index: number,\n): number {\n assertPartIndex(index, context.partCount);\n const plaintextLength = blobChunkPlaintextByteLength({\n chunkCount: context.partCount,\n chunkIndex: index,\n chunkSize: context.chunkSize,\n plaintextByteLength: context.plaintextByteLength,\n });\n return (\n plaintextLength +\n AES_GCM_TAG_BYTES +\n (index === 0 ? context.headerBytes.byteLength : 0)\n );\n}\n\nfunction createBlobEncryptionPlan(input: {\n readonly context: BlobEncryptionContext;\n readonly sha256: string;\n readonly snapshot: BlobSourceSnapshot;\n readonly source: BlobByteSource;\n}): BlobEncryptionPlan {\n const { context, sha256, snapshot, source } = input;\n return {\n byteLength: computeBlobEncryptedByteLength({\n chunkCount: context.partCount,\n headerByteLength: context.headerBytes.byteLength,\n plaintextByteLength: context.plaintextByteLength,\n }),\n chunkSize: context.chunkSize,\n metadataHash: context.metadataHash,\n partCount: context.partCount,\n plaintextByteLength: context.plaintextByteLength,\n plaintextSha256: snapshot.sha256,\n sha256,\n getPartByteLength(index) {\n return planPartByteLength(context, index);\n },\n async encryptPart(index) {\n assertPartIndex(index, context.partCount);\n const plaintext = await readVerifiedBlobSourceChunk({\n chunkIndex: index,\n snapshot,\n source,\n });\n const ciphertext = await encryptBlobChunk(context, index, plaintext);\n return joinBlobPartBytes(\n index === 0 ? context.headerBytes : null,\n ciphertext,\n );\n },\n };\n}\n\nexport async function prepareBlobEncryption(\n input: PrepareBlobEncryptionInput,\n): Promise<BlobEncryptionPlan> {\n if (input.contentKey.byteLength !== AES_256_KEY_BYTES) {\n throw new Error(\"Blob content key must be 32 bytes\");\n }\n if (input.nonceSeed) {\n assertAesGcmIv(input.nonceSeed, \"Blob nonce seed is invalid\");\n }\n const chunkSize = normalizeBlobChunkSize(\n input.chunkSize ?? DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n );\n const snapshot =\n input.sourceSnapshot ?? (await inspectBlobSource(input.source, chunkSize));\n const context = await createBlobEncryptionContext(input, snapshot);\n if (\n snapshot.byteLength !== context.plaintextByteLength ||\n snapshot.chunkSize !== context.chunkSize\n ) {\n throw new Error(\"Blob source snapshot does not match encryption layout\");\n }\n const sha256 = await runEncryptionHashPass(input.source, snapshot, context);\n return createBlobEncryptionPlan({\n context,\n sha256,\n snapshot,\n source: input.source,\n });\n}\n"]}
1
+ {"version":3,"file":"chunkedBlobCrypto.js","sourceRoot":"","sources":["chunkedBlobCrypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AES_256_KEY_BYTES,\n AES_GCM_TAG_BYTES,\n assertAesGcmIv,\n BLOB_CHUNK_SIZE_BYTES,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n blobContentMetadataHash,\n bytesToHex,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeBlobChunkCount,\n computeBlobEncryptedByteLength,\n computeContentRecordNonceDomainHash,\n createAesGcmIv,\n createIncrementalSha256,\n deriveBlobChunkIv,\n encodeBlobEnvelopeV2Header,\n normalizeBlobChunkSize,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport type { BlobContentKeyBundleRequest } from \"@tearleads/validators/request\";\nimport type { BlobByteSource, BlobBytes } from \"../../../blobContracts\";\nimport { importContentKeyMaterial } from \"../../shared/contentRecordKeys\";\nimport {\n contentRecordAdditionalDataBytes,\n deriveBlobBaseIv,\n deriveBlobContentRecordKey,\n} from \"./blobRecordCrypto\";\nimport {\n type BlobSourceSnapshot,\n inspectBlobSource,\n readVerifiedBlobSourceChunk,\n} from \"./blobSourceSnapshot\";\nimport { createEncryptedUploadParts } from \"./encryptedUploadParts\";\nimport type { BlobEncryptionPlan } from \"./types\";\n\nexport const DEFAULT_BLOB_CHUNK_SIZE_BYTES = BLOB_CHUNK_SIZE_BYTES;\n\ninterface PrepareBlobEncryptionInput {\n readonly blobId: string;\n readonly chunkSize?: number | undefined;\n readonly contentKey: Uint8Array;\n readonly contentKeyBundle: BlobContentKeyBundleRequest;\n readonly nonceSeed?: Uint8Array | undefined;\n readonly organizationId: string;\n readonly source: BlobByteSource;\n readonly sourceSnapshot?: BlobSourceSnapshot | undefined;\n}\n\ninterface BlobEncryptionContext {\n readonly baseIv: BlobBytes;\n readonly blobId: string;\n readonly chunkSize: number;\n readonly contentKeyEpoch: number;\n readonly contentRecordId: string;\n readonly headerBytes: BlobBytes;\n readonly metadataHash: string;\n readonly nonceDomainHash: string;\n readonly organizationId: string;\n readonly partCount: number;\n readonly plaintextByteLength: number;\n readonly recordKey: CryptoKey;\n}\n\nasync function createBlobEncryptionContext(\n input: PrepareBlobEncryptionInput,\n snapshot: BlobSourceSnapshot,\n): Promise<BlobEncryptionContext> {\n const chunkSize = normalizeBlobChunkSize(\n input.chunkSize ?? DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n );\n const plaintextByteLength = input.source.byteLength;\n const partCount = computeBlobChunkCount(plaintextByteLength, chunkSize);\n const contentRecordId = input.blobId;\n const { contentKeyEpoch } = input.contentKeyBundle;\n const nonceSeed = new Uint8Array(input.nonceSeed ?? createAesGcmIv());\n const baseIv = await deriveBlobBaseIv({\n chunkSize,\n nonceSeed,\n plaintextSha256: snapshot.sha256,\n });\n\n const [nonceDomainHash, metadataHash] = await Promise.all([\n computeContentRecordNonceDomainHash({\n version: 1,\n organizationId: input.organizationId,\n objectKind: \"blob\",\n objectId: input.blobId,\n contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId,\n }),\n blobContentMetadataHash({\n blobId: input.blobId,\n byteLength: plaintextByteLength,\n chunkCount: partCount,\n chunkSize,\n contentKeyEpoch,\n }),\n ]);\n const contentKeyMaterial = await importContentKeyMaterial(input.contentKey);\n const recordKey = await deriveBlobContentRecordKey({\n blobId: input.blobId,\n contentKeyEpoch,\n contentKeyMaterial,\n contentRecordId,\n organizationId: input.organizationId,\n usage: \"encrypt\",\n });\n return {\n baseIv,\n blobId: input.blobId,\n chunkSize,\n contentKeyEpoch,\n contentRecordId,\n headerBytes: encodeBlobEnvelopeV2Header({\n blobId: input.blobId,\n byteLength: plaintextByteLength,\n chunkCount: partCount,\n chunkSize,\n contentKeyEpoch,\n contentRecordId,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n format: BLOB_ENCRYPTED_BYTES_FORMAT,\n iv: bytesToBase64(baseIv),\n metadataHash,\n nonceDomainHash,\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n }),\n metadataHash,\n nonceDomainHash,\n organizationId: input.organizationId,\n partCount,\n plaintextByteLength,\n recordKey,\n };\n}\n\nasync function encryptBlobChunk(\n context: BlobEncryptionContext,\n chunkIndex: number,\n plaintext: BlobBytes,\n): Promise<BlobBytes> {\n return new Uint8Array(\n await crypto.subtle.encrypt(\n {\n name: \"AES-GCM\",\n iv: deriveBlobChunkIv(context.baseIv, chunkIndex),\n additionalData: contentRecordAdditionalDataBytes({\n blobId: context.blobId,\n chunkCount: context.partCount,\n chunkIndex,\n chunkPlaintextByteLength: plaintext.byteLength,\n chunkSize: context.chunkSize,\n contentKeyEpoch: context.contentKeyEpoch,\n contentRecordId: context.contentRecordId,\n metadataHash: context.metadataHash,\n nonceDomainHash: context.nonceDomainHash,\n organizationId: context.organizationId,\n plaintextByteLength: context.plaintextByteLength,\n }),\n },\n context.recordKey,\n plaintext,\n ),\n );\n}\n\nasync function runEncryptionHashPass(\n source: BlobByteSource,\n snapshot: BlobSourceSnapshot,\n context: BlobEncryptionContext,\n): Promise<string> {\n const objectHash = createIncrementalSha256();\n objectHash.update(context.headerBytes);\n for (let chunkIndex = 0; chunkIndex < context.partCount; chunkIndex += 1) {\n const plaintext = await readVerifiedBlobSourceChunk({\n chunkIndex,\n snapshot,\n source,\n });\n objectHash.update(await encryptBlobChunk(context, chunkIndex, plaintext));\n }\n return bytesToHex(objectHash.digest());\n}\n\nfunction createBlobEncryptionPlan(input: {\n readonly context: BlobEncryptionContext;\n readonly sha256: string;\n readonly snapshot: BlobSourceSnapshot;\n readonly source: BlobByteSource;\n}): BlobEncryptionPlan {\n const { context, sha256, snapshot, source } = input;\n const byteLength = computeBlobEncryptedByteLength({\n chunkCount: context.partCount,\n headerByteLength: context.headerBytes.byteLength,\n plaintextByteLength: context.plaintextByteLength,\n });\n return {\n byteLength,\n chunkSize: context.chunkSize,\n metadataHash: context.metadataHash,\n plaintextByteLength: context.plaintextByteLength,\n plaintextSha256: snapshot.sha256,\n sha256,\n ...createEncryptedUploadParts({\n byteLength,\n chunkSize: context.chunkSize + AES_GCM_TAG_BYTES,\n header: context.headerBytes,\n async encryptChunk(chunkIndex) {\n const plaintext = await readVerifiedBlobSourceChunk({\n chunkIndex,\n snapshot,\n source,\n });\n return encryptBlobChunk(context, chunkIndex, plaintext);\n },\n }),\n };\n}\n\nexport async function prepareBlobEncryption(\n input: PrepareBlobEncryptionInput,\n): Promise<BlobEncryptionPlan> {\n if (input.contentKey.byteLength !== AES_256_KEY_BYTES) {\n throw new Error(\"Blob content key must be 32 bytes\");\n }\n if (input.nonceSeed) {\n assertAesGcmIv(input.nonceSeed, \"Blob nonce seed is invalid\");\n }\n const chunkSize = normalizeBlobChunkSize(\n input.chunkSize ?? DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n );\n const snapshot =\n input.sourceSnapshot ?? (await inspectBlobSource(input.source, chunkSize));\n const context = await createBlobEncryptionContext(input, snapshot);\n if (\n snapshot.byteLength !== context.plaintextByteLength ||\n snapshot.chunkSize !== context.chunkSize\n ) {\n throw new Error(\"Blob source snapshot does not match encryption layout\");\n }\n const sha256 = await runEncryptionHashPass(input.source, snapshot, context);\n return createBlobEncryptionPlan({\n context,\n sha256,\n snapshot,\n source: input.source,\n });\n}\n"]}
@@ -1,3 +1,3 @@
1
- export { blobContentMetadataHash, contentRecordAdditionalDataBytes, deriveBlobContentRecordKey, } from "./blobRecordCrypto.js";
1
+ export { contentRecordAdditionalDataBytes, deriveBlobContentRecordKey, } from "./blobRecordCrypto.js";
2
2
  export { DEFAULT_BLOB_CHUNK_SIZE_BYTES, prepareBlobEncryption, } from "./chunkedBlobCrypto.js";
3
3
  //# sourceMappingURL=crypto.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,gCAAgC,EAChC,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC","sourcesContent":["export {\n blobContentMetadataHash,\n contentRecordAdditionalDataBytes,\n deriveBlobContentRecordKey,\n} from \"./blobRecordCrypto\";\nexport {\n DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n prepareBlobEncryption,\n} from \"./chunkedBlobCrypto\";\nexport type { BlobEncryptionPlan } from \"./types\";\n"]}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gCAAgC,EAChC,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC","sourcesContent":["export {\n contentRecordAdditionalDataBytes,\n deriveBlobContentRecordKey,\n} from \"./blobRecordCrypto\";\nexport {\n DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n prepareBlobEncryption,\n} from \"./chunkedBlobCrypto\";\nexport type { BlobEncryptionPlan } from \"./types\";\n"]}
@@ -0,0 +1,42 @@
1
+ /** Repartition the envelope without changing its encryption or buffering the file. */
2
+ export function createEncryptedUploadParts(input) {
3
+ // Including the header in the fixed transport size keeps even a maximum-size
4
+ // envelope within 10,000 parts. R2 requires equal sizes for all nonfinal parts.
5
+ const partSize = input.chunkSize + input.header.byteLength;
6
+ const partCount = Math.ceil(input.byteLength / partSize);
7
+ function getPartByteLength(index) {
8
+ if (!Number.isInteger(index) || index < 0 || index >= partCount) {
9
+ throw new Error("Blob encryption part index is out of range");
10
+ }
11
+ return Math.min(partSize, input.byteLength - index * partSize);
12
+ }
13
+ return {
14
+ partCount,
15
+ getPartByteLength,
16
+ async encryptPart(index) {
17
+ const output = new Uint8Array(getPartByteLength(index));
18
+ const start = index * partSize;
19
+ const end = start + output.byteLength;
20
+ if (start < input.header.byteLength) {
21
+ output.set(input.header.subarray(start, Math.min(end, input.header.byteLength)));
22
+ }
23
+ let position = Math.max(start, input.header.byteLength);
24
+ while (position < end) {
25
+ const chunkIndex = Math.floor((position - input.header.byteLength) / input.chunkSize);
26
+ const chunkStart = input.header.byteLength + chunkIndex * input.chunkSize;
27
+ // Adjacent transport parts can re-read and re-encrypt a shared chunk.
28
+ // Independent calls keep memory bounded during concurrent upload/resume;
29
+ // the tradeoff is up to two encryptions per chunk in this upload pass.
30
+ const chunk = await input.encryptChunk(chunkIndex);
31
+ const offset = position - chunkStart;
32
+ const length = Math.min(chunk.byteLength - offset, end - position);
33
+ if (length <= 0)
34
+ throw new Error("Encrypted blob chunk is shorter than its layout");
35
+ output.set(chunk.subarray(offset, offset + length), position - start);
36
+ position += length;
37
+ }
38
+ return output;
39
+ },
40
+ };
41
+ }
42
+ //# sourceMappingURL=encryptedUploadParts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryptedUploadParts.js","sourceRoot":"","sources":["encryptedUploadParts.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,MAAM,UAAU,0BAA0B,CAAC,KAK1C;IACC,6EAA6E;IAC7E,gFAAgF;IAChF,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC;IAC3D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,QAAQ,CAAC,CAAC;IACzD,SAAS,iBAAiB,CAAC,KAAa;QACtC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,SAAS,EAAE,CAAC;YAChE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAChE,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,GAAG,KAAK,GAAG,QAAQ,CAAC,CAAC;IACjE,CAAC;IACD,OAAO;QACL,SAAS;QACT,iBAAiB;QACjB,KAAK,CAAC,WAAW,CAAC,KAAa;YAC7B,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;YACxD,MAAM,KAAK,GAAG,KAAK,GAAG,QAAQ,CAAC;YAC/B,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC;YACtC,IAAI,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;gBACpC,MAAM,CAAC,GAAG,CACR,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CACrE,CAAC;YACJ,CAAC;YACD,IAAI,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACxD,OAAO,QAAQ,GAAG,GAAG,EAAE,CAAC;gBACtB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAC3B,CAAC,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,KAAK,CAAC,SAAS,CACvD,CAAC;gBACF,MAAM,UAAU,GACd,KAAK,CAAC,MAAM,CAAC,UAAU,GAAG,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC;gBACzD,sEAAsE;gBACtE,yEAAyE;gBACzE,uEAAuE;gBACvE,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;gBACnD,MAAM,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;gBACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,GAAG,MAAM,EAAE,GAAG,GAAG,QAAQ,CAAC,CAAC;gBACnE,IAAI,MAAM,IAAI,CAAC;oBACb,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;gBACrE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC,CAAC;gBACtE,QAAQ,IAAI,MAAM,CAAC;YACrB,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { BlobBytes } from \"../../../blobContracts\";\n\n/** Repartition the envelope without changing its encryption or buffering the file. */\nexport function createEncryptedUploadParts(input: {\n readonly byteLength: number;\n readonly chunkSize: number;\n readonly header: BlobBytes;\n readonly encryptChunk: (index: number) => Promise<BlobBytes>;\n}) {\n // Including the header in the fixed transport size keeps even a maximum-size\n // envelope within 10,000 parts. R2 requires equal sizes for all nonfinal parts.\n const partSize = input.chunkSize + input.header.byteLength;\n const partCount = Math.ceil(input.byteLength / partSize);\n function getPartByteLength(index: number): number {\n if (!Number.isInteger(index) || index < 0 || index >= partCount) {\n throw new Error(\"Blob encryption part index is out of range\");\n }\n return Math.min(partSize, input.byteLength - index * partSize);\n }\n return {\n partCount,\n getPartByteLength,\n async encryptPart(index: number): Promise<BlobBytes> {\n const output = new Uint8Array(getPartByteLength(index));\n const start = index * partSize;\n const end = start + output.byteLength;\n if (start < input.header.byteLength) {\n output.set(\n input.header.subarray(start, Math.min(end, input.header.byteLength)),\n );\n }\n let position = Math.max(start, input.header.byteLength);\n while (position < end) {\n const chunkIndex = Math.floor(\n (position - input.header.byteLength) / input.chunkSize,\n );\n const chunkStart =\n input.header.byteLength + chunkIndex * input.chunkSize;\n // Adjacent transport parts can re-read and re-encrypt a shared chunk.\n // Independent calls keep memory bounded during concurrent upload/resume;\n // the tradeoff is up to two encryptions per chunk in this upload pass.\n const chunk = await input.encryptChunk(chunkIndex);\n const offset = position - chunkStart;\n const length = Math.min(chunk.byteLength - offset, end - position);\n if (length <= 0)\n throw new Error(\"Encrypted blob chunk is shorter than its layout\");\n output.set(chunk.subarray(offset, offset + length), position - start);\n position += length;\n }\n return output;\n },\n };\n}\n"]}
@@ -8,7 +8,6 @@ import type { ExecSql } from "../../../sqlite/sqlSchema.js";
8
8
  import type { DocumentCreateAuthor } from "../../shared/types.js";
9
9
  export declare const BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN = "tearleads.blob.content-record-key-info";
10
10
  export declare const BLOB_CONTENT_RECORD_AAD_DOMAIN = "tearleads.blob.content-record-aad";
11
- export declare const BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN = "tearleads.blob.content-record-metadata";
12
11
  export declare const BLOB_CONTENT_RECORD_NONCE_DOMAIN = "tearleads.blob.content-record-nonce";
13
12
  export declare const BLOB_CONTENT_RECORD_HKDF_SALT: Uint8Array<ArrayBuffer>;
14
13
  export declare const TEXT_ENCODER: TextEncoder;
@@ -126,7 +125,7 @@ export interface UploadDocumentAttachmentInput {
126
125
  writerProjection?: DocumentWriterProjectionResponse | undefined;
127
126
  }
128
127
  export interface MultipartBlobUploadOptions {
129
- /** Exactly 5 MiB of plaintext per independently encrypted multipart chunk. */
128
+ /** Exactly 5 MiB per plaintext encryption chunk; transport parts include envelope overhead. */
130
129
  readonly partSize: number;
131
130
  readonly resumeStageId?: string | undefined;
132
131
  readonly uploadConcurrency?: number | undefined;
@@ -1,6 +1,5 @@
1
1
  export const BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN = "tearleads.blob.content-record-key-info";
2
2
  export const BLOB_CONTENT_RECORD_AAD_DOMAIN = "tearleads.blob.content-record-aad";
3
- export const BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN = "tearleads.blob.content-record-metadata";
4
3
  export const BLOB_CONTENT_RECORD_NONCE_DOMAIN = "tearleads.blob.content-record-nonce";
5
4
  export const BLOB_CONTENT_RECORD_HKDF_SALT = new TextEncoder().encode("tearleads.blob.content-record-hkdf-salt");
6
5
  export const TEXT_ENCODER = new TextEncoder();
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAmCA,MAAM,CAAC,MAAM,mCAAmC,GAC9C,wCAAwC,CAAC;AAC3C,MAAM,CAAC,MAAM,8BAA8B,GACzC,mCAAmC,CAAC;AACtC,MAAM,CAAC,MAAM,wCAAwC,GACnD,wCAAwC,CAAC;AAC3C,MAAM,CAAC,MAAM,gCAAgC,GAC3C,qCAAqC,CAAC;AACxC,MAAM,CAAC,MAAM,6BAA6B,GACxC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,yCAAyC,CAAC,CAAC;AACtE,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC","sourcesContent":["import type {\n RequestFailureKind,\n UploadMultipartBlobPartBytesRequest,\n} from \"@tearleads/api-client\";\nimport type {\n BlobContentKeyTarget,\n BlobEnvelopeHeaderRecord,\n BlobEnvelopeRecord,\n WriteHeader,\n} from \"@tearleads/crypto\";\nimport type {\n BlobAttachmentBindRequest,\n BlobAttachmentDetachRequest,\n BlobContentKeyBundleRequest,\n CompleteMultipartBlobStageRequest,\n InitiateMultipartBlobStageRequest,\n} from \"@tearleads/validators/request\";\nimport type {\n BlobAttachmentBindResponse,\n BlobAttachmentDetachResponse,\n BlobAttachmentSummary,\n CompleteMultipartBlobStageResponse,\n DocumentWriterProjectionResponse,\n InitiateMultipartBlobStageResponse,\n MultipartBlobStageStatusResponse,\n UploadMultipartBlobPartResponse,\n} from \"@tearleads/validators/response\";\nimport type { BlobByteSourceInput, BlobBytes } from \"../../../blobContracts\";\nimport type {\n ProjectionUserKeyResolver,\n ReferencedPrincipalPolicyWarmer,\n} from \"../../../keyingProjectionVerification\";\nimport type { ExecSql } from \"../../../sqlite/sqlSchema\";\nimport type { DocumentCreateAuthor } from \"../../shared/types\";\n\nexport const BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN =\n \"tearleads.blob.content-record-key-info\";\nexport const BLOB_CONTENT_RECORD_AAD_DOMAIN =\n \"tearleads.blob.content-record-aad\";\nexport const BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN =\n \"tearleads.blob.content-record-metadata\";\nexport const BLOB_CONTENT_RECORD_NONCE_DOMAIN =\n \"tearleads.blob.content-record-nonce\";\nexport const BLOB_CONTENT_RECORD_HKDF_SALT: Uint8Array<ArrayBuffer> =\n new TextEncoder().encode(\"tearleads.blob.content-record-hkdf-salt\");\nexport const TEXT_ENCODER = new TextEncoder();\n\nexport interface BlobAttachmentRequestOptions {\n readonly expectedPaymentRequiredOrganizationId?: string | undefined;\n}\n\nexport interface BlobAttachmentApi {\n bindBlobAttachment(\n blobId: string,\n input: BlobAttachmentBindRequest,\n options?: BlobAttachmentRequestOptions,\n ): Promise<BlobAttachmentBindResponse | null>;\n clearWriterProjectionCaches?(): void;\n evictDocumentWriterProjection?(documentId: string): void;\n getRequestFailure?(input: {\n method: \"DELETE\" | \"GET\" | \"POST\" | \"PUT\";\n path: string;\n }): {\n readonly code?: string | undefined;\n readonly kind: RequestFailureKind;\n readonly message: string;\n readonly status: number | null;\n } | null;\n completeMultipartBlobStage(\n stageId: string,\n input: CompleteMultipartBlobStageRequest,\n ): Promise<CompleteMultipartBlobStageResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n getMultipartBlobStage(\n stageId: string,\n ): Promise<MultipartBlobStageStatusResponse | null>;\n initiateMultipartBlobStage(\n input: InitiateMultipartBlobStageRequest,\n ): Promise<InitiateMultipartBlobStageResponse | null>;\n uploadMultipartBlobPartBytes(\n stageId: string,\n partNumber: number,\n input: UploadMultipartBlobPartBytesRequest,\n ): Promise<UploadMultipartBlobPartResponse | null>;\n}\n\nexport interface BlobAttachmentDetachApi {\n detachBlobAttachment(\n blobId: string,\n bindingId: string,\n input: BlobAttachmentDetachRequest,\n options?: BlobAttachmentRequestOptions,\n ): Promise<BlobAttachmentDetachResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n}\n\nexport type { BlobContentKeyTarget };\n\n/**\n * Aliases of the crypto parser's own types. The parser is shared with the API,\n * so restating these shapes here would let the two drift apart silently.\n */\nexport type BlobEncryptedBytesRecord = BlobEnvelopeRecord;\n\n/**\n * The envelope as the header establishes it. Streaming readers hold this\n * before any ciphertext is read, so it has no `chunks`: an empty array there\n * would read as an empty object rather than an unread one.\n */\nexport type BlobEncryptedBytesHeader = BlobEnvelopeHeaderRecord;\n\nexport type BlobEncryptedChunk = BlobEnvelopeRecord[\"chunks\"][number];\n\nexport interface BlobSourceSnapshot {\n readonly byteLength: number;\n readonly chunkSha256: readonly string[];\n readonly chunkSize: number;\n readonly sha256: string;\n}\n\nexport interface BlobEncryptionPlan {\n readonly byteLength: number;\n readonly chunkSize: number;\n readonly metadataHash: string;\n readonly partCount: number;\n readonly plaintextByteLength: number;\n readonly plaintextSha256: string;\n readonly sha256: string;\n encryptPart(index: number): Promise<BlobBytes>;\n getPartByteLength(index: number): number;\n}\n\nexport interface DocumentManifestIdentity {\n documentId: string;\n manifestHash: string;\n organizationId: string;\n}\n\nexport interface BlobAttachmentMaterial {\n blobAccessManifestHash: string;\n contentKeyBundle: BlobContentKeyBundleRequest;\n encrypted: BlobEncryptionPlan;\n manifestIdentity: DocumentManifestIdentity;\n targetHash: string;\n targets: BlobContentKeyTarget[];\n writerProjection: DocumentWriterProjectionResponse;\n}\n\n/**\n * Progress for an in-flight multipart blob upload.\n */\nexport interface MultipartUploadProgress {\n readonly bytesTotal: number;\n readonly bytesUploaded: number;\n readonly partsCompleted: number;\n readonly partsTotal: number;\n}\n\nexport type MultipartUploadProgressListener = (\n progress: MultipartUploadProgress,\n) => void;\n\n/**\n * Fires once a multipart stage has been opened (or resumed), with its id and\n * plaintext chunk size. Callers persist both so a later attempt can reproduce\n * the same encrypted parts and resume instead of orphaning the partial upload.\n */\nexport type MultipartStageResolvedListener = (input: {\n readonly partSize: number;\n readonly stageId: string;\n}) => void | Promise<void>;\n\nexport interface UploadDocumentAttachmentInput {\n apiClient: BlobAttachmentApi;\n author: DocumentCreateAuthor;\n blobId?: string | undefined;\n bindingId?: string | undefined;\n bytes: BlobByteSourceInput;\n contentKey?: Uint8Array | undefined;\n contentKeyEpoch?: number | undefined;\n documentId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n expectedBindingId: string | null;\n isRemoteSyncBlocked?: ((organizationId: string) => boolean) | undefined;\n // A persisted nonce seed. The plaintext digest derives the envelope IV from\n // it, then each chunk derives a unique IV. Same-source retries reproduce the\n // encrypted parts; changed bytes cannot reuse their AES-GCM nonces.\n nonceSeed?: Uint8Array | undefined;\n multipart?: MultipartBlobUploadOptions | undefined;\n onMultipartProgress?: MultipartUploadProgressListener | undefined;\n onStageResolved?: MultipartStageResolvedListener | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n slotId: string;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface MultipartBlobUploadOptions {\n /** Exactly 5 MiB of plaintext per independently encrypted multipart chunk. */\n readonly partSize: number;\n // The id of a previously opened stage to resume. When set, the upload refetches\n // that stage and skips parts the server already has instead of opening a new\n // one; if the stage is gone (expired), it falls back to a fresh stage.\n readonly resumeStageId?: string | undefined;\n readonly uploadConcurrency?: number | undefined;\n}\n\nexport interface UploadDocumentAttachmentResult {\n blobId: string;\n bindingId: string;\n request: BlobAttachmentBindRequest;\n response: BlobAttachmentBindResponse;\n sha256: string;\n byteLength: number;\n writeHeader: WriteHeader;\n writeHeaderHash: string;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DetachDocumentAttachmentInput {\n apiClient: BlobAttachmentDetachApi;\n author: DocumentCreateAuthor;\n bindingId: string;\n blobId: string;\n documentId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n isRemoteSyncBlocked?: ((organizationId: string) => boolean) | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n slotId: string;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface DetachDocumentAttachmentResult {\n request: BlobAttachmentDetachRequest;\n response: BlobAttachmentDetachResponse;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DecryptDocumentAttachmentBlobInput {\n binding: BlobAttachmentSummary;\n encryptedBytes: BlobBytes;\n expectedDocumentId: string;\n expectedSlotId: string;\n execSql: ExecSql;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n targetSecretKey: Uint8Array;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection: DocumentWriterProjectionResponse;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAmCA,MAAM,CAAC,MAAM,mCAAmC,GAC9C,wCAAwC,CAAC;AAC3C,MAAM,CAAC,MAAM,8BAA8B,GACzC,mCAAmC,CAAC;AACtC,MAAM,CAAC,MAAM,gCAAgC,GAC3C,qCAAqC,CAAC;AACxC,MAAM,CAAC,MAAM,6BAA6B,GACxC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,yCAAyC,CAAC,CAAC;AACtE,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC","sourcesContent":["import type {\n RequestFailureKind,\n UploadMultipartBlobPartBytesRequest,\n} from \"@tearleads/api-client\";\nimport type {\n BlobContentKeyTarget,\n BlobEnvelopeHeaderRecord,\n BlobEnvelopeRecord,\n WriteHeader,\n} from \"@tearleads/crypto\";\nimport type {\n BlobAttachmentBindRequest,\n BlobAttachmentDetachRequest,\n BlobContentKeyBundleRequest,\n CompleteMultipartBlobStageRequest,\n InitiateMultipartBlobStageRequest,\n} from \"@tearleads/validators/request\";\nimport type {\n BlobAttachmentBindResponse,\n BlobAttachmentDetachResponse,\n BlobAttachmentSummary,\n CompleteMultipartBlobStageResponse,\n DocumentWriterProjectionResponse,\n InitiateMultipartBlobStageResponse,\n MultipartBlobStageStatusResponse,\n UploadMultipartBlobPartResponse,\n} from \"@tearleads/validators/response\";\nimport type { BlobByteSourceInput, BlobBytes } from \"../../../blobContracts\";\nimport type {\n ProjectionUserKeyResolver,\n ReferencedPrincipalPolicyWarmer,\n} from \"../../../keyingProjectionVerification\";\nimport type { ExecSql } from \"../../../sqlite/sqlSchema\";\nimport type { DocumentCreateAuthor } from \"../../shared/types\";\n\nexport const BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN =\n \"tearleads.blob.content-record-key-info\";\nexport const BLOB_CONTENT_RECORD_AAD_DOMAIN =\n \"tearleads.blob.content-record-aad\";\nexport const BLOB_CONTENT_RECORD_NONCE_DOMAIN =\n \"tearleads.blob.content-record-nonce\";\nexport const BLOB_CONTENT_RECORD_HKDF_SALT: Uint8Array<ArrayBuffer> =\n new TextEncoder().encode(\"tearleads.blob.content-record-hkdf-salt\");\nexport const TEXT_ENCODER = new TextEncoder();\n\nexport interface BlobAttachmentRequestOptions {\n readonly expectedPaymentRequiredOrganizationId?: string | undefined;\n}\n\nexport interface BlobAttachmentApi {\n bindBlobAttachment(\n blobId: string,\n input: BlobAttachmentBindRequest,\n options?: BlobAttachmentRequestOptions,\n ): Promise<BlobAttachmentBindResponse | null>;\n clearWriterProjectionCaches?(): void;\n evictDocumentWriterProjection?(documentId: string): void;\n getRequestFailure?(input: {\n method: \"DELETE\" | \"GET\" | \"POST\" | \"PUT\";\n path: string;\n }): {\n readonly code?: string | undefined;\n readonly kind: RequestFailureKind;\n readonly message: string;\n readonly status: number | null;\n } | null;\n completeMultipartBlobStage(\n stageId: string,\n input: CompleteMultipartBlobStageRequest,\n ): Promise<CompleteMultipartBlobStageResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n getMultipartBlobStage(\n stageId: string,\n ): Promise<MultipartBlobStageStatusResponse | null>;\n initiateMultipartBlobStage(\n input: InitiateMultipartBlobStageRequest,\n ): Promise<InitiateMultipartBlobStageResponse | null>;\n uploadMultipartBlobPartBytes(\n stageId: string,\n partNumber: number,\n input: UploadMultipartBlobPartBytesRequest,\n ): Promise<UploadMultipartBlobPartResponse | null>;\n}\n\nexport interface BlobAttachmentDetachApi {\n detachBlobAttachment(\n blobId: string,\n bindingId: string,\n input: BlobAttachmentDetachRequest,\n options?: BlobAttachmentRequestOptions,\n ): Promise<BlobAttachmentDetachResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n}\n\nexport type { BlobContentKeyTarget };\n\n/**\n * Aliases of the crypto parser's own types. The parser is shared with the API,\n * so restating these shapes here would let the two drift apart silently.\n */\nexport type BlobEncryptedBytesRecord = BlobEnvelopeRecord;\n\n/**\n * The envelope as the header establishes it. Streaming readers hold this\n * before any ciphertext is read, so it has no `chunks`: an empty array there\n * would read as an empty object rather than an unread one.\n */\nexport type BlobEncryptedBytesHeader = BlobEnvelopeHeaderRecord;\n\nexport type BlobEncryptedChunk = BlobEnvelopeRecord[\"chunks\"][number];\n\nexport interface BlobSourceSnapshot {\n readonly byteLength: number;\n readonly chunkSha256: readonly string[];\n readonly chunkSize: number;\n readonly sha256: string;\n}\n\nexport interface BlobEncryptionPlan {\n readonly byteLength: number;\n readonly chunkSize: number;\n readonly metadataHash: string;\n readonly partCount: number;\n readonly plaintextByteLength: number;\n readonly plaintextSha256: string;\n readonly sha256: string;\n encryptPart(index: number): Promise<BlobBytes>;\n getPartByteLength(index: number): number;\n}\n\nexport interface DocumentManifestIdentity {\n documentId: string;\n manifestHash: string;\n organizationId: string;\n}\n\nexport interface BlobAttachmentMaterial {\n blobAccessManifestHash: string;\n contentKeyBundle: BlobContentKeyBundleRequest;\n encrypted: BlobEncryptionPlan;\n manifestIdentity: DocumentManifestIdentity;\n targetHash: string;\n targets: BlobContentKeyTarget[];\n writerProjection: DocumentWriterProjectionResponse;\n}\n\n/**\n * Progress for an in-flight multipart blob upload.\n */\nexport interface MultipartUploadProgress {\n readonly bytesTotal: number;\n readonly bytesUploaded: number;\n readonly partsCompleted: number;\n readonly partsTotal: number;\n}\n\nexport type MultipartUploadProgressListener = (\n progress: MultipartUploadProgress,\n) => void;\n\n/**\n * Fires once a multipart stage has been opened (or resumed), with its id and\n * plaintext chunk size. Callers persist both so a later attempt can reproduce\n * the same encrypted parts and resume instead of orphaning the partial upload.\n */\nexport type MultipartStageResolvedListener = (input: {\n readonly partSize: number;\n readonly stageId: string;\n}) => void | Promise<void>;\n\nexport interface UploadDocumentAttachmentInput {\n apiClient: BlobAttachmentApi;\n author: DocumentCreateAuthor;\n blobId?: string | undefined;\n bindingId?: string | undefined;\n bytes: BlobByteSourceInput;\n contentKey?: Uint8Array | undefined;\n contentKeyEpoch?: number | undefined;\n documentId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n expectedBindingId: string | null;\n isRemoteSyncBlocked?: ((organizationId: string) => boolean) | undefined;\n // A persisted nonce seed. The plaintext digest derives the envelope IV from\n // it, then each chunk derives a unique IV. Same-source retries reproduce the\n // encrypted parts; changed bytes cannot reuse their AES-GCM nonces.\n nonceSeed?: Uint8Array | undefined;\n multipart?: MultipartBlobUploadOptions | undefined;\n onMultipartProgress?: MultipartUploadProgressListener | undefined;\n onStageResolved?: MultipartStageResolvedListener | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n slotId: string;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface MultipartBlobUploadOptions {\n /** Exactly 5 MiB per plaintext encryption chunk; transport parts include envelope overhead. */\n readonly partSize: number;\n // The id of a previously opened stage to resume. When set, the upload refetches\n // that stage and skips parts the server already has instead of opening a new\n // one; if the stage is gone (expired), it falls back to a fresh stage.\n readonly resumeStageId?: string | undefined;\n readonly uploadConcurrency?: number | undefined;\n}\n\nexport interface UploadDocumentAttachmentResult {\n blobId: string;\n bindingId: string;\n request: BlobAttachmentBindRequest;\n response: BlobAttachmentBindResponse;\n sha256: string;\n byteLength: number;\n writeHeader: WriteHeader;\n writeHeaderHash: string;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DetachDocumentAttachmentInput {\n apiClient: BlobAttachmentDetachApi;\n author: DocumentCreateAuthor;\n bindingId: string;\n blobId: string;\n documentId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n isRemoteSyncBlocked?: ((organizationId: string) => boolean) | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n slotId: string;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface DetachDocumentAttachmentResult {\n request: BlobAttachmentDetachRequest;\n response: BlobAttachmentDetachResponse;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DecryptDocumentAttachmentBlobInput {\n binding: BlobAttachmentSummary;\n encryptedBytes: BlobBytes;\n expectedDocumentId: string;\n expectedSlotId: string;\n execSql: ExecSql;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n targetSecretKey: Uint8Array;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection: DocumentWriterProjectionResponse;\n}\n"]}
@@ -6,14 +6,50 @@ export async function applyStoredAttachmentRemoval(input) {
6
6
  const { localId, removal, tx } = 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
- await tx.delete(documentPendingAttachments).where(pendingMatch).run();
10
- // A pending-only slot leaves no detached marker to queue its bytes later.
9
+ const [pending] = await tx
10
+ .delete(documentPendingAttachments)
11
+ .where(pendingMatch)
12
+ .returning();
13
+ // A pending upload without a remote identity needs no detach marker.
11
14
  // Reclaim keeps any key that a marker or another row still references.
12
15
  await queueDocumentAttachmentStorageKeys(tx, [removal.storageKey]);
13
16
  if (removal.mode === "detach") {
17
+ const detachedAt = new Date().toISOString();
18
+ // The bind may have committed before its response (or settlement) was lost.
19
+ // Preserve its identity until sync confirms that the binding is detached.
20
+ if (pending?.uploadBlobId) {
21
+ const [marker] = await tx
22
+ .insert(documentAttachmentBlobProjection)
23
+ .values({
24
+ localId,
25
+ slotId: removal.slotId,
26
+ storageKey: removal.storageKey,
27
+ blobId: pending.uploadBlobId,
28
+ byteLength: pending.byteLength,
29
+ contentSha256: pending.contentSha256,
30
+ mimeType: pending.mimeType,
31
+ detachedAt,
32
+ updatedAt: detachedAt,
33
+ })
34
+ .onConflictDoUpdate({
35
+ target: [
36
+ documentAttachmentBlobProjection.localId,
37
+ documentAttachmentBlobProjection.slotId,
38
+ ],
39
+ set: {
40
+ blobId: pending.uploadBlobId,
41
+ detachedAt,
42
+ updatedAt: detachedAt,
43
+ },
44
+ setWhere: eq(documentAttachmentBlobProjection.storageKey, removal.storageKey),
45
+ })
46
+ .returning();
47
+ if (!marker)
48
+ throw new Error("Attachment slot changed during removal");
49
+ }
14
50
  await tx
15
51
  .update(documentAttachmentBlobProjection)
16
- .set({ detachedAt: new Date().toISOString() })
52
+ .set({ detachedAt })
17
53
  .where(and(localMatch, isNull(documentAttachmentBlobProjection.detachedAt)))
18
54
  .run();
19
55
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"attachmentStagingPersistence.js","sourceRoot":"","sources":["attachmentStagingPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, isNull } from \"drizzle-orm\";\nimport {\n documentAttachmentBlobProjection,\n documentPendingAttachments,\n} from \"../../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport type { DocumentsPersistence } from \"../types\";\nimport { buildPendingAttachmentRow } from \"./attachmentRows\";\nimport { queueDocumentAttachmentStorageKeys } from \"./orphanSideRows\";\n\ntype AttachmentStaging = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentStaging\"]\n>;\ntype AttachmentRemoval = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentRemoval\"]\n>;\n\nexport async function applyStoredAttachmentRemoval(input: {\n localId: string;\n removal: AttachmentRemoval;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { localId, removal, tx } = input;\n const pendingMatch = and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, removal.slotId),\n eq(documentPendingAttachments.storageKey, removal.storageKey),\n );\n const localMatch = and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, removal.slotId),\n eq(documentAttachmentBlobProjection.storageKey, removal.storageKey),\n );\n await tx.delete(documentPendingAttachments).where(pendingMatch).run();\n // 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"]}
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 const [pending] = await tx\n .delete(documentPendingAttachments)\n .where(pendingMatch)\n .returning();\n // A pending upload without a remote identity needs no detach marker.\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 const detachedAt = new Date().toISOString();\n // The bind may have committed before its response (or settlement) was lost.\n // Preserve its identity until sync confirms that the binding is detached.\n if (pending?.uploadBlobId) {\n const [marker] = await tx\n .insert(documentAttachmentBlobProjection)\n .values({\n localId,\n slotId: removal.slotId,\n storageKey: removal.storageKey,\n blobId: pending.uploadBlobId,\n byteLength: pending.byteLength,\n contentSha256: pending.contentSha256,\n mimeType: pending.mimeType,\n detachedAt,\n updatedAt: detachedAt,\n })\n .onConflictDoUpdate({\n target: [\n documentAttachmentBlobProjection.localId,\n documentAttachmentBlobProjection.slotId,\n ],\n set: {\n blobId: pending.uploadBlobId,\n detachedAt,\n updatedAt: detachedAt,\n },\n setWhere: eq(\n documentAttachmentBlobProjection.storageKey,\n removal.storageKey,\n ),\n })\n .returning();\n if (!marker) throw new Error(\"Attachment slot changed during removal\");\n }\n await tx\n .update(documentAttachmentBlobProjection)\n .set({ detachedAt })\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"]}
@@ -0,0 +1,7 @@
1
+ export declare function blobContentMetadataHash(input: {
2
+ blobId: string;
3
+ byteLength: number;
4
+ chunkCount: number;
5
+ chunkSize: number;
6
+ contentKeyEpoch: number;
7
+ }): Promise<string>;
@@ -0,0 +1,21 @@
1
+ import { BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, } from "./blobEnvelopeV2.js";
2
+ import { toFingerprint } from "./fingerprint.js";
3
+ import { serializeKeyingCanonicalJson } from "./keying/canonical.js";
4
+ const TEXT_ENCODER = new TextEncoder();
5
+ const BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN = "tearleads.blob.content-record-metadata";
6
+ export async function blobContentMetadataHash(input) {
7
+ return toFingerprint(TEXT_ENCODER.encode(serializeKeyingCanonicalJson({
8
+ domain: BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN,
9
+ payload: {
10
+ version: BLOB_ENCRYPTED_BYTES_VERSION,
11
+ recordKind: "blob_bytes",
12
+ blobId: input.blobId,
13
+ byteLength: input.byteLength,
14
+ chunkCount: input.chunkCount,
15
+ chunkSize: input.chunkSize,
16
+ contentKeyEpoch: input.contentKeyEpoch,
17
+ format: BLOB_ENCRYPTED_BYTES_FORMAT,
18
+ },
19
+ })));
20
+ }
21
+ //# sourceMappingURL=blobContentMetadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blobContentMetadata.js","sourceRoot":"","sources":["blobContentMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAElE,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC;AACvC,MAAM,wCAAwC,GAC5C,wCAAwC,CAAC;AAE3C,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,KAM7C;IACC,OAAO,aAAa,CAClB,YAAY,CAAC,MAAM,CACjB,4BAA4B,CAAC;QAC3B,MAAM,EAAE,wCAAwC;QAChD,OAAO,EAAE;YACP,OAAO,EAAE,4BAA4B;YACrC,UAAU,EAAE,YAAY;YACxB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,MAAM,EAAE,2BAA2B;SACpC;KACF,CAAC,CACH,CACF,CAAC;AACJ,CAAC","sourcesContent":["import {\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n} from \"./blobEnvelopeV2\";\nimport { toFingerprint } from \"./fingerprint\";\nimport { serializeKeyingCanonicalJson } from \"./keying/canonical\";\n\nconst TEXT_ENCODER = new TextEncoder();\nconst BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN =\n \"tearleads.blob.content-record-metadata\";\n\nexport async function blobContentMetadataHash(input: {\n blobId: string;\n byteLength: number;\n chunkCount: number;\n chunkSize: number;\n contentKeyEpoch: number;\n}): Promise<string> {\n return toFingerprint(\n TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN,\n payload: {\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n recordKind: \"blob_bytes\",\n blobId: input.blobId,\n byteLength: input.byteLength,\n chunkCount: input.chunkCount,\n chunkSize: input.chunkSize,\n contentKeyEpoch: input.contentKeyEpoch,\n format: BLOB_ENCRYPTED_BYTES_FORMAT,\n },\n }),\n ),\n );\n}\n"]}
@@ -1,3 +1,4 @@
1
+ export { blobContentMetadataHash } from "./blobContentMetadata.js";
1
2
  export { BlobEnvelopeError } from "./blobEnvelopeError.js";
2
3
  export type { BlobEnvelopeHeaderRecord, BlobEnvelopeRecord, } from "./blobEnvelopeReader.js";
3
4
  export { MAX_BLOB_ENVELOPE_HEADER_BYTES, parseBlobEnvelopeV2, parseBlobEnvelopeV2Header, readBlobEnvelopeHeaderByteLength, } from "./blobEnvelopeReader.js";
@@ -1,3 +1,4 @@
1
+ export { blobContentMetadataHash } from "./blobContentMetadata.js";
1
2
  export { BlobEnvelopeError } from "./blobEnvelopeError.js";
2
3
  export { MAX_BLOB_ENVELOPE_HEADER_BYTES, parseBlobEnvelopeV2, parseBlobEnvelopeV2Header, readBlobEnvelopeHeaderByteLength, } from "./blobEnvelopeReader.js";
3
4
  export { BLOB_CHUNK_SIZE_BYTES, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, BLOB_ENVELOPE_MAGIC_BYTES, BLOB_ENVELOPE_PREFIX_BYTES, blobChunkPlaintextByteLength, computeBlobChunkCount, computeBlobEncryptedByteLength, deriveBlobChunkIv, encodeBlobEnvelopeV2Header, joinBlobPartBytes, MAX_BLOB_CHUNK_COUNT, normalizeBlobChunkSize, } from "./blobEnvelopeV2.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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","sourcesContent":["export { BlobEnvelopeError } from \"./blobEnvelopeError\";\nexport type {\n BlobEnvelopeHeaderRecord,\n BlobEnvelopeRecord,\n} from \"./blobEnvelopeReader\";\nexport {\n MAX_BLOB_ENVELOPE_HEADER_BYTES,\n parseBlobEnvelopeV2,\n parseBlobEnvelopeV2Header,\n readBlobEnvelopeHeaderByteLength,\n} from \"./blobEnvelopeReader\";\nexport type { BlobEnvelopeV2Header } from \"./blobEnvelopeV2\";\nexport {\n BLOB_CHUNK_SIZE_BYTES,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n BLOB_ENVELOPE_MAGIC_BYTES,\n BLOB_ENVELOPE_PREFIX_BYTES,\n blobChunkPlaintextByteLength,\n computeBlobChunkCount,\n computeBlobEncryptedByteLength,\n deriveBlobChunkIv,\n encodeBlobEnvelopeV2Header,\n joinBlobPartBytes,\n MAX_BLOB_CHUNK_COUNT,\n normalizeBlobChunkSize,\n} from \"./blobEnvelopeV2\";\nexport { compareCanonicalStrings } from \"./canonicalOrdering\";\nexport {\n AUTH_CHALLENGE_BYTES,\n AUTH_CHALLENGE_HEX_LENGTH,\n authChallengeSigningBytes,\n CHALLENGE_TTL_SECONDS,\n canonicalAuthOrigin,\n generateChallenge,\n} from \"./challenge\";\nexport type {\n ContentKeyEnvelopeKind,\n ContentKeyWrapBinding,\n} from \"./contentKeyEnvelope\";\nexport {\n ContentKeyEnvelopeError,\n decodeContentKeyEnvelope,\n unwrapContentKey,\n wrapContentKey,\n} from \"./contentKeyEnvelope\";\nexport { decryptAsRecipient } from \"./encapsulation/decryptAsRecipient\";\nexport { encryptForRecipients } from \"./encapsulation/encryptForRecipients\";\nexport type { EncapsulationKeyPair } from \"./encapsulation/generateKeyPair\";\nexport {\n generateKemKeyPair,\n generateKemSeedAndKeyPair,\n ML_KEM1024_CIPHERTEXT_BYTES,\n ML_KEM1024_PUBLIC_KEY_BYTES,\n ML_KEM1024_SECRET_KEY_BYTES,\n ML_KEM1024_SEED_BYTES,\n} from \"./encapsulation/generateKeyPair\";\nexport type { EncryptedEnvelope, RecipientEntry } from \"./encapsulation/types\";\nexport { unwrapDek } from \"./encapsulation/unwrapDek\";\nexport { wrapDekForRecipients } from \"./encapsulation/wrapDek\";\nexport { toFingerprint } from \"./fingerprint\";\nexport {\n decryptGroupMetadata,\n encodeBuiltinGroupMetadata,\n encryptGroupMetadata,\n type GroupMetadata,\n type GroupMetadataKey,\n readGroupMetadata,\n} from \"./groupMetadata\";\nexport { bytesToHex, hexToBytes } from \"./hex\";\nexport {\n createIdentitySeedPhrase,\n createIdentitySeedPhraseFromEntropy,\n generateIdentityKeyPairsFromSeedPhrase,\n IDENTITY_SEED_PHRASE_DERIVATION_SUITE,\n IDENTITY_SEED_PHRASE_ENTROPY_BYTES,\n IDENTITY_SEED_PHRASE_STRENGTH_BITS,\n IDENTITY_SEED_PHRASE_WORDS,\n type IdentitySeedPhraseKeyPairs,\n normalizeIdentitySeedPhrase,\n validateIdentitySeedPhrase,\n} from \"./identitySeedPhrase\";\nexport {\n createIncrementalSha256,\n type IncrementalSha256,\n} from \"./incrementalSha256\";\nexport type {\n AccessEvent,\n AccessEventType,\n AccessManifest,\n AccessManifestCheckpoint,\n AccessManifestTransparencyLeaf,\n AccessObjectKind,\n AnyVerifiedAccessManifest,\n AnyVerifiedPrincipalPolicy,\n AttachmentAccessEventBody,\n AttachmentBindAccessEventBody,\n AttachmentDetachAccessEventBody,\n BlobAccessManifest,\n BlobContentKeyTarget,\n ContainerAccessEventBody,\n ContainerAccessKeyState,\n ContainerAccessLevel,\n ContainerAccessManifest,\n ContainerAccessManifestState,\n ContainerAccessStructural,\n ContainerCreateAccessEventBody,\n ContainerDirectGrant,\n ContainerGrantAccessEventBody,\n ContainerGrantPrincipalHead,\n ContainerGrantSubjectType,\n ContainerKekKeyring,\n ContainerKekKeyringEntry,\n ContainerKekPredecessorBridge,\n ContainerKekRecipientTarget,\n ContainerKekTarget,\n ContainerKeyEpoch,\n ContainerKeyWrap,\n ContainerMoveAccessEventBody,\n ContainerReciteAccessEventBody,\n ContainerRekeyAccessEventBody,\n ContainerRevokeAccessEventBody,\n ContainerUserRecipientKey,\n ContentObjectKind,\n ContentRecordEncryptionSuite,\n ContentRecordNonceDomain,\n DeriveBlobKekTargetsInput,\n DeriveContainerKekRecipientTargetsInput,\n DeriveDocumentKekTargetsInput,\n DocumentAccessEventBody,\n DocumentContentKeyTarget,\n DocumentContentRecordMetadataInput,\n DocumentLinkAccessEventBody,\n DocumentLinkSetManifestState,\n DocumentLinkSetStructural,\n DocumentPurgeAccessEventBody,\n DocumentUnlinkAccessEventBody,\n IdentityStateCheckpoint,\n IdentityStateHead,\n IdentityStateTransparencyLeaf,\n KekRecipientKind,\n KeyingCanonicalJson,\n KeyingHashDomain,\n KeyingLocalCheckpointStore,\n KeyingVerificationCode,\n KeyingVerificationResult,\n ManagedPrincipalKind,\n PrincipalHistoryIndexNode,\n PrincipalPolicyAuthorization,\n PrincipalPolicyBundle,\n PrincipalPolicyCheckpoint,\n PrincipalPolicyCurrent,\n PrincipalPolicyExternalAuthority,\n PrincipalPolicyExternalAuthorityState,\n PrincipalPolicyHistoryInput,\n PrincipalPolicyHistoryPage,\n PrincipalPolicyHistoryProgressOptions,\n PrincipalPolicyHistoryReferenceProof,\n PrincipalPolicyHistoryVerifier,\n PrincipalPolicyMemberEnvelopes,\n PrincipalPolicyPayload,\n PrincipalPolicySignedState,\n PrincipalPolicySignerPublicKey,\n PrincipalPolicySnapshot,\n PrincipalPolicyStateChainEntry,\n PrincipalPolicyTransitionMismatch,\n PrincipalPolicyTransitionMismatchCode,\n PrincipalPolicyTransparencyLeaf,\n ReferencedPrincipalHead,\n SignedTransparencyTreeHead,\n TransparencyConsistencyProof,\n TransparencyInclusionProof,\n TransparencyLeaf,\n TransparencyLeafKind,\n TransparencyTreeCheckpoint,\n UnsignedAccessEvent,\n UnsignedTransparencyTreeHead,\n UnsignedWriteHeader,\n VerifiedAccessEvent,\n VerifiedAccessManifest,\n VerifiedAccessManifestCheckpointEvidence,\n VerifiedAccessManifestSnapshot,\n VerifiedAttachmentBinding,\n VerifiedAttachmentDetach,\n VerifiedBlobKekTargets,\n VerifiedContainerAccessManifest,\n VerifiedContainerKekState,\n VerifiedContainerParentEdge,\n VerifiedDocumentKekTargets,\n VerifiedDocumentLinkSetManifest,\n VerifiedDocumentLinkSetSnapshot,\n VerifiedDocumentLinkSetStateEvidence,\n VerifiedIdentityState,\n VerifiedPrincipalPolicy,\n VerifiedPrincipalPolicyCurrent,\n VerifiedPrincipalPolicyHistory,\n VerifiedPrincipalPolicySelection,\n VerifiedPrincipalPolicySnapshot,\n VerifiedTransparencyProof,\n VerifiedTransparencyTreeHead,\n VerifiedWriteHeader,\n VerifyAccessEventInput,\n VerifyAccessManifestInput,\n VerifyAttachmentBindingEventInput,\n VerifyAttachmentDetachEventInput,\n VerifyContainerAccessManifestInput,\n VerifyContainerKekStateInput,\n VerifyContainerParentEdgeInput,\n VerifyDocumentLinkSetManifestInput,\n VerifyDocumentPurgeEventInput,\n VerifyIdentityStateCheckpointInput,\n VerifyPrincipalPolicyBundleInput,\n VerifyPrincipalPolicySnapshotInput,\n VerifySignedTransparencyTreeHeadInput,\n VerifyTransparencyConsistencyProofInput,\n VerifyTransparencyInclusionProofInput,\n VerifyTransparencyProofInput,\n VerifyWriteHeaderInput,\n WriteHeader,\n} from \"./keying\";\nexport {\n ACCESS_MANIFEST_VERIFICATION_REVISION,\n accessManifestCheckpointFromManifest,\n accessManifestTransparencyLeaf,\n assertSealedContainerKekKeyringLength,\n BLOB_CONTENT_KEY_WRAP_SUITE,\n CONTAINER_KEK_KEYRING_ENTRY_BYTES,\n CONTAINER_KEK_KEYRING_FORMAT_VERSION,\n CONTAINER_KEK_KEYRING_HEADER_BYTES,\n CONTAINER_KEK_KEYRING_SEAL_SUITE,\n CONTAINER_KEK_MATERIAL_ID_PREFIX,\n CONTAINER_KEK_PARENT_WRAP_SUITE,\n CONTAINER_KEK_PREDECESSOR_WRAP_SUITE,\n CONTAINER_KEK_USER_WRAP_SUITE,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeAccessEventBodyHash,\n computeAccessEventHash,\n computeAccessManifestHash,\n computeBlobAccessManifestHash,\n computeBlobContentKeyTargetHash,\n computeContainerAccessKeyTargetHash,\n computeContainerAccessStructuralHash,\n computeContainerDirectGrantRoot,\n computeContainerKekKeyringHash,\n computeContainerKekMaterialId,\n computeContainerKekPredecessorBridgeHash,\n computeContainerKekRecipientTargetHash,\n computeContainerKeyEpochHash,\n computeContentRecordNonceDomainHash,\n computeDocumentContentKeyTargetHash,\n computeDocumentContentRecordCiphertextHash,\n computeDocumentContentRecordMetadataHash,\n computeDocumentContentRecordPlaintextHash,\n computeDocumentLinkSetGrantRoot,\n computeDocumentLinkSetKeyTargetHash,\n computeDocumentLinkSetStructuralHash,\n computeKeyingDomainHash,\n computeTransparencyLeafHash,\n computeTransparencyMerkleRoot,\n computeWriteHeaderHash,\n createContainerKekPredecessorBridge,\n createPrincipalHistoryIndexProof,\n createPrincipalPolicyHistoryVerifier,\n createTransparencyConsistencyProof,\n createTransparencyInclusionProof,\n DOCUMENT_CONTENT_KEY_WRAP_SUITE,\n deriveBlobKekTargets,\n deriveContainerAccessManifest,\n deriveContainerKekRecipientTargets,\n deriveContainerKekWrappingPublicKey,\n deriveDocumentKekTargets,\n deriveDocumentLinkSetManifest,\n derivePrincipalRecipientKeyEpochId,\n documentContentRecordMetadata,\n expectedSealedContainerKekKeyringBytes,\n getPrincipalPolicyTransitionMismatch,\n getPrincipalPolicyTransitionMismatchReason,\n identityStateTransparencyLeaf,\n isContainerKekMaterialId,\n isKeyingVerificationCode,\n KEYING_VERIFICATION_CODES,\n KeyingVerificationError,\n MAX_CONTAINER_KEY_EPOCH,\n MAX_CONTAINER_RECITATION_EPOCH,\n makeVerifiedAccessEvent,\n makeVerifiedBlobKekTargets,\n makeVerifiedContainerAccessManifest,\n makeVerifiedContainerKekState,\n makeVerifiedDocumentKekTargets,\n makeVerifiedDocumentLinkSetManifest,\n makeVerifiedDocumentLinkSetSnapshot,\n makeVerifiedPrincipalPolicy,\n normalizeAttachmentAccessEventBody,\n normalizeContainerAccessEventBody,\n normalizeContainerKekKeyring,\n normalizeContainerKekPredecessorBridge,\n normalizeDocumentAccessEventBody,\n normalizeDocumentPurgeAccessEventBody,\n openContainerKekKeyring,\n PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT,\n PRINCIPAL_HISTORY_VERIFICATION_REVISION,\n principalHistoryIndexLeaf,\n principalPolicyMatchesReference,\n principalPolicyTransparencyLeaf,\n resolveContainerPathUserAccessLevel,\n resolveContainerStatePathUserAccessLevel,\n resolveHistoricalContainerPathUserAccessLevel,\n resolvePrincipalHistoryIndexProof,\n restorePrincipalPolicyHistoryVerifier,\n sealContainerKekKeyring,\n selectPrincipalPolicyAuthorization,\n selectPrincipalPolicyCurrentPredecessorReferences,\n serializeKeyingCanonicalJson,\n signAccessEvent,\n signTransparencyTreeHead,\n signWriteHeader,\n unwrapContainerKekParentWrap,\n unwrapContainerKekPredecessorBridge,\n verifyAccessManifest,\n verifyAccessManifestLocalCheckpoint,\n verifyAccessManifestSnapshot,\n verifyAttachmentBindingEvent,\n verifyAttachmentDetachEvent,\n verifyContainerAccessManifest,\n verifyContainerKekKeyringEntry,\n verifyContainerKekState,\n verifyContainerParentEdge,\n verifyDocumentLinkSetManifest,\n verifyDocumentLinkSetSnapshot,\n verifyDocumentPurgeEvent,\n verifyIdentityStateCheckpoint,\n verifyPrincipalPolicyBundle,\n verifyPrincipalPolicyBundleAgainstHistory,\n verifyPrincipalPolicyCheckpoint,\n verifyPrincipalPolicyCurrent,\n verifyPrincipalPolicyCurrentMutation,\n verifyPrincipalPolicyCurrentSuccessor,\n verifyPrincipalPolicyHistoryReferences,\n verifyPrincipalPolicySnapshot,\n verifySignedAccessEvent,\n verifySignedTransparencyTreeHead,\n verifyTransparencyConsistencyProof,\n verifyTransparencyInclusionProof,\n verifyTransparencyProof,\n verifyWriteHeader,\n} from \"./keying\";\nexport {\n organizationReplacementAuthorizationPayload,\n organizationReplacementSigningBytes,\n signOrganizationReplacementAuthorization,\n type VerifiedOrganizationReplacementAuthorization,\n verifyOrganizationReplacementAuthorization,\n} from \"./organizationReplacement\";\nexport {\n computePrincipalContainerGrantRoot,\n normalizePrincipalContainerGrants,\n} from \"./principalContainerGrants\";\nexport {\n computePrincipalMemberEnvelopesRoot,\n normalizePrincipalStateMemberEnvelopes,\n PrincipalMemberEnvelopeValidationError,\n} from \"./principalMemberEnvelopes\";\nexport type { PrincipalPolicyValidationErrorCode } from \"./principalPolicyValidationError\";\nexport {\n PrincipalPolicyValidationError,\n throwPrincipalPolicyValidationError,\n} from \"./principalPolicyValidationError\";\nexport type {\n ManagedRecipientPrincipalType,\n PrincipalContainerGrant,\n PrincipalContainerGrantAccessLevel,\n PrincipalProjectionMember,\n PrincipalProjectionRole,\n PrincipalStateExternalAuthority,\n PrincipalStateHeaderInput,\n PrincipalStateMember,\n PrincipalStateMemberEnvelope,\n PrincipalStateMembershipMode,\n PrincipalStatePayloadCipherSuite,\n PrincipalStateSigningInput,\n SignedPrincipalState,\n UnsignedPrincipalState,\n} from \"./principalState\";\nexport {\n buildPrincipalStateSigningInput,\n computePrincipalMembershipRoot,\n computePrincipalProjectionRoot,\n computePrincipalStateHash,\n computePrincipalStatePayloadCiphertextHash,\n derivePrincipalProjectionMembers,\n isManagedRecipientPrincipalType,\n isPrincipalProjectionRole,\n normalizePrincipalProjectionMembers,\n normalizePrincipalStateMembers,\n serializeUnsignedPrincipalState,\n signPrincipalState,\n} from \"./principalState\";\nexport {\n verifySignedPrincipalState,\n verifySignedPrincipalStateResult,\n} from \"./principalStateVerification\";\nexport {\n captureProjectionHistory,\n type HistoryProjection,\n omitProjectionHistory,\n type RetainedProjectionHistory,\n restoreProjectionHistory,\n} from \"./projectionHistory\";\nexport type { SigningKeyPair } from \"./signing/generateKeyPair\";\nexport {\n generateSigningKeyPair,\n generateSigningSeedAndKeyPair,\n ML_DSA87_PUBLIC_KEY_BYTES,\n ML_DSA87_SECRET_KEY_BYTES,\n ML_DSA87_SIGNATURE_BYTES,\n} from \"./signing/generateKeyPair\";\nexport { sign } from \"./signing/sign\";\nexport { verify } from \"./signing/verify\";\nexport type { SymmetricCiphertext } from \"./symmetric\";\nexport {\n AES_256_KEY_BYTES,\n AES_GCM_IV_BYTES,\n AES_GCM_TAG_BYTES,\n assertAesGcmIv,\n createAesGcmIv,\n decryptWithDek,\n encryptWithDek,\n} from \"./symmetric\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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","sourcesContent":["export { blobContentMetadataHash } from \"./blobContentMetadata\";\nexport { BlobEnvelopeError } from \"./blobEnvelopeError\";\nexport type {\n BlobEnvelopeHeaderRecord,\n BlobEnvelopeRecord,\n} from \"./blobEnvelopeReader\";\nexport {\n MAX_BLOB_ENVELOPE_HEADER_BYTES,\n parseBlobEnvelopeV2,\n parseBlobEnvelopeV2Header,\n readBlobEnvelopeHeaderByteLength,\n} from \"./blobEnvelopeReader\";\nexport type { BlobEnvelopeV2Header } from \"./blobEnvelopeV2\";\nexport {\n BLOB_CHUNK_SIZE_BYTES,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n BLOB_ENVELOPE_MAGIC_BYTES,\n BLOB_ENVELOPE_PREFIX_BYTES,\n blobChunkPlaintextByteLength,\n computeBlobChunkCount,\n computeBlobEncryptedByteLength,\n deriveBlobChunkIv,\n encodeBlobEnvelopeV2Header,\n joinBlobPartBytes,\n MAX_BLOB_CHUNK_COUNT,\n normalizeBlobChunkSize,\n} from \"./blobEnvelopeV2\";\nexport { compareCanonicalStrings } from \"./canonicalOrdering\";\nexport {\n AUTH_CHALLENGE_BYTES,\n AUTH_CHALLENGE_HEX_LENGTH,\n authChallengeSigningBytes,\n CHALLENGE_TTL_SECONDS,\n canonicalAuthOrigin,\n generateChallenge,\n} from \"./challenge\";\nexport type {\n ContentKeyEnvelopeKind,\n ContentKeyWrapBinding,\n} from \"./contentKeyEnvelope\";\nexport {\n ContentKeyEnvelopeError,\n decodeContentKeyEnvelope,\n unwrapContentKey,\n wrapContentKey,\n} from \"./contentKeyEnvelope\";\nexport { decryptAsRecipient } from \"./encapsulation/decryptAsRecipient\";\nexport { encryptForRecipients } from \"./encapsulation/encryptForRecipients\";\nexport type { EncapsulationKeyPair } from \"./encapsulation/generateKeyPair\";\nexport {\n generateKemKeyPair,\n generateKemSeedAndKeyPair,\n ML_KEM1024_CIPHERTEXT_BYTES,\n ML_KEM1024_PUBLIC_KEY_BYTES,\n ML_KEM1024_SECRET_KEY_BYTES,\n ML_KEM1024_SEED_BYTES,\n} from \"./encapsulation/generateKeyPair\";\nexport type { EncryptedEnvelope, RecipientEntry } from \"./encapsulation/types\";\nexport { unwrapDek } from \"./encapsulation/unwrapDek\";\nexport { wrapDekForRecipients } from \"./encapsulation/wrapDek\";\nexport { toFingerprint } from \"./fingerprint\";\nexport {\n decryptGroupMetadata,\n encodeBuiltinGroupMetadata,\n encryptGroupMetadata,\n type GroupMetadata,\n type GroupMetadataKey,\n readGroupMetadata,\n} from \"./groupMetadata\";\nexport { bytesToHex, hexToBytes } from \"./hex\";\nexport {\n createIdentitySeedPhrase,\n createIdentitySeedPhraseFromEntropy,\n generateIdentityKeyPairsFromSeedPhrase,\n IDENTITY_SEED_PHRASE_DERIVATION_SUITE,\n IDENTITY_SEED_PHRASE_ENTROPY_BYTES,\n IDENTITY_SEED_PHRASE_STRENGTH_BITS,\n IDENTITY_SEED_PHRASE_WORDS,\n type IdentitySeedPhraseKeyPairs,\n normalizeIdentitySeedPhrase,\n validateIdentitySeedPhrase,\n} from \"./identitySeedPhrase\";\nexport {\n createIncrementalSha256,\n type IncrementalSha256,\n} from \"./incrementalSha256\";\nexport type {\n AccessEvent,\n AccessEventType,\n AccessManifest,\n AccessManifestCheckpoint,\n AccessManifestTransparencyLeaf,\n AccessObjectKind,\n AnyVerifiedAccessManifest,\n AnyVerifiedPrincipalPolicy,\n AttachmentAccessEventBody,\n AttachmentBindAccessEventBody,\n AttachmentDetachAccessEventBody,\n BlobAccessManifest,\n BlobContentKeyTarget,\n ContainerAccessEventBody,\n ContainerAccessKeyState,\n ContainerAccessLevel,\n ContainerAccessManifest,\n ContainerAccessManifestState,\n ContainerAccessStructural,\n ContainerCreateAccessEventBody,\n ContainerDirectGrant,\n ContainerGrantAccessEventBody,\n ContainerGrantPrincipalHead,\n ContainerGrantSubjectType,\n ContainerKekKeyring,\n ContainerKekKeyringEntry,\n ContainerKekPredecessorBridge,\n ContainerKekRecipientTarget,\n ContainerKekTarget,\n ContainerKeyEpoch,\n ContainerKeyWrap,\n ContainerMoveAccessEventBody,\n ContainerReciteAccessEventBody,\n ContainerRekeyAccessEventBody,\n ContainerRevokeAccessEventBody,\n ContainerUserRecipientKey,\n ContentObjectKind,\n ContentRecordEncryptionSuite,\n ContentRecordNonceDomain,\n DeriveBlobKekTargetsInput,\n DeriveContainerKekRecipientTargetsInput,\n DeriveDocumentKekTargetsInput,\n DocumentAccessEventBody,\n DocumentContentKeyTarget,\n DocumentContentRecordMetadataInput,\n DocumentLinkAccessEventBody,\n DocumentLinkSetManifestState,\n DocumentLinkSetStructural,\n DocumentPurgeAccessEventBody,\n DocumentUnlinkAccessEventBody,\n IdentityStateCheckpoint,\n IdentityStateHead,\n IdentityStateTransparencyLeaf,\n KekRecipientKind,\n KeyingCanonicalJson,\n KeyingHashDomain,\n KeyingLocalCheckpointStore,\n KeyingVerificationCode,\n KeyingVerificationResult,\n ManagedPrincipalKind,\n PrincipalHistoryIndexNode,\n PrincipalPolicyAuthorization,\n PrincipalPolicyBundle,\n PrincipalPolicyCheckpoint,\n PrincipalPolicyCurrent,\n PrincipalPolicyExternalAuthority,\n PrincipalPolicyExternalAuthorityState,\n PrincipalPolicyHistoryInput,\n PrincipalPolicyHistoryPage,\n PrincipalPolicyHistoryProgressOptions,\n PrincipalPolicyHistoryReferenceProof,\n PrincipalPolicyHistoryVerifier,\n PrincipalPolicyMemberEnvelopes,\n PrincipalPolicyPayload,\n PrincipalPolicySignedState,\n PrincipalPolicySignerPublicKey,\n PrincipalPolicySnapshot,\n PrincipalPolicyStateChainEntry,\n PrincipalPolicyTransitionMismatch,\n PrincipalPolicyTransitionMismatchCode,\n PrincipalPolicyTransparencyLeaf,\n ReferencedPrincipalHead,\n SignedTransparencyTreeHead,\n TransparencyConsistencyProof,\n TransparencyInclusionProof,\n TransparencyLeaf,\n TransparencyLeafKind,\n TransparencyTreeCheckpoint,\n UnsignedAccessEvent,\n UnsignedTransparencyTreeHead,\n UnsignedWriteHeader,\n VerifiedAccessEvent,\n VerifiedAccessManifest,\n VerifiedAccessManifestCheckpointEvidence,\n VerifiedAccessManifestSnapshot,\n VerifiedAttachmentBinding,\n VerifiedAttachmentDetach,\n VerifiedBlobKekTargets,\n VerifiedContainerAccessManifest,\n VerifiedContainerKekState,\n VerifiedContainerParentEdge,\n VerifiedDocumentKekTargets,\n VerifiedDocumentLinkSetManifest,\n VerifiedDocumentLinkSetSnapshot,\n VerifiedDocumentLinkSetStateEvidence,\n VerifiedIdentityState,\n VerifiedPrincipalPolicy,\n VerifiedPrincipalPolicyCurrent,\n VerifiedPrincipalPolicyHistory,\n VerifiedPrincipalPolicySelection,\n VerifiedPrincipalPolicySnapshot,\n VerifiedTransparencyProof,\n VerifiedTransparencyTreeHead,\n VerifiedWriteHeader,\n VerifyAccessEventInput,\n VerifyAccessManifestInput,\n VerifyAttachmentBindingEventInput,\n VerifyAttachmentDetachEventInput,\n VerifyContainerAccessManifestInput,\n VerifyContainerKekStateInput,\n VerifyContainerParentEdgeInput,\n VerifyDocumentLinkSetManifestInput,\n VerifyDocumentPurgeEventInput,\n VerifyIdentityStateCheckpointInput,\n VerifyPrincipalPolicyBundleInput,\n VerifyPrincipalPolicySnapshotInput,\n VerifySignedTransparencyTreeHeadInput,\n VerifyTransparencyConsistencyProofInput,\n VerifyTransparencyInclusionProofInput,\n VerifyTransparencyProofInput,\n VerifyWriteHeaderInput,\n WriteHeader,\n} from \"./keying\";\nexport {\n ACCESS_MANIFEST_VERIFICATION_REVISION,\n accessManifestCheckpointFromManifest,\n accessManifestTransparencyLeaf,\n assertSealedContainerKekKeyringLength,\n BLOB_CONTENT_KEY_WRAP_SUITE,\n CONTAINER_KEK_KEYRING_ENTRY_BYTES,\n CONTAINER_KEK_KEYRING_FORMAT_VERSION,\n CONTAINER_KEK_KEYRING_HEADER_BYTES,\n CONTAINER_KEK_KEYRING_SEAL_SUITE,\n CONTAINER_KEK_MATERIAL_ID_PREFIX,\n CONTAINER_KEK_PARENT_WRAP_SUITE,\n CONTAINER_KEK_PREDECESSOR_WRAP_SUITE,\n CONTAINER_KEK_USER_WRAP_SUITE,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeAccessEventBodyHash,\n computeAccessEventHash,\n computeAccessManifestHash,\n computeBlobAccessManifestHash,\n computeBlobContentKeyTargetHash,\n computeContainerAccessKeyTargetHash,\n computeContainerAccessStructuralHash,\n computeContainerDirectGrantRoot,\n computeContainerKekKeyringHash,\n computeContainerKekMaterialId,\n computeContainerKekPredecessorBridgeHash,\n computeContainerKekRecipientTargetHash,\n computeContainerKeyEpochHash,\n computeContentRecordNonceDomainHash,\n computeDocumentContentKeyTargetHash,\n computeDocumentContentRecordCiphertextHash,\n computeDocumentContentRecordMetadataHash,\n computeDocumentContentRecordPlaintextHash,\n computeDocumentLinkSetGrantRoot,\n computeDocumentLinkSetKeyTargetHash,\n computeDocumentLinkSetStructuralHash,\n computeKeyingDomainHash,\n computeTransparencyLeafHash,\n computeTransparencyMerkleRoot,\n computeWriteHeaderHash,\n createContainerKekPredecessorBridge,\n createPrincipalHistoryIndexProof,\n createPrincipalPolicyHistoryVerifier,\n createTransparencyConsistencyProof,\n createTransparencyInclusionProof,\n DOCUMENT_CONTENT_KEY_WRAP_SUITE,\n deriveBlobKekTargets,\n deriveContainerAccessManifest,\n deriveContainerKekRecipientTargets,\n deriveContainerKekWrappingPublicKey,\n deriveDocumentKekTargets,\n deriveDocumentLinkSetManifest,\n derivePrincipalRecipientKeyEpochId,\n documentContentRecordMetadata,\n expectedSealedContainerKekKeyringBytes,\n getPrincipalPolicyTransitionMismatch,\n getPrincipalPolicyTransitionMismatchReason,\n identityStateTransparencyLeaf,\n isContainerKekMaterialId,\n isKeyingVerificationCode,\n KEYING_VERIFICATION_CODES,\n KeyingVerificationError,\n MAX_CONTAINER_KEY_EPOCH,\n MAX_CONTAINER_RECITATION_EPOCH,\n makeVerifiedAccessEvent,\n makeVerifiedBlobKekTargets,\n makeVerifiedContainerAccessManifest,\n makeVerifiedContainerKekState,\n makeVerifiedDocumentKekTargets,\n makeVerifiedDocumentLinkSetManifest,\n makeVerifiedDocumentLinkSetSnapshot,\n makeVerifiedPrincipalPolicy,\n normalizeAttachmentAccessEventBody,\n normalizeContainerAccessEventBody,\n normalizeContainerKekKeyring,\n normalizeContainerKekPredecessorBridge,\n normalizeDocumentAccessEventBody,\n normalizeDocumentPurgeAccessEventBody,\n openContainerKekKeyring,\n PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT,\n PRINCIPAL_HISTORY_VERIFICATION_REVISION,\n principalHistoryIndexLeaf,\n principalPolicyMatchesReference,\n principalPolicyTransparencyLeaf,\n resolveContainerPathUserAccessLevel,\n resolveContainerStatePathUserAccessLevel,\n resolveHistoricalContainerPathUserAccessLevel,\n resolvePrincipalHistoryIndexProof,\n restorePrincipalPolicyHistoryVerifier,\n sealContainerKekKeyring,\n selectPrincipalPolicyAuthorization,\n selectPrincipalPolicyCurrentPredecessorReferences,\n serializeKeyingCanonicalJson,\n signAccessEvent,\n signTransparencyTreeHead,\n signWriteHeader,\n unwrapContainerKekParentWrap,\n unwrapContainerKekPredecessorBridge,\n verifyAccessManifest,\n verifyAccessManifestLocalCheckpoint,\n verifyAccessManifestSnapshot,\n verifyAttachmentBindingEvent,\n verifyAttachmentDetachEvent,\n verifyContainerAccessManifest,\n verifyContainerKekKeyringEntry,\n verifyContainerKekState,\n verifyContainerParentEdge,\n verifyDocumentLinkSetManifest,\n verifyDocumentLinkSetSnapshot,\n verifyDocumentPurgeEvent,\n verifyIdentityStateCheckpoint,\n verifyPrincipalPolicyBundle,\n verifyPrincipalPolicyBundleAgainstHistory,\n verifyPrincipalPolicyCheckpoint,\n verifyPrincipalPolicyCurrent,\n verifyPrincipalPolicyCurrentMutation,\n verifyPrincipalPolicyCurrentSuccessor,\n verifyPrincipalPolicyHistoryReferences,\n verifyPrincipalPolicySnapshot,\n verifySignedAccessEvent,\n verifySignedTransparencyTreeHead,\n verifyTransparencyConsistencyProof,\n verifyTransparencyInclusionProof,\n verifyTransparencyProof,\n verifyWriteHeader,\n} from \"./keying\";\nexport {\n organizationReplacementAuthorizationPayload,\n organizationReplacementSigningBytes,\n signOrganizationReplacementAuthorization,\n type VerifiedOrganizationReplacementAuthorization,\n verifyOrganizationReplacementAuthorization,\n} from \"./organizationReplacement\";\nexport {\n computePrincipalContainerGrantRoot,\n normalizePrincipalContainerGrants,\n} from \"./principalContainerGrants\";\nexport {\n computePrincipalMemberEnvelopesRoot,\n normalizePrincipalStateMemberEnvelopes,\n PrincipalMemberEnvelopeValidationError,\n} from \"./principalMemberEnvelopes\";\nexport type { PrincipalPolicyValidationErrorCode } from \"./principalPolicyValidationError\";\nexport {\n PrincipalPolicyValidationError,\n throwPrincipalPolicyValidationError,\n} from \"./principalPolicyValidationError\";\nexport type {\n ManagedRecipientPrincipalType,\n PrincipalContainerGrant,\n PrincipalContainerGrantAccessLevel,\n PrincipalProjectionMember,\n PrincipalProjectionRole,\n PrincipalStateExternalAuthority,\n PrincipalStateHeaderInput,\n PrincipalStateMember,\n PrincipalStateMemberEnvelope,\n PrincipalStateMembershipMode,\n PrincipalStatePayloadCipherSuite,\n PrincipalStateSigningInput,\n SignedPrincipalState,\n UnsignedPrincipalState,\n} from \"./principalState\";\nexport {\n buildPrincipalStateSigningInput,\n computePrincipalMembershipRoot,\n computePrincipalProjectionRoot,\n computePrincipalStateHash,\n computePrincipalStatePayloadCiphertextHash,\n derivePrincipalProjectionMembers,\n isManagedRecipientPrincipalType,\n isPrincipalProjectionRole,\n normalizePrincipalProjectionMembers,\n normalizePrincipalStateMembers,\n serializeUnsignedPrincipalState,\n signPrincipalState,\n} from \"./principalState\";\nexport {\n verifySignedPrincipalState,\n verifySignedPrincipalStateResult,\n} from \"./principalStateVerification\";\nexport {\n captureProjectionHistory,\n type HistoryProjection,\n omitProjectionHistory,\n type RetainedProjectionHistory,\n restoreProjectionHistory,\n} from \"./projectionHistory\";\nexport type { SigningKeyPair } from \"./signing/generateKeyPair\";\nexport {\n generateSigningKeyPair,\n generateSigningSeedAndKeyPair,\n ML_DSA87_PUBLIC_KEY_BYTES,\n ML_DSA87_SECRET_KEY_BYTES,\n ML_DSA87_SIGNATURE_BYTES,\n} from \"./signing/generateKeyPair\";\nexport { sign } from \"./signing/sign\";\nexport { verify } from \"./signing/verify\";\nexport type { SymmetricCiphertext } from \"./symmetric\";\nexport {\n AES_256_KEY_BYTES,\n AES_GCM_IV_BYTES,\n AES_GCM_TAG_BYTES,\n assertAesGcmIv,\n createAesGcmIv,\n decryptWithDek,\n encryptWithDek,\n} from \"./symmetric\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tearleads/client-sdk",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
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 +1 @@
1
- {"version":3,"file":"attachmentPersistence.js","sourceRoot":"","sources":["attachmentPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n deleteLocalDocumentAttachment,\n deletePendingDocumentAttachment,\n type LocalAttachmentRecord,\n type PendingAttachmentRecord,\n saveLocalDocumentAttachments,\n savePendingDocumentAttachment,\n} from \"../../../workflows/documents\";\nimport { withLocalAttachmentDetachState } from \"./attachmentDetachState\";\nimport { withGenerationGuardedMutation } from \"./persistence\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n setReadySnapshot,\n} from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\nexport async function deletePendingAttachment(\n state: DocumentStoreState,\n slotId: string,\n storageKey: string,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n // After a REFUSED discard the reset store keeps its rows, and a stale pass\n // racing that reset must not delete state the refusal deliberately\n // preserved.\n await withGenerationGuardedMutation(state, expectedGeneration, (execSql) =>\n deletePendingDocumentAttachment({\n execSql,\n localId: state.localId,\n persistence: state.persistence,\n slotId,\n storageKey,\n }),\n );\n}\n\nexport async function deleteLocalAttachmentRecord(\n state: DocumentStoreState,\n slotId: string,\n storageKey: string,\n currentDoc: DocumentState | null = state.doc,\n) {\n await deleteLocalDocumentAttachment({\n execSql: state.runtime.infra.execSql,\n localId: state.localId,\n persistence: state.persistence,\n slotId,\n storageKey,\n });\n\n if (state.attachmentStorageKeyBySlotId[slotId] === storageKey) {\n removeLocalAttachmentSlot(state, slotId);\n }\n\n if (currentDoc && currentDoc === state.doc) {\n setReadySnapshot(\n state,\n currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n }\n}\n\nexport async function saveLocalAttachmentRecord(\n state: DocumentStoreState,\n attachment: LocalAttachmentRecord,\n currentDoc: DocumentState | null = state.doc,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n await saveLocalAttachmentRecords(\n state,\n [attachment],\n currentDoc,\n expectedGeneration,\n );\n}\n\nexport async function saveLocalAttachmentRecords(\n state: DocumentStoreState,\n attachments: ReadonlyArray<LocalAttachmentRecord>,\n currentDoc: DocumentState | null = state.doc,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n if (attachments.length === 0) {\n return;\n }\n\n // These rows are upserts, and a stale writer racing a teardown must never\n // re-insert what the teardown just removed.\n const saved = await withGenerationGuardedMutation(\n state,\n expectedGeneration,\n (execSql) =>\n saveLocalDocumentAttachments({\n attachments: withLocalAttachmentDetachState(attachments, currentDoc),\n execSql,\n persistence: state.persistence,\n }),\n );\n if (\n !saved ||\n (expectedGeneration && !isSyncGenerationCurrent(state, expectedGeneration))\n ) {\n return;\n }\n\n installLocalAttachmentRecords(state, attachments, currentDoc);\n}\n\n/** Publish the held copy of each record's slot to the store's slot maps. */\nexport function assignLocalAttachmentSlots(\n state: DocumentStoreState,\n attachments: ReadonlyArray<LocalAttachmentRecord>,\n): void {\n const entries = <T>(pick: (attachment: LocalAttachmentRecord) => T) =>\n Object.fromEntries(\n attachments.map((attachment) => [attachment.slotId, pick(attachment)]),\n );\n state.attachmentBlobIdBySlotId = {\n ...state.attachmentBlobIdBySlotId,\n ...entries((attachment) => attachment.blobId),\n };\n state.attachmentContentSha256BySlotId = {\n ...state.attachmentContentSha256BySlotId,\n ...entries((attachment) => attachment.contentSha256),\n };\n state.attachmentStorageKeyBySlotId = {\n ...state.attachmentStorageKeyBySlotId,\n ...entries((attachment) => attachment.storageKey),\n };\n}\n\n/** Forget the held copy of one slot without touching its durable row. */\nexport function removeLocalAttachmentSlot(\n state: DocumentStoreState,\n slotId: string,\n): void {\n const { [slotId]: _blobId, ...blobIds } = state.attachmentBlobIdBySlotId;\n const { [slotId]: _contentSha256, ...contentSha256s } =\n state.attachmentContentSha256BySlotId;\n const { [slotId]: _storageKey, ...storageKeys } =\n state.attachmentStorageKeyBySlotId;\n state.attachmentBlobIdBySlotId = blobIds;\n state.attachmentContentSha256BySlotId = contentSha256s;\n state.attachmentStorageKeyBySlotId = storageKeys;\n}\n\nexport function installLocalAttachmentRecords(\n state: DocumentStoreState,\n attachments: ReadonlyArray<LocalAttachmentRecord>,\n currentDoc: DocumentState | null,\n) {\n assignLocalAttachmentSlots(state, attachments);\n\n if (currentDoc && currentDoc === state.doc) {\n setReadySnapshot(\n state,\n currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n }\n}\n\nexport function upsertPendingAttachments(\n state: DocumentStoreState,\n nextPendingAttachments: ReadonlyArray<PendingAttachmentRecord>,\n) {\n const nextSlotIds = new Set(\n nextPendingAttachments.map((pendingAttachment) => pendingAttachment.slotId),\n );\n state.pendingAttachments = [\n ...state.pendingAttachments.filter(\n (pendingAttachment) => !nextSlotIds.has(pendingAttachment.slotId),\n ),\n ...nextPendingAttachments,\n ];\n}\n\n/**\n * Persist a pending attachment's upload-resume identity (blob id, content key,\n * IV, and — once staged — the multipart stage id/part size) so a later attempt\n * reuses it instead of orphaning the stage. The caller publishes the saved\n * identity on the live pending record only after this write succeeds.\n */\nexport async function savePendingAttachmentUpload(\n state: DocumentStoreState,\n pendingAttachment: PendingAttachmentRecord,\n expectedGeneration?: DocumentStoreSyncGeneration,\n): Promise<void> {\n // This row is an upsert, and a stale resume racing a teardown must never\n // re-insert the pending row the teardown just removed.\n await withGenerationGuardedMutation(state, expectedGeneration, (execSql) =>\n savePendingDocumentAttachment({\n attachment: pendingAttachment,\n execSql,\n persistence: state.persistence,\n }),\n );\n}\n"]}
1
+ {"version":3,"file":"attachmentPersistence.js","sourceRoot":"","sources":["attachmentPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n deleteLocalDocumentAttachment,\n deletePendingDocumentAttachment,\n type LocalAttachmentRecord,\n type PendingAttachmentRecord,\n saveLocalDocumentAttachments,\n savePendingDocumentAttachment,\n} from \"../../../workflows/documents\";\nimport { withLocalAttachmentDetachState } from \"./attachmentDetachState\";\nimport { withGenerationGuardedMutation } from \"./persistence\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n setReadySnapshot,\n} from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\nexport async function deletePendingAttachment(\n state: DocumentStoreState,\n slotId: string,\n storageKey: string,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n // After a REFUSED discard the reset store keeps its rows, and a stale pass\n // racing that reset must not delete state the refusal deliberately\n // preserved.\n await withGenerationGuardedMutation(state, expectedGeneration, (execSql) =>\n deletePendingDocumentAttachment({\n execSql,\n localId: state.localId,\n persistence: state.persistence,\n slotId,\n storageKey,\n }),\n );\n}\n\nexport async function deleteLocalAttachmentRecord(\n state: DocumentStoreState,\n slotId: string,\n storageKey: string,\n currentDoc: DocumentState | null = state.doc,\n) {\n await deleteLocalDocumentAttachment({\n execSql: state.runtime.infra.execSql,\n localId: state.localId,\n persistence: state.persistence,\n slotId,\n storageKey,\n });\n\n if (state.attachmentStorageKeyBySlotId[slotId] === storageKey) {\n removeLocalAttachmentSlot(state, slotId);\n }\n\n if (currentDoc && currentDoc === state.doc) {\n setReadySnapshot(\n state,\n currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n }\n}\n\nexport async function saveLocalAttachmentRecord(\n state: DocumentStoreState,\n attachment: LocalAttachmentRecord,\n currentDoc: DocumentState | null = state.doc,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n await saveLocalAttachmentRecords(\n state,\n [attachment],\n currentDoc,\n expectedGeneration,\n );\n}\n\nexport async function saveLocalAttachmentRecords(\n state: DocumentStoreState,\n attachments: ReadonlyArray<LocalAttachmentRecord>,\n currentDoc: DocumentState | null = state.doc,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n if (attachments.length === 0) {\n return;\n }\n\n // These rows are upserts, and a stale writer racing a teardown must never\n // re-insert what the teardown just removed.\n const saved = await withGenerationGuardedMutation(\n state,\n expectedGeneration,\n (execSql) =>\n saveLocalDocumentAttachments({\n attachments: withLocalAttachmentDetachState(attachments, currentDoc),\n execSql,\n persistence: state.persistence,\n }),\n );\n if (\n !saved ||\n (expectedGeneration && !isSyncGenerationCurrent(state, expectedGeneration))\n ) {\n return;\n }\n\n installLocalAttachmentRecords(state, attachments, currentDoc);\n}\n\ntype AttachmentSlot = Pick<\n LocalAttachmentRecord,\n \"slotId\" | \"blobId\" | \"contentSha256\" | \"storageKey\"\n>;\n\n/** Publish the held copy of each record's slot to the store's slot maps. */\nexport function assignLocalAttachmentSlots(\n state: DocumentStoreState,\n attachments: ReadonlyArray<AttachmentSlot>,\n): void {\n const entries = <T>(pick: (attachment: AttachmentSlot) => T) =>\n Object.fromEntries(\n attachments.map((attachment) => [attachment.slotId, pick(attachment)]),\n );\n state.attachmentBlobIdBySlotId = {\n ...state.attachmentBlobIdBySlotId,\n ...entries((attachment) => attachment.blobId),\n };\n state.attachmentContentSha256BySlotId = {\n ...state.attachmentContentSha256BySlotId,\n ...entries((attachment) => attachment.contentSha256),\n };\n state.attachmentStorageKeyBySlotId = {\n ...state.attachmentStorageKeyBySlotId,\n ...entries((attachment) => attachment.storageKey),\n };\n}\n\n/** Forget the held copy of one slot without touching its durable row. */\nexport function removeLocalAttachmentSlot(\n state: DocumentStoreState,\n slotId: string,\n): void {\n const { [slotId]: _blobId, ...blobIds } = state.attachmentBlobIdBySlotId;\n const { [slotId]: _contentSha256, ...contentSha256s } =\n state.attachmentContentSha256BySlotId;\n const { [slotId]: _storageKey, ...storageKeys } =\n state.attachmentStorageKeyBySlotId;\n state.attachmentBlobIdBySlotId = blobIds;\n state.attachmentContentSha256BySlotId = contentSha256s;\n state.attachmentStorageKeyBySlotId = storageKeys;\n}\n\nexport function installLocalAttachmentRecords(\n state: DocumentStoreState,\n attachments: ReadonlyArray<LocalAttachmentRecord>,\n currentDoc: DocumentState | null,\n) {\n assignLocalAttachmentSlots(state, attachments);\n\n if (currentDoc && currentDoc === state.doc) {\n setReadySnapshot(\n state,\n currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n }\n}\n\nexport function upsertPendingAttachments(\n state: DocumentStoreState,\n nextPendingAttachments: ReadonlyArray<PendingAttachmentRecord>,\n) {\n const nextSlotIds = new Set(\n nextPendingAttachments.map((pendingAttachment) => pendingAttachment.slotId),\n );\n state.pendingAttachments = [\n ...state.pendingAttachments.filter(\n (pendingAttachment) => !nextSlotIds.has(pendingAttachment.slotId),\n ),\n ...nextPendingAttachments,\n ];\n}\n\n/**\n * Persist a pending attachment's upload-resume identity (blob id, content key,\n * IV, and — once staged — the multipart stage id/part size) so a later attempt\n * reuses it instead of orphaning the stage. The caller publishes the saved\n * identity on the live pending record only after this write succeeds.\n */\nexport async function savePendingAttachmentUpload(\n state: DocumentStoreState,\n pendingAttachment: PendingAttachmentRecord,\n expectedGeneration?: DocumentStoreSyncGeneration,\n): Promise<void> {\n // This row is an upsert, and a stale resume racing a teardown must never\n // re-insert the pending row the teardown just removed.\n await withGenerationGuardedMutation(state, expectedGeneration, (execSql) =>\n savePendingDocumentAttachment({\n attachment: pendingAttachment,\n execSql,\n persistence: state.persistence,\n }),\n );\n}\n"]}
@@ -4,7 +4,7 @@ import { createPendingUpdateFields } from "../../../data/documents/documentSync.
4
4
  import { requestDocumentStoreSync } from "../registry.js";
5
5
  import { buildAttachmentIntent, buildPendingAttachments, } from "./attachmentIntents.js";
6
6
  import { persistStagedAttachmentMutation, restoreFailedAttachmentMutation, } from "./attachmentMutationPersistence.js";
7
- import { removeLocalAttachmentSlot } from "./attachmentPersistence.js";
7
+ import { assignLocalAttachmentSlots, removeLocalAttachmentSlot, } from "./attachmentPersistence.js";
8
8
  import { deleteUnreferencedStagedAttachmentBytes, installPendingAttachmentRows, stagePendingAttachments, } from "./attachmentStaging.js";
9
9
  import { captureAttachmentWriteGeneration, isAttachmentWriteGenerationCurrent, shouldAbortAttachmentPersistFollowup, } from "./attachmentWriteGeneration.js";
10
10
  import { ensureDocumentStoreReady } from "./initialization.js";
@@ -176,9 +176,20 @@ function installCommittedAttachmentRemoval(input) {
176
176
  const { slotId, state, storageKey } = input;
177
177
  if (!storageKey)
178
178
  return;
179
+ const pending = state.pendingAttachments.find((attachment) => attachment.slotId === slotId && attachment.storageKey === storageKey);
180
+ if (input.syncedAttachment && pending?.upload) {
181
+ assignLocalAttachmentSlots(state, [
182
+ {
183
+ slotId,
184
+ storageKey,
185
+ blobId: pending.upload.blobId,
186
+ contentSha256: pending.contentSha256,
187
+ },
188
+ ]);
189
+ }
179
190
  state.pendingAttachments = state.pendingAttachments.filter((attachment) => attachment.slotId !== slotId || attachment.storageKey !== storageKey);
180
191
  // The committed removal queued this key. A reset can leave another document
181
- // uploading from the same copy, and a pending-only slot leaves no marker.
192
+ // uploading from the same copy; a detached marker keeps it until sync.
182
193
  void runDocumentOrphanMaintenance(state);
183
194
  if (input.syncedAttachment ||
184
195
  state.attachmentStorageKeyBySlotId[slotId] !== storageKey) {
@@ -1 +1 @@
1
- {"version":3,"file":"attachments.js","sourceRoot":"","sources":["attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n encodeVersionVector,\n exportFullHistorySnapshot,\n} from \"@tearleads/loro\";\nimport {\n addDocumentAttachments,\n getDocumentAttachments,\n removeDocumentAttachment,\n} from \"../../../data/documents/documentContent\";\nimport { createPendingUpdateFields } from \"../../../data/documents/documentSync\";\nimport type { PendingAttachmentRecord } from \"../../../workflows/documents\";\nimport { requestDocumentStoreSync } from \"../registry\";\nimport type { DocumentAttachmentUpload } from \"../types\";\nimport {\n buildAttachmentIntent,\n buildPendingAttachments,\n} from \"./attachmentIntents\";\nimport {\n persistStagedAttachmentMutation,\n restoreFailedAttachmentMutation,\n} from \"./attachmentMutationPersistence\";\nimport { removeLocalAttachmentSlot } from \"./attachmentPersistence\";\nimport {\n deleteUnreferencedStagedAttachmentBytes,\n installPendingAttachmentRows,\n stagePendingAttachments,\n} from \"./attachmentStaging\";\nimport {\n type AttachmentWriteGeneration,\n captureAttachmentWriteGeneration,\n isAttachmentWriteGenerationCurrent,\n shouldAbortAttachmentPersistFollowup,\n} from \"./attachmentWriteGeneration\";\nimport { ensureDocumentStoreReady } from \"./initialization\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport {\n advancePendingBaseVersion,\n pendingDeltaSinceBase,\n persistDocument,\n} from \"./persistence\";\nimport { reportDocumentStoreWriteFailure } from \"./reportWriteFailure\";\nimport {\n canAttachFiles,\n canWriteDocument,\n type DocumentStoreState,\n setReadySnapshot,\n} from \"./state\";\nimport { captureDocumentStoreSyncGeneration } from \"./syncGeneration\";\n\n// Fixed literal: a reported message must never carry file names or slot ids.\nconst ATTACHMENT_FAILURE_MESSAGE = \"Documents: attachment mutation failed\";\n\nfunction logAttachedFiles(state: DocumentStoreState, count: number) {\n state.runtime.util.log(\n state.runtime.state.online && state.runtime.auth.isAuthenticated\n ? `Attached ${count} file${count === 1 ? \"\" : \"s\"} to document ${state.localId}.`\n : `Stored ${count} attachment${count === 1 ? \"\" : \"s\"} locally for document ${state.localId}.`,\n );\n}\n\nasync function persistAttachedFiles(\n state: DocumentStoreState,\n files: ReadonlyArray<DocumentAttachmentUpload>,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n\n if (!currentDoc || !canAttachFiles(state)) {\n state.runtime.util.log(\n \"Documents: attachments require a local key package.\",\n );\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const { nextAttachments, nextPendingAttachments } =\n await buildPendingAttachments(state.localId, files);\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n addDocumentAttachments(currentDoc, nextAttachments);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n // Captured at delta time: the enqueued row proves coverage up to exactly\n // this version, so it is the frontier the persist below may publish.\n const coveredVersion = encodeVersionVector(currentDoc);\n\n let staged: Awaited<ReturnType<typeof stagePendingAttachments>>;\n try {\n staged = await stagePendingAttachments({\n files,\n generation,\n pendingAttachments: nextPendingAttachments,\n state,\n });\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (!staged) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n return;\n }\n // This persist changes attachments, not prose. setReadySnapshot always\n // re-derives attachments from the doc, but preserve the text/structured\n // fields so an attach that overlaps an in-flight keystroke does not republish\n // a stale doc read over the live optimistic editor value.\n const persisted = await persistStagedAttachmentMutation({\n attachmentUpdate,\n coveredVersion,\n currentDoc,\n generation,\n rollbackSnapshot,\n staged,\n state,\n });\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n await deleteUnreferencedStagedAttachmentBytes({\n generation,\n pendingAttachments: staged.pendingAttachments,\n state,\n });\n return;\n }\n installPendingAttachmentRows({ ...staged, state });\n advancePendingBaseVersion(state, currentDoc);\n logAttachedFiles(state, files.length);\n requestDocumentStoreSync(state);\n}\n\nasync function persistSlotAttachmentFile(\n state: DocumentStoreState,\n slotId: string,\n file: DocumentAttachmentUpload,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n\n if (!currentDoc || !canAttachFiles(state)) {\n state.runtime.util.log(\n \"Documents: slot attachments require a local key package.\",\n );\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const replacementAttachment = await buildAttachmentIntent(slotId, file);\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n addDocumentAttachments(currentDoc, [replacementAttachment]);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n const coveredVersion = encodeVersionVector(currentDoc);\n\n const storageKey = `${state.localId}-${slotId}-${crypto.randomUUID()}`;\n const pendingAttachment: PendingAttachmentRecord = {\n byteLength: replacementAttachment.byteLength,\n contentSha256: replacementAttachment.contentSha256,\n localId: state.localId,\n mimeType: replacementAttachment.mimeType,\n name: replacementAttachment.name,\n slotId,\n storageKey,\n };\n let staged: Awaited<ReturnType<typeof stagePendingAttachments>>;\n try {\n staged = await stagePendingAttachments({\n files: [file],\n generation,\n pendingAttachments: [pendingAttachment],\n state,\n });\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (!staged) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n return;\n }\n // Preserve the optimistic text/structured fields (see persistAttachedFiles):\n // a slot replacement overlapping a keystroke must not regress the editor.\n const persisted = await persistStagedAttachmentMutation({\n attachmentUpdate,\n coveredVersion,\n currentDoc,\n generation,\n rollbackSnapshot,\n staged,\n state,\n });\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n await deleteUnreferencedStagedAttachmentBytes({\n generation,\n pendingAttachments: staged.pendingAttachments,\n state,\n });\n return;\n }\n installPendingAttachmentRows({ ...staged, state });\n advancePendingBaseVersion(state, currentDoc);\n state.runtime.util.log(`Queued attachment ${file.name} for slot ${slotId}.`);\n requestDocumentStoreSync(state);\n}\n\nfunction installCommittedAttachmentRemoval(input: {\n currentDoc: NonNullable<DocumentStoreState[\"doc\"]>;\n slotId: string;\n state: DocumentStoreState;\n storageKey: string | undefined;\n syncedAttachment: boolean;\n}): void {\n const { slotId, state, storageKey } = input;\n if (!storageKey) return;\n state.pendingAttachments = state.pendingAttachments.filter(\n (attachment) =>\n attachment.slotId !== slotId || attachment.storageKey !== storageKey,\n );\n // The committed removal queued this key. A reset can leave another document\n // uploading from the same copy, and a pending-only slot leaves no marker.\n void runDocumentOrphanMaintenance(state);\n if (\n input.syncedAttachment ||\n state.attachmentStorageKeyBySlotId[slotId] !== storageKey\n ) {\n return;\n }\n removeLocalAttachmentSlot(state, slotId);\n setReadySnapshot(\n state,\n input.currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n}\n\nasync function persistRemovedAttachment(\n state: DocumentStoreState,\n slotId: string,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n if (!currentDoc) {\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const existingAttachment = getDocumentAttachments(currentDoc).find(\n (attachment) => attachment.slotId === slotId,\n );\n if (!existingAttachment) {\n return;\n }\n\n // After a reset, a store opened cold holds the slot only as a pending upload.\n const storageKey =\n state.attachmentStorageKeyBySlotId[slotId] ??\n state.pendingAttachments.find((attachment) => attachment.slotId === slotId)\n ?.storageKey;\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n removeDocumentAttachment(currentDoc, slotId);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n const coveredVersion = encodeVersionVector(currentDoc);\n const pendingUpdate = createPendingUpdateFields(attachmentUpdate);\n const syncedAttachment = Boolean(state.record?.documentId);\n\n // Preserve the optimistic text/structured fields (see persistAttachedFiles):\n // removing an attachment while typing must not regress the editor.\n let persisted: Awaited<ReturnType<typeof persistDocument>>;\n try {\n persisted = await persistDocument(\n state,\n currentDoc,\n { snapshotEndVersion: coveredVersion },\n {\n ...(storageKey\n ? {\n attachmentRemoval: {\n mode: syncedAttachment\n ? (\"detach\" as const)\n : (\"delete\" as const),\n slotId,\n storageKey,\n },\n }\n : {}),\n ...(pendingUpdate ? { pendingUpdate } : {}),\n preserveSnapshotStructuredFields: true,\n preserveSnapshotText: true,\n },\n generation,\n );\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n // See persistAttachedFiles: only durable same-identity work re-arms sync.\n return;\n }\n installCommittedAttachmentRemoval({\n currentDoc,\n slotId,\n state,\n storageKey,\n syncedAttachment,\n });\n advancePendingBaseVersion(state, currentDoc);\n state.runtime.util.log(\n `Removed attachment ${existingAttachment.name} from document ${state.localId}.`,\n );\n requestDocumentStoreSync(state);\n}\n\nexport async function attachFilesToDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n files: ReadonlyArray<DocumentAttachmentUpload>,\n) {\n if (files.length === 0) {\n return;\n }\n\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () => persistAttachedFiles(state, files, writeGeneration))\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n\nexport async function removeAttachmentFromDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n slotId: string,\n) {\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () => persistRemovedAttachment(state, slotId, writeGeneration))\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n\nexport async function replaceAttachmentInDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n slotId: string,\n file: DocumentAttachmentUpload,\n) {\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () =>\n persistSlotAttachmentFile(state, slotId, file, writeGeneration),\n )\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n"]}
1
+ {"version":3,"file":"attachments.js","sourceRoot":"","sources":["attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n encodeVersionVector,\n exportFullHistorySnapshot,\n} from \"@tearleads/loro\";\nimport {\n addDocumentAttachments,\n getDocumentAttachments,\n removeDocumentAttachment,\n} from \"../../../data/documents/documentContent\";\nimport { createPendingUpdateFields } from \"../../../data/documents/documentSync\";\nimport type { PendingAttachmentRecord } from \"../../../workflows/documents\";\nimport { requestDocumentStoreSync } from \"../registry\";\nimport type { DocumentAttachmentUpload } from \"../types\";\nimport {\n buildAttachmentIntent,\n buildPendingAttachments,\n} from \"./attachmentIntents\";\nimport {\n persistStagedAttachmentMutation,\n restoreFailedAttachmentMutation,\n} from \"./attachmentMutationPersistence\";\nimport {\n assignLocalAttachmentSlots,\n removeLocalAttachmentSlot,\n} from \"./attachmentPersistence\";\nimport {\n deleteUnreferencedStagedAttachmentBytes,\n installPendingAttachmentRows,\n stagePendingAttachments,\n} from \"./attachmentStaging\";\nimport {\n type AttachmentWriteGeneration,\n captureAttachmentWriteGeneration,\n isAttachmentWriteGenerationCurrent,\n shouldAbortAttachmentPersistFollowup,\n} from \"./attachmentWriteGeneration\";\nimport { ensureDocumentStoreReady } from \"./initialization\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport {\n advancePendingBaseVersion,\n pendingDeltaSinceBase,\n persistDocument,\n} from \"./persistence\";\nimport { reportDocumentStoreWriteFailure } from \"./reportWriteFailure\";\nimport {\n canAttachFiles,\n canWriteDocument,\n type DocumentStoreState,\n setReadySnapshot,\n} from \"./state\";\nimport { captureDocumentStoreSyncGeneration } from \"./syncGeneration\";\n\n// Fixed literal: a reported message must never carry file names or slot ids.\nconst ATTACHMENT_FAILURE_MESSAGE = \"Documents: attachment mutation failed\";\n\nfunction logAttachedFiles(state: DocumentStoreState, count: number) {\n state.runtime.util.log(\n state.runtime.state.online && state.runtime.auth.isAuthenticated\n ? `Attached ${count} file${count === 1 ? \"\" : \"s\"} to document ${state.localId}.`\n : `Stored ${count} attachment${count === 1 ? \"\" : \"s\"} locally for document ${state.localId}.`,\n );\n}\n\nasync function persistAttachedFiles(\n state: DocumentStoreState,\n files: ReadonlyArray<DocumentAttachmentUpload>,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n\n if (!currentDoc || !canAttachFiles(state)) {\n state.runtime.util.log(\n \"Documents: attachments require a local key package.\",\n );\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const { nextAttachments, nextPendingAttachments } =\n await buildPendingAttachments(state.localId, files);\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n addDocumentAttachments(currentDoc, nextAttachments);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n // Captured at delta time: the enqueued row proves coverage up to exactly\n // this version, so it is the frontier the persist below may publish.\n const coveredVersion = encodeVersionVector(currentDoc);\n\n let staged: Awaited<ReturnType<typeof stagePendingAttachments>>;\n try {\n staged = await stagePendingAttachments({\n files,\n generation,\n pendingAttachments: nextPendingAttachments,\n state,\n });\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (!staged) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n return;\n }\n // This persist changes attachments, not prose. setReadySnapshot always\n // re-derives attachments from the doc, but preserve the text/structured\n // fields so an attach that overlaps an in-flight keystroke does not republish\n // a stale doc read over the live optimistic editor value.\n const persisted = await persistStagedAttachmentMutation({\n attachmentUpdate,\n coveredVersion,\n currentDoc,\n generation,\n rollbackSnapshot,\n staged,\n state,\n });\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n await deleteUnreferencedStagedAttachmentBytes({\n generation,\n pendingAttachments: staged.pendingAttachments,\n state,\n });\n return;\n }\n installPendingAttachmentRows({ ...staged, state });\n advancePendingBaseVersion(state, currentDoc);\n logAttachedFiles(state, files.length);\n requestDocumentStoreSync(state);\n}\n\nasync function persistSlotAttachmentFile(\n state: DocumentStoreState,\n slotId: string,\n file: DocumentAttachmentUpload,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n\n if (!currentDoc || !canAttachFiles(state)) {\n state.runtime.util.log(\n \"Documents: slot attachments require a local key package.\",\n );\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const replacementAttachment = await buildAttachmentIntent(slotId, file);\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n addDocumentAttachments(currentDoc, [replacementAttachment]);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n const coveredVersion = encodeVersionVector(currentDoc);\n\n const storageKey = `${state.localId}-${slotId}-${crypto.randomUUID()}`;\n const pendingAttachment: PendingAttachmentRecord = {\n byteLength: replacementAttachment.byteLength,\n contentSha256: replacementAttachment.contentSha256,\n localId: state.localId,\n mimeType: replacementAttachment.mimeType,\n name: replacementAttachment.name,\n slotId,\n storageKey,\n };\n let staged: Awaited<ReturnType<typeof stagePendingAttachments>>;\n try {\n staged = await stagePendingAttachments({\n files: [file],\n generation,\n pendingAttachments: [pendingAttachment],\n state,\n });\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (!staged) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n return;\n }\n // Preserve the optimistic text/structured fields (see persistAttachedFiles):\n // a slot replacement overlapping a keystroke must not regress the editor.\n const persisted = await persistStagedAttachmentMutation({\n attachmentUpdate,\n coveredVersion,\n currentDoc,\n generation,\n rollbackSnapshot,\n staged,\n state,\n });\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n await deleteUnreferencedStagedAttachmentBytes({\n generation,\n pendingAttachments: staged.pendingAttachments,\n state,\n });\n return;\n }\n installPendingAttachmentRows({ ...staged, state });\n advancePendingBaseVersion(state, currentDoc);\n state.runtime.util.log(`Queued attachment ${file.name} for slot ${slotId}.`);\n requestDocumentStoreSync(state);\n}\n\nfunction installCommittedAttachmentRemoval(input: {\n currentDoc: NonNullable<DocumentStoreState[\"doc\"]>;\n slotId: string;\n state: DocumentStoreState;\n storageKey: string | undefined;\n syncedAttachment: boolean;\n}): void {\n const { slotId, state, storageKey } = input;\n if (!storageKey) return;\n const pending = state.pendingAttachments.find(\n (attachment) =>\n attachment.slotId === slotId && attachment.storageKey === storageKey,\n );\n if (input.syncedAttachment && pending?.upload) {\n assignLocalAttachmentSlots(state, [\n {\n slotId,\n storageKey,\n blobId: pending.upload.blobId,\n contentSha256: pending.contentSha256,\n },\n ]);\n }\n state.pendingAttachments = state.pendingAttachments.filter(\n (attachment) =>\n attachment.slotId !== slotId || attachment.storageKey !== storageKey,\n );\n // The committed removal queued this key. A reset can leave another document\n // uploading from the same copy; a detached marker keeps it until sync.\n void runDocumentOrphanMaintenance(state);\n if (\n input.syncedAttachment ||\n state.attachmentStorageKeyBySlotId[slotId] !== storageKey\n ) {\n return;\n }\n removeLocalAttachmentSlot(state, slotId);\n setReadySnapshot(\n state,\n input.currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n}\n\nasync function persistRemovedAttachment(\n state: DocumentStoreState,\n slotId: string,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n if (!currentDoc) {\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const existingAttachment = getDocumentAttachments(currentDoc).find(\n (attachment) => attachment.slotId === slotId,\n );\n if (!existingAttachment) {\n return;\n }\n\n // After a reset, a store opened cold holds the slot only as a pending upload.\n const storageKey =\n state.attachmentStorageKeyBySlotId[slotId] ??\n state.pendingAttachments.find((attachment) => attachment.slotId === slotId)\n ?.storageKey;\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n removeDocumentAttachment(currentDoc, slotId);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n const coveredVersion = encodeVersionVector(currentDoc);\n const pendingUpdate = createPendingUpdateFields(attachmentUpdate);\n const syncedAttachment = Boolean(state.record?.documentId);\n\n // Preserve the optimistic text/structured fields (see persistAttachedFiles):\n // removing an attachment while typing must not regress the editor.\n let persisted: Awaited<ReturnType<typeof persistDocument>>;\n try {\n persisted = await persistDocument(\n state,\n currentDoc,\n { snapshotEndVersion: coveredVersion },\n {\n ...(storageKey\n ? {\n attachmentRemoval: {\n mode: syncedAttachment\n ? (\"detach\" as const)\n : (\"delete\" as const),\n slotId,\n storageKey,\n },\n }\n : {}),\n ...(pendingUpdate ? { pendingUpdate } : {}),\n preserveSnapshotStructuredFields: true,\n preserveSnapshotText: true,\n },\n generation,\n );\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n // See persistAttachedFiles: only durable same-identity work re-arms sync.\n return;\n }\n installCommittedAttachmentRemoval({\n currentDoc,\n slotId,\n state,\n storageKey,\n syncedAttachment,\n });\n advancePendingBaseVersion(state, currentDoc);\n state.runtime.util.log(\n `Removed attachment ${existingAttachment.name} from document ${state.localId}.`,\n );\n requestDocumentStoreSync(state);\n}\n\nexport async function attachFilesToDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n files: ReadonlyArray<DocumentAttachmentUpload>,\n) {\n if (files.length === 0) {\n return;\n }\n\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () => persistAttachedFiles(state, files, writeGeneration))\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n\nexport async function removeAttachmentFromDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n slotId: string,\n) {\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () => persistRemovedAttachment(state, slotId, writeGeneration))\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n\nexport async function replaceAttachmentInDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n slotId: string,\n file: DocumentAttachmentUpload,\n) {\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () =>\n persistSlotAttachmentFile(state, slotId, file, writeGeneration),\n )\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { bytesToHex, CONTENT_RECORD_ENCRYPTION_SUITE, computeBlobAccessManifestHash, computeBlobContentKeyTargetHash, computeContentRecordNonceDomainHash, makeVerifiedBlobKekTargets, verifyWriteHeader, } from "../../internal/crypto/index.js";
2
- import { blobContentMetadataHash, deriveBlobContentRecordKey, } from "../../data/documents/blob/shared/crypto.js";
1
+ import { blobContentMetadataHash, bytesToHex, CONTENT_RECORD_ENCRYPTION_SUITE, computeBlobAccessManifestHash, computeBlobContentKeyTargetHash, computeContentRecordNonceDomainHash, makeVerifiedBlobKekTargets, verifyWriteHeader, } from "../../internal/crypto/index.js";
2
+ import { deriveBlobContentRecordKey } from "../../data/documents/blob/shared/crypto.js";
3
3
  import { unwrapBlobContentKey } from "../../data/documents/blob/shared/projection.js";
4
4
  import { parseBlobEncryptedBytes, readBlobKekTarget, readDocumentManifestIdentity, sortBlobTargets, } from "../../data/documents/blob/shared/readers.js";
5
5
  import { assertBlobContentKeyBundleTargetHash } from "../../data/documents/blob/shared/responses.js";
@@ -1 +1 @@
1
- {"version":3,"file":"decrypt.js","sourceRoot":"","sources":["decrypt.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n bytesToHex,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeBlobAccessManifestHash,\n computeBlobContentKeyTargetHash,\n computeContentRecordNonceDomainHash,\n makeVerifiedBlobKekTargets,\n verifyWriteHeader,\n} from \"@tearleads/crypto\";\nimport type { BlobKekTargetsResponse } from \"@tearleads/validators/response\";\nimport type { BlobBytes } from \"../../data/blobContracts\";\nimport {\n blobContentMetadataHash,\n deriveBlobContentRecordKey,\n} from \"../../data/documents/blob/shared/crypto\";\nimport { unwrapBlobContentKey } from \"../../data/documents/blob/shared/projection\";\nimport {\n parseBlobEncryptedBytes,\n readBlobKekTarget,\n readDocumentManifestIdentity,\n sortBlobTargets,\n} from \"../../data/documents/blob/shared/readers\";\nimport { assertBlobContentKeyBundleTargetHash } from \"../../data/documents/blob/shared/responses\";\nimport type {\n BlobEncryptedBytesHeader,\n DecryptDocumentAttachmentBlobInput,\n} from \"../../data/documents/blob/shared/types\";\nimport { importContentKeyMaterial } from \"../../data/documents/shared/contentRecordKeys\";\nimport { assertDocumentWriterProjectionConsistent } from \"../../data/documents/shared/projection\";\nimport {\n asWebCryptoBytes,\n readWriteHeader,\n uniqueSortedStrings,\n} from \"../../data/documents/shared/readers\";\nimport { projectionVerificationOptions } from \"../../data/documents/shared/types\";\nimport { readCanonicalRecord } from \"../../data/keyingCanonicalJson\";\nimport type { DocumentWriterProjectionAuthorization } from \"../../data/keyingProjectionVerification\";\nimport {\n readRecordString,\n requireProjectionUserKeyResolver,\n} from \"../../data/keyingProjectionVerification\";\nimport { resolveEventContainerPaths } from \"../../data/keyingProjectionVerification/documentDependencyPaths\";\n\nimport { assertAttachmentBindingVerified } from \"./attachmentBindingVerification\";\nimport { decryptBlobChunks } from \"./blobChunkDecryption\";\nimport { assertBlobWrapScopeVerified } from \"./verifiedWrapScope\";\n\nasync function assertBlobEncryptionMetadata(input: {\n readonly contentKeyBundle: DecryptDocumentAttachmentBlobInput[\"binding\"][\"contentKeyBundle\"];\n readonly encrypted: BlobEncryptedBytesHeader;\n readonly expectedBlobId: string;\n readonly organizationId: string;\n}): Promise<void> {\n const { contentKeyBundle, encrypted, expectedBlobId, organizationId } = input;\n if (\n encrypted.blobId !== expectedBlobId ||\n encrypted.contentRecordId !== expectedBlobId\n ) {\n throw new Error(\"Blob encrypted bytes blob id mismatch\");\n }\n if (\n encrypted.contentKeyEpoch !== contentKeyBundle.contentKeyEpoch ||\n contentKeyBundle.blobId !== expectedBlobId\n ) {\n throw new Error(\"Blob encrypted bytes content-key bundle mismatch\");\n }\n await assertBlobContentKeyBundleTargetHash(contentKeyBundle);\n\n const expectedNonceDomainHash = await computeContentRecordNonceDomainHash({\n version: 1,\n organizationId,\n objectKind: \"blob\",\n objectId: expectedBlobId,\n contentKeyEpoch: encrypted.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId: encrypted.contentRecordId,\n });\n if (encrypted.nonceDomainHash !== expectedNonceDomainHash) {\n throw new Error(\"Blob encrypted bytes nonce domain mismatch\");\n }\n const expectedMetadataHash = await blobContentMetadataHash({\n blobId: expectedBlobId,\n byteLength: encrypted.byteLength,\n chunkCount: encrypted.chunkCount,\n chunkSize: encrypted.chunkSize,\n contentKeyEpoch: encrypted.contentKeyEpoch,\n });\n if (encrypted.metadataHash !== expectedMetadataHash) {\n throw new Error(\"Blob encrypted bytes metadata hash mismatch\");\n }\n}\n\nexport type AttachmentBlobDecryptionInput = Omit<\n DecryptDocumentAttachmentBlobInput,\n \"encryptedBytes\"\n> & {\n encrypted: BlobEncryptedBytesHeader;\n};\n\n/** Verifies metadata and authority; the caller must still authenticate ciphertext. */\nexport async function prepareDocumentAttachmentBlobDecryption({\n binding,\n encrypted,\n expectedDocumentId,\n expectedSlotId,\n execSql,\n resolveProjectionUserKey,\n targetSecretKey,\n stillCurrent,\n warmReferencedPrincipalPolicies,\n writerProjection,\n}: AttachmentBlobDecryptionInput): Promise<{\n contentKey: Uint8Array;\n recordKey: CryptoKey;\n organizationId: string;\n}> {\n const requiredResolveProjectionUserKey = requireProjectionUserKeyResolver(\n resolveProjectionUserKey,\n \"Document attachment blob decrypt\",\n );\n const verificationOptions = projectionVerificationOptions({\n execSql,\n stillCurrent,\n warmReferencedPrincipalPolicies,\n resolveProjectionUserKey: requiredResolveProjectionUserKey,\n });\n let documentAuthorization: DocumentWriterProjectionAuthorization | undefined;\n await assertDocumentWriterProjectionConsistent(writerProjection, {\n allowStaleContentKeyBundle: true,\n execSql,\n onVerifiedAuthorization: (authorization) => {\n documentAuthorization = authorization;\n },\n ...verificationOptions,\n });\n const { documentId, organizationId } =\n readDocumentManifestIdentity(writerProjection);\n await assertBlobEncryptionMetadata({\n contentKeyBundle: binding.contentKeyBundle,\n encrypted,\n expectedBlobId: binding.blobId,\n organizationId,\n });\n await assertAttachmentBindingVerified({\n authorization: documentAuthorization,\n binding,\n expectedDocumentId,\n expectedSlotId,\n resolveProjectionUserKey: requiredResolveProjectionUserKey,\n });\n await assertBlobWriteHeaderVerified({\n authorization: documentAuthorization,\n binding,\n encrypted,\n organizationId,\n resolveProjectionUserKey: requiredResolveProjectionUserKey,\n });\n\n if (!documentAuthorization)\n throw new Error(\"Blob document authorization is unavailable\");\n assertBlobWrapScopeVerified({\n authorization: documentAuthorization,\n binding,\n documentId,\n });\n const contentKey = await unwrapBlobContentKey({\n blobId: binding.blobId,\n contentKeyBundle: binding.contentKeyBundle,\n documentId,\n encrypted,\n execSql,\n expectedBindingId: binding.bindingId,\n secretKey: targetSecretKey,\n ...verificationOptions,\n writerProjection,\n });\n const contentKeyMaterial = await importContentKeyMaterial(contentKey);\n const recordKey = await deriveBlobContentRecordKey({\n blobId: binding.blobId,\n contentKeyEpoch: encrypted.contentKeyEpoch,\n contentKeyMaterial,\n contentRecordId: encrypted.contentRecordId,\n organizationId,\n usage: \"decrypt\",\n });\n return { contentKey, recordKey, organizationId };\n}\n\nasync function decryptDocumentAttachmentBlobWithKey(\n input: DecryptDocumentAttachmentBlobInput,\n): Promise<{ bytes: BlobBytes; contentKey: Uint8Array }> {\n const encrypted = parseBlobEncryptedBytes(input.encryptedBytes);\n const context = await prepareDocumentAttachmentBlobDecryption({\n ...input,\n encrypted,\n });\n const header = readWriteHeader(\n input.binding.writeHeader,\n \"Attachment blob write header\",\n );\n const ciphertextHash = bytesToHex(\n new Uint8Array(\n await crypto.subtle.digest(\n \"SHA-256\",\n asWebCryptoBytes(input.encryptedBytes),\n ),\n ),\n );\n if (header.ciphertextHash !== ciphertextHash)\n throw new Error(\"Attachment blob write header does not match ciphertext\");\n const bytes = await decryptBlobChunks({\n ...context,\n encrypted,\n expectedBlobId: input.binding.blobId,\n });\n return { bytes, contentKey: context.contentKey };\n}\n\nexport async function decryptDocumentAttachmentBlob(\n input: DecryptDocumentAttachmentBlobInput,\n): Promise<BlobBytes> {\n return (await decryptDocumentAttachmentBlobWithKey(input)).bytes;\n}\n\nfunction assertStringSetEquals(input: {\n readonly actual: readonly string[];\n readonly expected: readonly string[];\n readonly label: string;\n}): void {\n const actual = uniqueSortedStrings(input.actual);\n const expected = uniqueSortedStrings(input.expected);\n if (\n actual.length !== expected.length ||\n actual.some((value, index) => value !== expected[index])\n ) {\n throw new Error(`${input.label} is inconsistent`);\n }\n}\n\nasync function verifiedBlobKekTargetsForBinding(input: {\n readonly binding: DecryptDocumentAttachmentBlobInput[\"binding\"];\n readonly requireBindingMembership: boolean;\n readonly projection: BlobKekTargetsResponse | undefined;\n}) {\n const { binding, projection } = input;\n if (!projection) {\n throw new Error(\"Attachment binding lacks blob KEK verification material\");\n }\n const targets = sortBlobTargets(\n projection.targets.map((target, index) =>\n readBlobKekTarget(target, `Attachment blob KEK target[${index}]`),\n ),\n );\n const targetHash = await computeBlobContentKeyTargetHash(targets);\n if (\n targetHash !== projection.blobKeyTargetHash ||\n projection.blobId !== binding.blobId\n ) {\n throw new Error(\"Attachment blob KEK target hash is inconsistent\");\n }\n if (\n input.requireBindingMembership &&\n (!projection.activeBindingIds.includes(binding.bindingId) ||\n !targets.some(\n (target) =>\n target.bindingId === binding.bindingId &&\n target.documentId === attachmentBindingDocumentId(binding),\n ))\n ) {\n throw new Error(\"Attachment binding is absent from blob KEK targets\");\n }\n assertStringSetEquals({\n actual: projection.linkedContainerManifestHashes,\n expected: targets.map((target) => target.containerManifestHash),\n label: \"Attachment blob container manifests\",\n });\n assertStringSetEquals({\n actual: projection.linkedContainerKeyEpochIds,\n expected: targets.map((target) => target.containerKeyEpochId),\n label: \"Attachment blob container KEKs\",\n });\n const accessManifestHash = await computeBlobAccessManifestHash({\n version: 1,\n blobId: projection.blobId,\n organizationId: projection.organizationId,\n activeBindingIds: projection.activeBindingIds,\n documentManifestHashes: projection.documentManifestHashes,\n linkedContainerManifestHashes: projection.linkedContainerManifestHashes,\n linkedContainerKeyEpochIds: projection.linkedContainerKeyEpochIds,\n blobKeyTargetHash: projection.blobKeyTargetHash,\n });\n if (accessManifestHash !== projection.blobAccessManifestHash) {\n throw new Error(\"Attachment blob access manifest hash is inconsistent\");\n }\n return makeVerifiedBlobKekTargets({ ...projection, targets });\n}\n\nfunction attachmentBindingDocumentId(\n binding: DecryptDocumentAttachmentBlobInput[\"binding\"],\n): string {\n if (!binding.bindingEvent) {\n throw new Error(\"Attachment binding lacks a signed event\");\n }\n return readRecordString(\n readCanonicalRecord(\n binding.bindingEvent.body,\n \"Attachment binding event body\",\n ),\n \"documentId\",\n \"Attachment binding event body\",\n );\n}\n\nasync function assertBlobWriteHeaderVerified(input: {\n readonly authorization: DocumentWriterProjectionAuthorization | undefined;\n readonly binding: DecryptDocumentAttachmentBlobInput[\"binding\"];\n readonly encrypted: BlobEncryptedBytesHeader;\n readonly organizationId: string;\n readonly resolveProjectionUserKey: ReturnType<\n typeof requireProjectionUserKeyResolver\n >;\n}): Promise<void> {\n if (!input.authorization || !input.binding.writeHeader) {\n throw new Error(\"Attachment blob lacks verified write authority\");\n }\n const header = readWriteHeader(\n input.binding.writeHeader,\n \"Attachment blob write header\",\n );\n const writer = await input.resolveProjectionUserKey(header.writerUserId);\n if (!writer) {\n throw new Error(\"Attachment blob writer identity is unavailable\");\n }\n await verifiedBlobKekTargetsForBinding({\n binding: input.binding,\n projection: input.binding.blobKekTargets,\n requireBindingMembership: true,\n });\n const blobKekTargets = await verifiedBlobKekTargetsForBinding({\n binding: input.binding,\n projection: input.binding.writeAuthorization,\n // The signed binding proves which slot exposes the blob now. Writer\n // authorization describes the older target set committed when the bytes\n // were written, so a later valid binding need not appear in that set.\n requireBindingMembership: false,\n });\n if (\n header.metadataHash !== input.encrypted.metadataHash ||\n header.contentKeyEpoch !== input.encrypted.contentKeyEpoch ||\n header.contentRecordId !== input.encrypted.contentRecordId ||\n header.nonceDomainHash !== input.encrypted.nonceDomainHash\n ) {\n throw new Error(\"Attachment blob write header does not match ciphertext\");\n }\n const verified = await verifyWriteHeader({\n authorizationMembership: \"referenced\",\n blobAuthorization: {\n authorizingContainerPaths: resolveEventContainerPaths({\n containerPathByManifestHash:\n input.authorization.containerPathByManifestHash,\n dependencyManifestHashes: header.dependencyManifestHashes,\n }).dependencyContainerPaths,\n blobKekTargets,\n principalPolicies: input.authorization.principalPolicies,\n },\n expectedAccessManifestHash: blobKekTargets.blobAccessManifestHash,\n expectedObject: {\n objectKind: \"blob\",\n objectId: input.binding.blobId,\n organizationId: input.organizationId,\n },\n expectedTargetHash: blobKekTargets.blobKeyTargetHash,\n header,\n writerPublicKey: writer.signingPublicKey,\n });\n if (!verified.ok) {\n throw verified.error;\n }\n}\n"]}
1
+ {"version":3,"file":"decrypt.js","sourceRoot":"","sources":["decrypt.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n blobContentMetadataHash,\n bytesToHex,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeBlobAccessManifestHash,\n computeBlobContentKeyTargetHash,\n computeContentRecordNonceDomainHash,\n makeVerifiedBlobKekTargets,\n verifyWriteHeader,\n} from \"@tearleads/crypto\";\nimport type { BlobKekTargetsResponse } from \"@tearleads/validators/response\";\nimport type { BlobBytes } from \"../../data/blobContracts\";\nimport { deriveBlobContentRecordKey } from \"../../data/documents/blob/shared/crypto\";\nimport { unwrapBlobContentKey } from \"../../data/documents/blob/shared/projection\";\nimport {\n parseBlobEncryptedBytes,\n readBlobKekTarget,\n readDocumentManifestIdentity,\n sortBlobTargets,\n} from \"../../data/documents/blob/shared/readers\";\nimport { assertBlobContentKeyBundleTargetHash } from \"../../data/documents/blob/shared/responses\";\nimport type {\n BlobEncryptedBytesHeader,\n DecryptDocumentAttachmentBlobInput,\n} from \"../../data/documents/blob/shared/types\";\nimport { importContentKeyMaterial } from \"../../data/documents/shared/contentRecordKeys\";\nimport { assertDocumentWriterProjectionConsistent } from \"../../data/documents/shared/projection\";\nimport {\n asWebCryptoBytes,\n readWriteHeader,\n uniqueSortedStrings,\n} from \"../../data/documents/shared/readers\";\nimport { projectionVerificationOptions } from \"../../data/documents/shared/types\";\nimport { readCanonicalRecord } from \"../../data/keyingCanonicalJson\";\nimport type { DocumentWriterProjectionAuthorization } from \"../../data/keyingProjectionVerification\";\nimport {\n readRecordString,\n requireProjectionUserKeyResolver,\n} from \"../../data/keyingProjectionVerification\";\nimport { resolveEventContainerPaths } from \"../../data/keyingProjectionVerification/documentDependencyPaths\";\n\nimport { assertAttachmentBindingVerified } from \"./attachmentBindingVerification\";\nimport { decryptBlobChunks } from \"./blobChunkDecryption\";\nimport { assertBlobWrapScopeVerified } from \"./verifiedWrapScope\";\n\nasync function assertBlobEncryptionMetadata(input: {\n readonly contentKeyBundle: DecryptDocumentAttachmentBlobInput[\"binding\"][\"contentKeyBundle\"];\n readonly encrypted: BlobEncryptedBytesHeader;\n readonly expectedBlobId: string;\n readonly organizationId: string;\n}): Promise<void> {\n const { contentKeyBundle, encrypted, expectedBlobId, organizationId } = input;\n if (\n encrypted.blobId !== expectedBlobId ||\n encrypted.contentRecordId !== expectedBlobId\n ) {\n throw new Error(\"Blob encrypted bytes blob id mismatch\");\n }\n if (\n encrypted.contentKeyEpoch !== contentKeyBundle.contentKeyEpoch ||\n contentKeyBundle.blobId !== expectedBlobId\n ) {\n throw new Error(\"Blob encrypted bytes content-key bundle mismatch\");\n }\n await assertBlobContentKeyBundleTargetHash(contentKeyBundle);\n\n const expectedNonceDomainHash = await computeContentRecordNonceDomainHash({\n version: 1,\n organizationId,\n objectKind: \"blob\",\n objectId: expectedBlobId,\n contentKeyEpoch: encrypted.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId: encrypted.contentRecordId,\n });\n if (encrypted.nonceDomainHash !== expectedNonceDomainHash) {\n throw new Error(\"Blob encrypted bytes nonce domain mismatch\");\n }\n const expectedMetadataHash = await blobContentMetadataHash({\n blobId: expectedBlobId,\n byteLength: encrypted.byteLength,\n chunkCount: encrypted.chunkCount,\n chunkSize: encrypted.chunkSize,\n contentKeyEpoch: encrypted.contentKeyEpoch,\n });\n if (encrypted.metadataHash !== expectedMetadataHash) {\n throw new Error(\"Blob encrypted bytes metadata hash mismatch\");\n }\n}\n\nexport type AttachmentBlobDecryptionInput = Omit<\n DecryptDocumentAttachmentBlobInput,\n \"encryptedBytes\"\n> & {\n encrypted: BlobEncryptedBytesHeader;\n};\n\n/** Verifies metadata and authority; the caller must still authenticate ciphertext. */\nexport async function prepareDocumentAttachmentBlobDecryption({\n binding,\n encrypted,\n expectedDocumentId,\n expectedSlotId,\n execSql,\n resolveProjectionUserKey,\n targetSecretKey,\n stillCurrent,\n warmReferencedPrincipalPolicies,\n writerProjection,\n}: AttachmentBlobDecryptionInput): Promise<{\n contentKey: Uint8Array;\n recordKey: CryptoKey;\n organizationId: string;\n}> {\n const requiredResolveProjectionUserKey = requireProjectionUserKeyResolver(\n resolveProjectionUserKey,\n \"Document attachment blob decrypt\",\n );\n const verificationOptions = projectionVerificationOptions({\n execSql,\n stillCurrent,\n warmReferencedPrincipalPolicies,\n resolveProjectionUserKey: requiredResolveProjectionUserKey,\n });\n let documentAuthorization: DocumentWriterProjectionAuthorization | undefined;\n await assertDocumentWriterProjectionConsistent(writerProjection, {\n allowStaleContentKeyBundle: true,\n execSql,\n onVerifiedAuthorization: (authorization) => {\n documentAuthorization = authorization;\n },\n ...verificationOptions,\n });\n const { documentId, organizationId } =\n readDocumentManifestIdentity(writerProjection);\n await assertBlobEncryptionMetadata({\n contentKeyBundle: binding.contentKeyBundle,\n encrypted,\n expectedBlobId: binding.blobId,\n organizationId,\n });\n await assertAttachmentBindingVerified({\n authorization: documentAuthorization,\n binding,\n expectedDocumentId,\n expectedSlotId,\n resolveProjectionUserKey: requiredResolveProjectionUserKey,\n });\n await assertBlobWriteHeaderVerified({\n authorization: documentAuthorization,\n binding,\n encrypted,\n organizationId,\n resolveProjectionUserKey: requiredResolveProjectionUserKey,\n });\n\n if (!documentAuthorization)\n throw new Error(\"Blob document authorization is unavailable\");\n assertBlobWrapScopeVerified({\n authorization: documentAuthorization,\n binding,\n documentId,\n });\n const contentKey = await unwrapBlobContentKey({\n blobId: binding.blobId,\n contentKeyBundle: binding.contentKeyBundle,\n documentId,\n encrypted,\n execSql,\n expectedBindingId: binding.bindingId,\n secretKey: targetSecretKey,\n ...verificationOptions,\n writerProjection,\n });\n const contentKeyMaterial = await importContentKeyMaterial(contentKey);\n const recordKey = await deriveBlobContentRecordKey({\n blobId: binding.blobId,\n contentKeyEpoch: encrypted.contentKeyEpoch,\n contentKeyMaterial,\n contentRecordId: encrypted.contentRecordId,\n organizationId,\n usage: \"decrypt\",\n });\n return { contentKey, recordKey, organizationId };\n}\n\nasync function decryptDocumentAttachmentBlobWithKey(\n input: DecryptDocumentAttachmentBlobInput,\n): Promise<{ bytes: BlobBytes; contentKey: Uint8Array }> {\n const encrypted = parseBlobEncryptedBytes(input.encryptedBytes);\n const context = await prepareDocumentAttachmentBlobDecryption({\n ...input,\n encrypted,\n });\n const header = readWriteHeader(\n input.binding.writeHeader,\n \"Attachment blob write header\",\n );\n const ciphertextHash = bytesToHex(\n new Uint8Array(\n await crypto.subtle.digest(\n \"SHA-256\",\n asWebCryptoBytes(input.encryptedBytes),\n ),\n ),\n );\n if (header.ciphertextHash !== ciphertextHash)\n throw new Error(\"Attachment blob write header does not match ciphertext\");\n const bytes = await decryptBlobChunks({\n ...context,\n encrypted,\n expectedBlobId: input.binding.blobId,\n });\n return { bytes, contentKey: context.contentKey };\n}\n\nexport async function decryptDocumentAttachmentBlob(\n input: DecryptDocumentAttachmentBlobInput,\n): Promise<BlobBytes> {\n return (await decryptDocumentAttachmentBlobWithKey(input)).bytes;\n}\n\nfunction assertStringSetEquals(input: {\n readonly actual: readonly string[];\n readonly expected: readonly string[];\n readonly label: string;\n}): void {\n const actual = uniqueSortedStrings(input.actual);\n const expected = uniqueSortedStrings(input.expected);\n if (\n actual.length !== expected.length ||\n actual.some((value, index) => value !== expected[index])\n ) {\n throw new Error(`${input.label} is inconsistent`);\n }\n}\n\nasync function verifiedBlobKekTargetsForBinding(input: {\n readonly binding: DecryptDocumentAttachmentBlobInput[\"binding\"];\n readonly requireBindingMembership: boolean;\n readonly projection: BlobKekTargetsResponse | undefined;\n}) {\n const { binding, projection } = input;\n if (!projection) {\n throw new Error(\"Attachment binding lacks blob KEK verification material\");\n }\n const targets = sortBlobTargets(\n projection.targets.map((target, index) =>\n readBlobKekTarget(target, `Attachment blob KEK target[${index}]`),\n ),\n );\n const targetHash = await computeBlobContentKeyTargetHash(targets);\n if (\n targetHash !== projection.blobKeyTargetHash ||\n projection.blobId !== binding.blobId\n ) {\n throw new Error(\"Attachment blob KEK target hash is inconsistent\");\n }\n if (\n input.requireBindingMembership &&\n (!projection.activeBindingIds.includes(binding.bindingId) ||\n !targets.some(\n (target) =>\n target.bindingId === binding.bindingId &&\n target.documentId === attachmentBindingDocumentId(binding),\n ))\n ) {\n throw new Error(\"Attachment binding is absent from blob KEK targets\");\n }\n assertStringSetEquals({\n actual: projection.linkedContainerManifestHashes,\n expected: targets.map((target) => target.containerManifestHash),\n label: \"Attachment blob container manifests\",\n });\n assertStringSetEquals({\n actual: projection.linkedContainerKeyEpochIds,\n expected: targets.map((target) => target.containerKeyEpochId),\n label: \"Attachment blob container KEKs\",\n });\n const accessManifestHash = await computeBlobAccessManifestHash({\n version: 1,\n blobId: projection.blobId,\n organizationId: projection.organizationId,\n activeBindingIds: projection.activeBindingIds,\n documentManifestHashes: projection.documentManifestHashes,\n linkedContainerManifestHashes: projection.linkedContainerManifestHashes,\n linkedContainerKeyEpochIds: projection.linkedContainerKeyEpochIds,\n blobKeyTargetHash: projection.blobKeyTargetHash,\n });\n if (accessManifestHash !== projection.blobAccessManifestHash) {\n throw new Error(\"Attachment blob access manifest hash is inconsistent\");\n }\n return makeVerifiedBlobKekTargets({ ...projection, targets });\n}\n\nfunction attachmentBindingDocumentId(\n binding: DecryptDocumentAttachmentBlobInput[\"binding\"],\n): string {\n if (!binding.bindingEvent) {\n throw new Error(\"Attachment binding lacks a signed event\");\n }\n return readRecordString(\n readCanonicalRecord(\n binding.bindingEvent.body,\n \"Attachment binding event body\",\n ),\n \"documentId\",\n \"Attachment binding event body\",\n );\n}\n\nasync function assertBlobWriteHeaderVerified(input: {\n readonly authorization: DocumentWriterProjectionAuthorization | undefined;\n readonly binding: DecryptDocumentAttachmentBlobInput[\"binding\"];\n readonly encrypted: BlobEncryptedBytesHeader;\n readonly organizationId: string;\n readonly resolveProjectionUserKey: ReturnType<\n typeof requireProjectionUserKeyResolver\n >;\n}): Promise<void> {\n if (!input.authorization || !input.binding.writeHeader) {\n throw new Error(\"Attachment blob lacks verified write authority\");\n }\n const header = readWriteHeader(\n input.binding.writeHeader,\n \"Attachment blob write header\",\n );\n const writer = await input.resolveProjectionUserKey(header.writerUserId);\n if (!writer) {\n throw new Error(\"Attachment blob writer identity is unavailable\");\n }\n await verifiedBlobKekTargetsForBinding({\n binding: input.binding,\n projection: input.binding.blobKekTargets,\n requireBindingMembership: true,\n });\n const blobKekTargets = await verifiedBlobKekTargetsForBinding({\n binding: input.binding,\n projection: input.binding.writeAuthorization,\n // The signed binding proves which slot exposes the blob now. Writer\n // authorization describes the older target set committed when the bytes\n // were written, so a later valid binding need not appear in that set.\n requireBindingMembership: false,\n });\n if (\n header.metadataHash !== input.encrypted.metadataHash ||\n header.contentKeyEpoch !== input.encrypted.contentKeyEpoch ||\n header.contentRecordId !== input.encrypted.contentRecordId ||\n header.nonceDomainHash !== input.encrypted.nonceDomainHash\n ) {\n throw new Error(\"Attachment blob write header does not match ciphertext\");\n }\n const verified = await verifyWriteHeader({\n authorizationMembership: \"referenced\",\n blobAuthorization: {\n authorizingContainerPaths: resolveEventContainerPaths({\n containerPathByManifestHash:\n input.authorization.containerPathByManifestHash,\n dependencyManifestHashes: header.dependencyManifestHashes,\n }).dependencyContainerPaths,\n blobKekTargets,\n principalPolicies: input.authorization.principalPolicies,\n },\n expectedAccessManifestHash: blobKekTargets.blobAccessManifestHash,\n expectedObject: {\n objectKind: \"blob\",\n objectId: input.binding.blobId,\n organizationId: input.organizationId,\n },\n expectedTargetHash: blobKekTargets.blobKeyTargetHash,\n header,\n writerPublicKey: writer.signingPublicKey,\n });\n if (!verified.ok) {\n throw verified.error;\n }\n}\n"]}
@@ -196,7 +196,7 @@ async function resolveMultipartStageStatus(input) {
196
196
  }
197
197
  function assertMultipartPlanLimits(encryption) {
198
198
  if (encryption.partCount > MAX_BLOB_CHUNK_COUNT) {
199
- throw new Error(`Multipart blob upload would require ${encryption.partCount.toLocaleString()} parts; the maximum is ${MAX_BLOB_CHUNK_COUNT.toLocaleString()} fixed 5 MiB chunks.`);
199
+ throw new Error(`Multipart blob upload would require ${encryption.partCount.toLocaleString()} parts; the maximum is ${MAX_BLOB_CHUNK_COUNT.toLocaleString()} parts.`);
200
200
  }
201
201
  for (let index = 0; index < encryption.partCount; index += 1) {
202
202
  if (encryption.getPartByteLength(index) > MAX_MULTIPART_PART_BYTES) {
@@ -1 +1 @@
1
- {"version":3,"file":"multipartUpload.js","sourceRoot":"","sources":["multipartUpload.ts"],"names":[],"mappings":"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","sourcesContent":["import { bytesToHex, MAX_BLOB_CHUNK_COUNT } from \"@tearleads/crypto\";\nimport type {\n InitiateMultipartBlobStageResponse,\n MultipartBlobStagePart,\n MultipartBlobStageStatusResponse,\n} from \"@tearleads/validators/response\";\nimport type { BlobEncryptionPlan } from \"../../data/documents/blob/shared/crypto\";\nimport type {\n BlobAttachmentApi,\n MultipartStageResolvedListener,\n MultipartUploadProgressListener,\n UploadDocumentAttachmentInput,\n} from \"../../data/documents/blob/shared/types\";\nimport {\n assertMultipartStageReplaceable,\n multipartApiFailureMessage,\n multipartStagePath,\n} from \"./multipartStageFailures\";\n\nconst DEFAULT_MULTIPART_UPLOAD_CONCURRENCY = 4;\nconst MAX_MULTIPART_UPLOAD_CONCURRENCY = 4;\nconst MAX_MULTIPART_PART_BYTES = 5 * 1024 * 1024 * 1024;\n\ninterface MultipartPartCommit {\n readonly etag: string;\n readonly partNumber: number;\n}\n\ninterface MultipartPartUploadTask {\n readonly byteLength: number;\n readonly partIndex: number;\n readonly partNumber: number;\n}\n\nfunction pathSegment(value: number | string): string {\n return encodeURIComponent(String(value));\n}\n\nfunction multipartPartPath(input: {\n readonly partNumber: number;\n readonly stageId: string;\n}): string {\n const basePath = `${multipartStagePath(input.stageId)}/parts/${pathSegment(input.partNumber)}`;\n return `${basePath}/bytes`;\n}\n\nasync function sha256BytesHex(bytes: Uint8Array<ArrayBuffer>): Promise<string> {\n return bytesToHex(\n new Uint8Array(await crypto.subtle.digest(\"SHA-256\", bytes)),\n );\n}\n\nfunction normalizeMultipartUploadConcurrency(\n value: number | undefined,\n): number {\n if (value === undefined) {\n return DEFAULT_MULTIPART_UPLOAD_CONCURRENCY;\n }\n if (\n !Number.isInteger(value) ||\n value <= 0 ||\n value > MAX_MULTIPART_UPLOAD_CONCURRENCY\n ) {\n throw new Error(\n `Multipart blob upload concurrency must be between 1 and ${MAX_MULTIPART_UPLOAD_CONCURRENCY}`,\n );\n }\n\n return value;\n}\n\nfunction isMultipartPartCommit(\n part: MultipartPartCommit | undefined,\n): part is MultipartPartCommit {\n return part !== undefined;\n}\n\nasync function uploadMultipartPartTasks(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly completeParts: (MultipartPartCommit | undefined)[];\n readonly concurrency: number;\n readonly encryption: BlobEncryptionPlan;\n readonly onPartUploaded?: (task: MultipartPartUploadTask) => void;\n readonly stageId: string;\n readonly tasks: readonly MultipartPartUploadTask[];\n readonly uploadId: string;\n}): Promise<void> {\n let failed = false;\n let firstFailure: { readonly error: unknown } | undefined;\n let nextTaskIndex = 0;\n\n async function worker(): Promise<void> {\n while (!failed) {\n const task = input.tasks[nextTaskIndex];\n nextTaskIndex += 1;\n if (!task) {\n return;\n }\n\n try {\n const uploaded = await uploadMultipartPartBytes({\n apiClient: input.apiClient,\n encryption: input.encryption,\n stageId: input.stageId,\n task,\n uploadId: input.uploadId,\n });\n if (!uploaded) {\n failed = true;\n throw new Error(\n multipartApiFailureMessage({\n apiClient: input.apiClient,\n fallback: `Multipart blob part ${task.partNumber} upload failed for stage ${input.stageId}.`,\n request: {\n method: \"PUT\",\n path: multipartPartPath({\n partNumber: task.partNumber,\n stageId: input.stageId,\n }),\n },\n }),\n );\n }\n\n input.completeParts[task.partIndex] = {\n etag: uploaded.part.etag,\n partNumber: task.partNumber,\n };\n input.onPartUploaded?.(task);\n } catch (error) {\n // Stop sibling workers from pulling new tasks and firing more uploads\n // once any part rejects. Keep this worker alive long enough for the\n // shared join below to drain uploads siblings already started.\n failed = true;\n firstFailure ??= { error };\n return;\n }\n }\n }\n\n const workerCount = Math.min(input.concurrency, input.tasks.length);\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n if (firstFailure) {\n throw firstFailure.error;\n }\n}\n\nasync function uploadMultipartPartBytes(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly encryption: BlobEncryptionPlan;\n readonly stageId: string;\n readonly task: MultipartPartUploadTask;\n readonly uploadId: string;\n}): ReturnType<BlobAttachmentApi[\"uploadMultipartBlobPartBytes\"]> {\n const encryptedPartBytes = await input.encryption.encryptPart(\n input.task.partIndex,\n );\n if (encryptedPartBytes.byteLength !== input.task.byteLength) {\n throw new Error(\n `Encrypted blob part ${input.task.partNumber} byte length mismatch: expected ${input.task.byteLength.toLocaleString()}, received ${encryptedPartBytes.byteLength.toLocaleString()}.`,\n );\n }\n\n const uploaded = await input.apiClient.uploadMultipartBlobPartBytes(\n input.stageId,\n input.task.partNumber,\n {\n byteLength: encryptedPartBytes.byteLength,\n encryptedBytes: encryptedPartBytes,\n sha256: await sha256BytesHex(encryptedPartBytes),\n uploadId: input.uploadId,\n },\n );\n if (\n uploaded &&\n (uploaded.stageId !== input.stageId ||\n uploaded.uploadId !== input.uploadId ||\n uploaded.part.partNumber !== input.task.partNumber ||\n uploaded.part.byteLength !== input.task.byteLength)\n ) {\n throw new Error(\n `Multipart blob part ${input.task.partNumber} response does not match its request.`,\n );\n }\n return uploaded;\n}\n\nfunction uploadedPartsByPartNumber(\n parts: readonly MultipartBlobStagePart[],\n): ReadonlyMap<number, MultipartBlobStagePart> {\n return new Map(parts.map((part) => [part.partNumber, part]));\n}\n\nfunction isMultipartStageStatus(\n stage: InitiateMultipartBlobStageResponse | MultipartBlobStageStatusResponse,\n): stage is MultipartBlobStageStatusResponse {\n return \"completed\" in stage && typeof stage.completed === \"boolean\";\n}\n\ninterface MultipartUploadProgressState {\n bytesTotal: number;\n bytesUploaded: number;\n partsCompleted: number;\n partsTotal: number;\n}\n\ninterface MultipartPartPlan {\n completeParts: (MultipartPartCommit | undefined)[];\n progress: MultipartUploadProgressState;\n uploadTasks: MultipartPartUploadTask[];\n}\n\n/**\n * Plans encrypted part indices without materializing their bytes, reusing any\n * already-staged parts and seeding cumulative progress for resumed uploads.\n */\nfunction planMultipartParts(input: {\n encryption: BlobEncryptionPlan;\n uploadedParts: ReadonlyMap<number, MultipartBlobStagePart>;\n}): MultipartPartPlan {\n const { encryption, uploadedParts } = input;\n const completeParts: (MultipartPartCommit | undefined)[] = new Array(\n encryption.partCount,\n );\n const uploadTasks: MultipartPartUploadTask[] = [];\n const progress: MultipartUploadProgressState = {\n bytesTotal: 0,\n bytesUploaded: 0,\n partsCompleted: 0,\n partsTotal: encryption.partCount,\n };\n\n for (let partIndex = 0; partIndex < encryption.partCount; partIndex += 1) {\n const partNumber = partIndex + 1;\n const byteLength = encryption.getPartByteLength(partIndex);\n progress.bytesTotal += byteLength;\n const uploadedPart = uploadedParts.get(partNumber);\n if (uploadedPart?.byteLength === byteLength) {\n completeParts[partIndex] = {\n etag: uploadedPart.etag,\n partNumber,\n };\n progress.partsCompleted += 1;\n progress.bytesUploaded += byteLength;\n continue;\n }\n\n uploadTasks.push({\n byteLength,\n partIndex,\n partNumber,\n });\n }\n\n return { completeParts, progress, uploadTasks };\n}\n\nasync function resolveMultipartStageStatus(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly byteLength: number;\n readonly multipart: NonNullable<UploadDocumentAttachmentInput[\"multipart\"]>;\n readonly organizationId: string;\n readonly onStageUnavailable?:\n | ((stageId: string) => Promise<void>)\n | undefined;\n readonly sha256: string;\n}): Promise<MultipartBlobStageStatusResponse> {\n const resumeStageId = input.multipart.resumeStageId;\n if (resumeStageId) {\n const resumed = await input.apiClient.getMultipartBlobStage(resumeStageId);\n if (!resumed) {\n await assertMultipartStageReplaceable({\n apiClient: input.apiClient,\n onStageUnavailable: input.onStageUnavailable,\n stageId: resumeStageId,\n });\n }\n if (resumed && resumed.stageId !== resumeStageId) {\n throw new Error(\n `Multipart blob resume response does not match stage ${resumeStageId}.`,\n );\n }\n if (resumed) {\n // A successful lookup is authoritative: a metadata mismatch indicates\n // inconsistent persisted state or a bad server response, not proof that\n // the existing stage is safe to abandon.\n if (\n resumed.organizationId !== input.organizationId ||\n resumed.byteLength !== input.byteLength ||\n resumed.sha256 !== input.sha256\n ) {\n throw new Error(\n `Multipart blob resume stage ${resumeStageId} does not match the requested organization or bytes.`,\n );\n }\n return resumed;\n }\n }\n\n const stage = await input.apiClient.initiateMultipartBlobStage({\n organizationId: input.organizationId,\n byteLength: input.byteLength,\n sha256: input.sha256,\n });\n if (!stage) {\n throw new Error(\n multipartApiFailureMessage({\n apiClient: input.apiClient,\n fallback: `Multipart blob stage initiation failed for ${input.byteLength.toLocaleString()} bytes.`,\n request: { method: \"POST\", path: \"/blobs/stages/multipart\" },\n }),\n );\n }\n if (\n stage.organizationId !== input.organizationId ||\n stage.byteLength !== input.byteLength ||\n stage.sha256 !== input.sha256\n ) {\n throw new Error(\n \"Multipart blob stage initiation response does not match its request.\",\n );\n }\n\n return isMultipartStageStatus(stage)\n ? stage\n : {\n ...stage,\n completed: false,\n };\n}\n\nfunction assertMultipartPlanLimits(encryption: BlobEncryptionPlan): void {\n if (encryption.partCount > MAX_BLOB_CHUNK_COUNT) {\n throw new Error(\n `Multipart blob upload would require ${encryption.partCount.toLocaleString()} parts; the maximum is ${MAX_BLOB_CHUNK_COUNT.toLocaleString()} fixed 5 MiB chunks.`,\n );\n }\n for (let index = 0; index < encryption.partCount; index += 1) {\n if (encryption.getPartByteLength(index) > MAX_MULTIPART_PART_BYTES) {\n throw new Error(\n `Encrypted blob part ${index + 1} exceeds the 5 GiB multipart part limit.`,\n );\n }\n }\n}\n\nexport async function stageMultipartBlobAttachment(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly encryption: BlobEncryptionPlan;\n readonly multipart: NonNullable<UploadDocumentAttachmentInput[\"multipart\"]>;\n readonly organizationId: string;\n readonly onMultipartProgress?: MultipartUploadProgressListener | undefined;\n readonly onStageResolved?: MultipartStageResolvedListener | undefined;\n readonly onStageUnavailable?:\n | ((stageId: string) => Promise<void>)\n | undefined;\n}): Promise<string | null> {\n if (input.multipart.partSize !== input.encryption.chunkSize) {\n throw new Error(\n \"Multipart part size must match the encryption chunk size exactly.\",\n );\n }\n assertMultipartPlanLimits(input.encryption);\n const status = await resolveMultipartStageStatus({\n apiClient: input.apiClient,\n byteLength: input.encryption.byteLength,\n multipart: input.multipart,\n organizationId: input.organizationId,\n onStageUnavailable: input.onStageUnavailable,\n sha256: input.encryption.sha256,\n });\n // Surface the stage id before any parts upload so the caller can persist it;\n // an upload interrupted mid-flight can then resume this same stage.\n await input.onStageResolved?.({\n partSize: input.encryption.chunkSize,\n stageId: status.stageId,\n });\n if (status.completed) {\n input.onMultipartProgress?.({\n bytesTotal: input.encryption.byteLength,\n bytesUploaded: input.encryption.byteLength,\n partsCompleted: input.encryption.partCount,\n partsTotal: input.encryption.partCount,\n });\n return status.stageId;\n }\n\n const { completeParts, progress, uploadTasks } = planMultipartParts({\n encryption: input.encryption,\n uploadedParts: uploadedPartsByPartNumber(status.uploadedParts),\n });\n\n const reportMultipartProgress = () => {\n input.onMultipartProgress?.({\n bytesTotal: progress.bytesTotal,\n bytesUploaded: progress.bytesUploaded,\n partsCompleted: progress.partsCompleted,\n partsTotal: progress.partsTotal,\n });\n };\n reportMultipartProgress();\n\n await uploadMultipartPartTasks({\n apiClient: input.apiClient,\n completeParts,\n concurrency: normalizeMultipartUploadConcurrency(\n input.multipart.uploadConcurrency,\n ),\n encryption: input.encryption,\n onPartUploaded: (task) => {\n progress.partsCompleted += 1;\n progress.bytesUploaded += task.byteLength;\n reportMultipartProgress();\n },\n stageId: status.stageId,\n tasks: uploadTasks,\n uploadId: status.uploadId,\n });\n const committedParts = completeParts.filter(isMultipartPartCommit);\n if (committedParts.length !== input.encryption.partCount) {\n throw new Error(\n `Multipart blob upload incomplete for stage ${status.stageId}: committed ${committedParts.length.toLocaleString()} of ${input.encryption.partCount.toLocaleString()} parts.`,\n );\n }\n\n const completed = await input.apiClient.completeMultipartBlobStage(\n status.stageId,\n {\n parts: committedParts,\n uploadId: status.uploadId,\n },\n );\n if (!completed) {\n throw new Error(\n multipartApiFailureMessage({\n apiClient: input.apiClient,\n fallback: `Multipart blob stage completion failed for stage ${status.stageId} with ${committedParts.length.toLocaleString()} committed parts.`,\n request: {\n method: \"POST\",\n path: `${multipartStagePath(status.stageId)}/complete`,\n },\n }),\n );\n }\n if (\n completed.organizationId !== input.organizationId ||\n completed.stageId !== status.stageId ||\n completed.byteLength !== input.encryption.byteLength ||\n completed.sha256 !== input.encryption.sha256\n ) {\n throw new Error(\n `Multipart blob stage ${status.stageId} completion response does not match its request.`,\n );\n }\n\n return completed.stageId;\n}\n"]}
1
+ {"version":3,"file":"multipartUpload.js","sourceRoot":"","sources":["multipartUpload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAarE,OAAO,EACL,+BAA+B,EAC/B,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,0BAA0B,CAAC;AAElC,MAAM,oCAAoC,GAAG,CAAC,CAAC;AAC/C,MAAM,gCAAgC,GAAG,CAAC,CAAC;AAC3C,MAAM,wBAAwB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAaxD,SAAS,WAAW,CAAC,KAAsB;IACzC,OAAO,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,iBAAiB,CAAC,KAG1B;IACC,MAAM,QAAQ,GAAG,GAAG,kBAAkB,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;IAC/F,OAAO,GAAG,QAAQ,QAAQ,CAAC;AAC7B,CAAC;AAED,KAAK,UAAU,cAAc,CAAC,KAA8B;IAC1D,OAAO,UAAU,CACf,IAAI,UAAU,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,CAC7D,CAAC;AACJ,CAAC;AAED,SAAS,mCAAmC,CAC1C,KAAyB;IAEzB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,oCAAoC,CAAC;IAC9C,CAAC;IACD,IACE,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;QACxB,KAAK,IAAI,CAAC;QACV,KAAK,GAAG,gCAAgC,EACxC,CAAC;QACD,MAAM,IAAI,KAAK,CACb,2DAA2D,gCAAgC,EAAE,CAC9F,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,qBAAqB,CAC5B,IAAqC;IAErC,OAAO,IAAI,KAAK,SAAS,CAAC;AAC5B,CAAC;AAED,KAAK,UAAU,wBAAwB,CAAC,KASvC;IACC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,YAAqD,CAAC;IAC1D,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,KAAK,UAAU,MAAM;QACnB,OAAO,CAAC,MAAM,EAAE,CAAC;YACf,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;YACxC,aAAa,IAAI,CAAC,CAAC;YACnB,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO;YACT,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,wBAAwB,CAAC;oBAC9C,SAAS,EAAE,KAAK,CAAC,SAAS;oBAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,IAAI;oBACJ,QAAQ,EAAE,KAAK,CAAC,QAAQ;iBACzB,CAAC,CAAC;gBACH,IAAI,CAAC,QAAQ,EAAE,CAAC;oBACd,MAAM,GAAG,IAAI,CAAC;oBACd,MAAM,IAAI,KAAK,CACb,0BAA0B,CAAC;wBACzB,SAAS,EAAE,KAAK,CAAC,SAAS;wBAC1B,QAAQ,EAAE,uBAAuB,IAAI,CAAC,UAAU,4BAA4B,KAAK,CAAC,OAAO,GAAG;wBAC5F,OAAO,EAAE;4BACP,MAAM,EAAE,KAAK;4BACb,IAAI,EAAE,iBAAiB,CAAC;gCACtB,UAAU,EAAE,IAAI,CAAC,UAAU;gCAC3B,OAAO,EAAE,KAAK,CAAC,OAAO;6BACvB,CAAC;yBACH;qBACF,CAAC,CACH,CAAC;gBACJ,CAAC;gBAED,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG;oBACpC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI;oBACxB,UAAU,EAAE,IAAI,CAAC,UAAU;iBAC5B,CAAC;gBACF,KAAK,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,sEAAsE;gBACtE,oEAAoE;gBACpE,+DAA+D;gBAC/D,MAAM,GAAG,IAAI,CAAC;gBACd,YAAY,KAAK,EAAE,KAAK,EAAE,CAAC;gBAC3B,OAAO;YACT,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACpE,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACvE,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,YAAY,CAAC,KAAK,CAAC;IAC3B,CAAC;AACH,CAAC;AAED,KAAK,UAAU,wBAAwB,CAAC,KAMvC;IACC,MAAM,kBAAkB,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,WAAW,CAC3D,KAAK,CAAC,IAAI,CAAC,SAAS,CACrB,CAAC;IACF,IAAI,kBAAkB,CAAC,UAAU,KAAK,KAAK,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;QAC5D,MAAM,IAAI,KAAK,CACb,uBAAuB,KAAK,CAAC,IAAI,CAAC,UAAU,mCAAmC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE,cAAc,kBAAkB,CAAC,UAAU,CAAC,cAAc,EAAE,GAAG,CACrL,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,4BAA4B,CACjE,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,IAAI,CAAC,UAAU,EACrB;QACE,UAAU,EAAE,kBAAkB,CAAC,UAAU;QACzC,cAAc,EAAE,kBAAkB;QAClC,MAAM,EAAE,MAAM,cAAc,CAAC,kBAAkB,CAAC;QAChD,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACzB,CACF,CAAC;IACF,IACE,QAAQ;QACR,CAAC,QAAQ,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO;YACjC,QAAQ,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ;YACpC,QAAQ,CAAC,IAAI,CAAC,UAAU,KAAK,KAAK,CAAC,IAAI,CAAC,UAAU;YAClD,QAAQ,CAAC,IAAI,CAAC,UAAU,KAAK,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,EACrD,CAAC;QACD,MAAM,IAAI,KAAK,CACb,uBAAuB,KAAK,CAAC,IAAI,CAAC,UAAU,uCAAuC,CACpF,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,SAAS,yBAAyB,CAChC,KAAwC;IAExC,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,sBAAsB,CAC7B,KAA4E;IAE5E,OAAO,WAAW,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC;AACtE,CAAC;AAeD;;;GAGG;AACH,SAAS,kBAAkB,CAAC,KAG3B;IACC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,KAAK,CAAC;IAC5C,MAAM,aAAa,GAAwC,IAAI,KAAK,CAClE,UAAU,CAAC,SAAS,CACrB,CAAC;IACF,MAAM,WAAW,GAA8B,EAAE,CAAC;IAClD,MAAM,QAAQ,GAAiC;QAC7C,UAAU,EAAE,CAAC;QACb,aAAa,EAAE,CAAC;QAChB,cAAc,EAAE,CAAC;QACjB,UAAU,EAAE,UAAU,CAAC,SAAS;KACjC,CAAC;IAEF,KAAK,IAAI,SAAS,GAAG,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC,SAAS,EAAE,SAAS,IAAI,CAAC,EAAE,CAAC;QACzE,MAAM,UAAU,GAAG,SAAS,GAAG,CAAC,CAAC;QACjC,MAAM,UAAU,GAAG,UAAU,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC3D,QAAQ,CAAC,UAAU,IAAI,UAAU,CAAC;QAClC,MAAM,YAAY,GAAG,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACnD,IAAI,YAAY,EAAE,UAAU,KAAK,UAAU,EAAE,CAAC;YAC5C,aAAa,CAAC,SAAS,CAAC,GAAG;gBACzB,IAAI,EAAE,YAAY,CAAC,IAAI;gBACvB,UAAU;aACX,CAAC;YACF,QAAQ,CAAC,cAAc,IAAI,CAAC,CAAC;YAC7B,QAAQ,CAAC,aAAa,IAAI,UAAU,CAAC;YACrC,SAAS;QACX,CAAC;QAED,WAAW,CAAC,IAAI,CAAC;YACf,UAAU;YACV,SAAS;YACT,UAAU;SACX,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AAClD,CAAC;AAED,KAAK,UAAU,2BAA2B,CAAC,KAS1C;IACC,MAAM,aAAa,GAAG,KAAK,CAAC,SAAS,CAAC,aAAa,CAAC;IACpD,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,qBAAqB,CAAC,aAAa,CAAC,CAAC;QAC3E,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,+BAA+B,CAAC;gBACpC,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;gBAC5C,OAAO,EAAE,aAAa;aACvB,CAAC,CAAC;QACL,CAAC;QACD,IAAI,OAAO,IAAI,OAAO,CAAC,OAAO,KAAK,aAAa,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CACb,uDAAuD,aAAa,GAAG,CACxE,CAAC;QACJ,CAAC;QACD,IAAI,OAAO,EAAE,CAAC;YACZ,sEAAsE;YACtE,wEAAwE;YACxE,yCAAyC;YACzC,IACE,OAAO,CAAC,cAAc,KAAK,KAAK,CAAC,cAAc;gBAC/C,OAAO,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU;gBACvC,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAC/B,CAAC;gBACD,MAAM,IAAI,KAAK,CACb,+BAA+B,aAAa,sDAAsD,CACnG,CAAC;YACJ,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,0BAA0B,CAAC;QAC7D,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,MAAM,EAAE,KAAK,CAAC,MAAM;KACrB,CAAC,CAAC;IACH,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,0BAA0B,CAAC;YACzB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,QAAQ,EAAE,8CAA8C,KAAK,CAAC,UAAU,CAAC,cAAc,EAAE,SAAS;YAClG,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,EAAE;SAC7D,CAAC,CACH,CAAC;IACJ,CAAC;IACD,IACE,KAAK,CAAC,cAAc,KAAK,KAAK,CAAC,cAAc;QAC7C,KAAK,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU;QACrC,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAC7B,CAAC;QACD,MAAM,IAAI,KAAK,CACb,sEAAsE,CACvE,CAAC;IACJ,CAAC;IAED,OAAO,sBAAsB,CAAC,KAAK,CAAC;QAClC,CAAC,CAAC,KAAK;QACP,CAAC,CAAC;YACE,GAAG,KAAK;YACR,SAAS,EAAE,KAAK;SACjB,CAAC;AACR,CAAC;AAED,SAAS,yBAAyB,CAAC,UAA8B;IAC/D,IAAI,UAAU,CAAC,SAAS,GAAG,oBAAoB,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CACb,uCAAuC,UAAU,CAAC,SAAS,CAAC,cAAc,EAAE,0BAA0B,oBAAoB,CAAC,cAAc,EAAE,SAAS,CACrJ,CAAC;IACJ,CAAC;IACD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,SAAS,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC7D,IAAI,UAAU,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,wBAAwB,EAAE,CAAC;YACnE,MAAM,IAAI,KAAK,CACb,uBAAuB,KAAK,GAAG,CAAC,0CAA0C,CAC3E,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,KAUlD;IACC,IAAI,KAAK,CAAC,SAAS,CAAC,QAAQ,KAAK,KAAK,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;QAC5D,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;IACJ,CAAC;IACD,yBAAyB,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,2BAA2B,CAAC;QAC/C,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,UAAU;QACvC,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;QAC5C,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,MAAM;KAChC,CAAC,CAAC;IACH,6EAA6E;IAC7E,oEAAoE;IACpE,MAAM,KAAK,CAAC,eAAe,EAAE,CAAC;QAC5B,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,SAAS;QACpC,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACrB,KAAK,CAAC,mBAAmB,EAAE,CAAC;YAC1B,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,UAAU;YACvC,aAAa,EAAE,KAAK,CAAC,UAAU,CAAC,UAAU;YAC1C,cAAc,EAAE,KAAK,CAAC,UAAU,CAAC,SAAS;YAC1C,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,SAAS;SACvC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,OAAO,CAAC;IACxB,CAAC;IAED,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,kBAAkB,CAAC;QAClE,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,aAAa,EAAE,yBAAyB,CAAC,MAAM,CAAC,aAAa,CAAC;KAC/D,CAAC,CAAC;IAEH,MAAM,uBAAuB,GAAG,GAAG,EAAE;QACnC,KAAK,CAAC,mBAAmB,EAAE,CAAC;YAC1B,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,aAAa,EAAE,QAAQ,CAAC,aAAa;YACrC,cAAc,EAAE,QAAQ,CAAC,cAAc;YACvC,UAAU,EAAE,QAAQ,CAAC,UAAU;SAChC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,uBAAuB,EAAE,CAAC;IAE1B,MAAM,wBAAwB,CAAC;QAC7B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,aAAa;QACb,WAAW,EAAE,mCAAmC,CAC9C,KAAK,CAAC,SAAS,CAAC,iBAAiB,CAClC;QACD,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,cAAc,EAAE,CAAC,IAAI,EAAE,EAAE;YACvB,QAAQ,CAAC,cAAc,IAAI,CAAC,CAAC;YAC7B,QAAQ,CAAC,aAAa,IAAI,IAAI,CAAC,UAAU,CAAC;YAC1C,uBAAuB,EAAE,CAAC;QAC5B,CAAC;QACD,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,KAAK,EAAE,WAAW;QAClB,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC,CAAC;IACH,MAAM,cAAc,GAAG,aAAa,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC;IACnE,IAAI,cAAc,CAAC,MAAM,KAAK,KAAK,CAAC,UAAU,CAAC,SAAS,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CACb,8CAA8C,MAAM,CAAC,OAAO,eAAe,cAAc,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,cAAc,EAAE,SAAS,CAC7K,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,0BAA0B,CAChE,MAAM,CAAC,OAAO,EACd;QACE,KAAK,EAAE,cAAc;QACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CACF,CAAC;IACF,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,0BAA0B,CAAC;YACzB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,QAAQ,EAAE,oDAAoD,MAAM,CAAC,OAAO,SAAS,cAAc,CAAC,MAAM,CAAC,cAAc,EAAE,mBAAmB;YAC9I,OAAO,EAAE;gBACP,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE,GAAG,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW;aACvD;SACF,CAAC,CACH,CAAC;IACJ,CAAC;IACD,IACE,SAAS,CAAC,cAAc,KAAK,KAAK,CAAC,cAAc;QACjD,SAAS,CAAC,OAAO,KAAK,MAAM,CAAC,OAAO;QACpC,SAAS,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU,CAAC,UAAU;QACpD,SAAS,CAAC,MAAM,KAAK,KAAK,CAAC,UAAU,CAAC,MAAM,EAC5C,CAAC;QACD,MAAM,IAAI,KAAK,CACb,wBAAwB,MAAM,CAAC,OAAO,kDAAkD,CACzF,CAAC;IACJ,CAAC;IAED,OAAO,SAAS,CAAC,OAAO,CAAC;AAC3B,CAAC","sourcesContent":["import { bytesToHex, MAX_BLOB_CHUNK_COUNT } from \"@tearleads/crypto\";\nimport type {\n InitiateMultipartBlobStageResponse,\n MultipartBlobStagePart,\n MultipartBlobStageStatusResponse,\n} from \"@tearleads/validators/response\";\nimport type { BlobEncryptionPlan } from \"../../data/documents/blob/shared/crypto\";\nimport type {\n BlobAttachmentApi,\n MultipartStageResolvedListener,\n MultipartUploadProgressListener,\n UploadDocumentAttachmentInput,\n} from \"../../data/documents/blob/shared/types\";\nimport {\n assertMultipartStageReplaceable,\n multipartApiFailureMessage,\n multipartStagePath,\n} from \"./multipartStageFailures\";\n\nconst DEFAULT_MULTIPART_UPLOAD_CONCURRENCY = 4;\nconst MAX_MULTIPART_UPLOAD_CONCURRENCY = 4;\nconst MAX_MULTIPART_PART_BYTES = 5 * 1024 * 1024 * 1024;\n\ninterface MultipartPartCommit {\n readonly etag: string;\n readonly partNumber: number;\n}\n\ninterface MultipartPartUploadTask {\n readonly byteLength: number;\n readonly partIndex: number;\n readonly partNumber: number;\n}\n\nfunction pathSegment(value: number | string): string {\n return encodeURIComponent(String(value));\n}\n\nfunction multipartPartPath(input: {\n readonly partNumber: number;\n readonly stageId: string;\n}): string {\n const basePath = `${multipartStagePath(input.stageId)}/parts/${pathSegment(input.partNumber)}`;\n return `${basePath}/bytes`;\n}\n\nasync function sha256BytesHex(bytes: Uint8Array<ArrayBuffer>): Promise<string> {\n return bytesToHex(\n new Uint8Array(await crypto.subtle.digest(\"SHA-256\", bytes)),\n );\n}\n\nfunction normalizeMultipartUploadConcurrency(\n value: number | undefined,\n): number {\n if (value === undefined) {\n return DEFAULT_MULTIPART_UPLOAD_CONCURRENCY;\n }\n if (\n !Number.isInteger(value) ||\n value <= 0 ||\n value > MAX_MULTIPART_UPLOAD_CONCURRENCY\n ) {\n throw new Error(\n `Multipart blob upload concurrency must be between 1 and ${MAX_MULTIPART_UPLOAD_CONCURRENCY}`,\n );\n }\n\n return value;\n}\n\nfunction isMultipartPartCommit(\n part: MultipartPartCommit | undefined,\n): part is MultipartPartCommit {\n return part !== undefined;\n}\n\nasync function uploadMultipartPartTasks(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly completeParts: (MultipartPartCommit | undefined)[];\n readonly concurrency: number;\n readonly encryption: BlobEncryptionPlan;\n readonly onPartUploaded?: (task: MultipartPartUploadTask) => void;\n readonly stageId: string;\n readonly tasks: readonly MultipartPartUploadTask[];\n readonly uploadId: string;\n}): Promise<void> {\n let failed = false;\n let firstFailure: { readonly error: unknown } | undefined;\n let nextTaskIndex = 0;\n\n async function worker(): Promise<void> {\n while (!failed) {\n const task = input.tasks[nextTaskIndex];\n nextTaskIndex += 1;\n if (!task) {\n return;\n }\n\n try {\n const uploaded = await uploadMultipartPartBytes({\n apiClient: input.apiClient,\n encryption: input.encryption,\n stageId: input.stageId,\n task,\n uploadId: input.uploadId,\n });\n if (!uploaded) {\n failed = true;\n throw new Error(\n multipartApiFailureMessage({\n apiClient: input.apiClient,\n fallback: `Multipart blob part ${task.partNumber} upload failed for stage ${input.stageId}.`,\n request: {\n method: \"PUT\",\n path: multipartPartPath({\n partNumber: task.partNumber,\n stageId: input.stageId,\n }),\n },\n }),\n );\n }\n\n input.completeParts[task.partIndex] = {\n etag: uploaded.part.etag,\n partNumber: task.partNumber,\n };\n input.onPartUploaded?.(task);\n } catch (error) {\n // Stop sibling workers from pulling new tasks and firing more uploads\n // once any part rejects. Keep this worker alive long enough for the\n // shared join below to drain uploads siblings already started.\n failed = true;\n firstFailure ??= { error };\n return;\n }\n }\n }\n\n const workerCount = Math.min(input.concurrency, input.tasks.length);\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n if (firstFailure) {\n throw firstFailure.error;\n }\n}\n\nasync function uploadMultipartPartBytes(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly encryption: BlobEncryptionPlan;\n readonly stageId: string;\n readonly task: MultipartPartUploadTask;\n readonly uploadId: string;\n}): ReturnType<BlobAttachmentApi[\"uploadMultipartBlobPartBytes\"]> {\n const encryptedPartBytes = await input.encryption.encryptPart(\n input.task.partIndex,\n );\n if (encryptedPartBytes.byteLength !== input.task.byteLength) {\n throw new Error(\n `Encrypted blob part ${input.task.partNumber} byte length mismatch: expected ${input.task.byteLength.toLocaleString()}, received ${encryptedPartBytes.byteLength.toLocaleString()}.`,\n );\n }\n\n const uploaded = await input.apiClient.uploadMultipartBlobPartBytes(\n input.stageId,\n input.task.partNumber,\n {\n byteLength: encryptedPartBytes.byteLength,\n encryptedBytes: encryptedPartBytes,\n sha256: await sha256BytesHex(encryptedPartBytes),\n uploadId: input.uploadId,\n },\n );\n if (\n uploaded &&\n (uploaded.stageId !== input.stageId ||\n uploaded.uploadId !== input.uploadId ||\n uploaded.part.partNumber !== input.task.partNumber ||\n uploaded.part.byteLength !== input.task.byteLength)\n ) {\n throw new Error(\n `Multipart blob part ${input.task.partNumber} response does not match its request.`,\n );\n }\n return uploaded;\n}\n\nfunction uploadedPartsByPartNumber(\n parts: readonly MultipartBlobStagePart[],\n): ReadonlyMap<number, MultipartBlobStagePart> {\n return new Map(parts.map((part) => [part.partNumber, part]));\n}\n\nfunction isMultipartStageStatus(\n stage: InitiateMultipartBlobStageResponse | MultipartBlobStageStatusResponse,\n): stage is MultipartBlobStageStatusResponse {\n return \"completed\" in stage && typeof stage.completed === \"boolean\";\n}\n\ninterface MultipartUploadProgressState {\n bytesTotal: number;\n bytesUploaded: number;\n partsCompleted: number;\n partsTotal: number;\n}\n\ninterface MultipartPartPlan {\n completeParts: (MultipartPartCommit | undefined)[];\n progress: MultipartUploadProgressState;\n uploadTasks: MultipartPartUploadTask[];\n}\n\n/**\n * Plans encrypted part indices without materializing their bytes, reusing any\n * already-staged parts and seeding cumulative progress for resumed uploads.\n */\nfunction planMultipartParts(input: {\n encryption: BlobEncryptionPlan;\n uploadedParts: ReadonlyMap<number, MultipartBlobStagePart>;\n}): MultipartPartPlan {\n const { encryption, uploadedParts } = input;\n const completeParts: (MultipartPartCommit | undefined)[] = new Array(\n encryption.partCount,\n );\n const uploadTasks: MultipartPartUploadTask[] = [];\n const progress: MultipartUploadProgressState = {\n bytesTotal: 0,\n bytesUploaded: 0,\n partsCompleted: 0,\n partsTotal: encryption.partCount,\n };\n\n for (let partIndex = 0; partIndex < encryption.partCount; partIndex += 1) {\n const partNumber = partIndex + 1;\n const byteLength = encryption.getPartByteLength(partIndex);\n progress.bytesTotal += byteLength;\n const uploadedPart = uploadedParts.get(partNumber);\n if (uploadedPart?.byteLength === byteLength) {\n completeParts[partIndex] = {\n etag: uploadedPart.etag,\n partNumber,\n };\n progress.partsCompleted += 1;\n progress.bytesUploaded += byteLength;\n continue;\n }\n\n uploadTasks.push({\n byteLength,\n partIndex,\n partNumber,\n });\n }\n\n return { completeParts, progress, uploadTasks };\n}\n\nasync function resolveMultipartStageStatus(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly byteLength: number;\n readonly multipart: NonNullable<UploadDocumentAttachmentInput[\"multipart\"]>;\n readonly organizationId: string;\n readonly onStageUnavailable?:\n | ((stageId: string) => Promise<void>)\n | undefined;\n readonly sha256: string;\n}): Promise<MultipartBlobStageStatusResponse> {\n const resumeStageId = input.multipart.resumeStageId;\n if (resumeStageId) {\n const resumed = await input.apiClient.getMultipartBlobStage(resumeStageId);\n if (!resumed) {\n await assertMultipartStageReplaceable({\n apiClient: input.apiClient,\n onStageUnavailable: input.onStageUnavailable,\n stageId: resumeStageId,\n });\n }\n if (resumed && resumed.stageId !== resumeStageId) {\n throw new Error(\n `Multipart blob resume response does not match stage ${resumeStageId}.`,\n );\n }\n if (resumed) {\n // A successful lookup is authoritative: a metadata mismatch indicates\n // inconsistent persisted state or a bad server response, not proof that\n // the existing stage is safe to abandon.\n if (\n resumed.organizationId !== input.organizationId ||\n resumed.byteLength !== input.byteLength ||\n resumed.sha256 !== input.sha256\n ) {\n throw new Error(\n `Multipart blob resume stage ${resumeStageId} does not match the requested organization or bytes.`,\n );\n }\n return resumed;\n }\n }\n\n const stage = await input.apiClient.initiateMultipartBlobStage({\n organizationId: input.organizationId,\n byteLength: input.byteLength,\n sha256: input.sha256,\n });\n if (!stage) {\n throw new Error(\n multipartApiFailureMessage({\n apiClient: input.apiClient,\n fallback: `Multipart blob stage initiation failed for ${input.byteLength.toLocaleString()} bytes.`,\n request: { method: \"POST\", path: \"/blobs/stages/multipart\" },\n }),\n );\n }\n if (\n stage.organizationId !== input.organizationId ||\n stage.byteLength !== input.byteLength ||\n stage.sha256 !== input.sha256\n ) {\n throw new Error(\n \"Multipart blob stage initiation response does not match its request.\",\n );\n }\n\n return isMultipartStageStatus(stage)\n ? stage\n : {\n ...stage,\n completed: false,\n };\n}\n\nfunction assertMultipartPlanLimits(encryption: BlobEncryptionPlan): void {\n if (encryption.partCount > MAX_BLOB_CHUNK_COUNT) {\n throw new Error(\n `Multipart blob upload would require ${encryption.partCount.toLocaleString()} parts; the maximum is ${MAX_BLOB_CHUNK_COUNT.toLocaleString()} parts.`,\n );\n }\n for (let index = 0; index < encryption.partCount; index += 1) {\n if (encryption.getPartByteLength(index) > MAX_MULTIPART_PART_BYTES) {\n throw new Error(\n `Encrypted blob part ${index + 1} exceeds the 5 GiB multipart part limit.`,\n );\n }\n }\n}\n\nexport async function stageMultipartBlobAttachment(input: {\n readonly apiClient: BlobAttachmentApi;\n readonly encryption: BlobEncryptionPlan;\n readonly multipart: NonNullable<UploadDocumentAttachmentInput[\"multipart\"]>;\n readonly organizationId: string;\n readonly onMultipartProgress?: MultipartUploadProgressListener | undefined;\n readonly onStageResolved?: MultipartStageResolvedListener | undefined;\n readonly onStageUnavailable?:\n | ((stageId: string) => Promise<void>)\n | undefined;\n}): Promise<string | null> {\n if (input.multipart.partSize !== input.encryption.chunkSize) {\n throw new Error(\n \"Multipart part size must match the encryption chunk size exactly.\",\n );\n }\n assertMultipartPlanLimits(input.encryption);\n const status = await resolveMultipartStageStatus({\n apiClient: input.apiClient,\n byteLength: input.encryption.byteLength,\n multipart: input.multipart,\n organizationId: input.organizationId,\n onStageUnavailable: input.onStageUnavailable,\n sha256: input.encryption.sha256,\n });\n // Surface the stage id before any parts upload so the caller can persist it;\n // an upload interrupted mid-flight can then resume this same stage.\n await input.onStageResolved?.({\n partSize: input.encryption.chunkSize,\n stageId: status.stageId,\n });\n if (status.completed) {\n input.onMultipartProgress?.({\n bytesTotal: input.encryption.byteLength,\n bytesUploaded: input.encryption.byteLength,\n partsCompleted: input.encryption.partCount,\n partsTotal: input.encryption.partCount,\n });\n return status.stageId;\n }\n\n const { completeParts, progress, uploadTasks } = planMultipartParts({\n encryption: input.encryption,\n uploadedParts: uploadedPartsByPartNumber(status.uploadedParts),\n });\n\n const reportMultipartProgress = () => {\n input.onMultipartProgress?.({\n bytesTotal: progress.bytesTotal,\n bytesUploaded: progress.bytesUploaded,\n partsCompleted: progress.partsCompleted,\n partsTotal: progress.partsTotal,\n });\n };\n reportMultipartProgress();\n\n await uploadMultipartPartTasks({\n apiClient: input.apiClient,\n completeParts,\n concurrency: normalizeMultipartUploadConcurrency(\n input.multipart.uploadConcurrency,\n ),\n encryption: input.encryption,\n onPartUploaded: (task) => {\n progress.partsCompleted += 1;\n progress.bytesUploaded += task.byteLength;\n reportMultipartProgress();\n },\n stageId: status.stageId,\n tasks: uploadTasks,\n uploadId: status.uploadId,\n });\n const committedParts = completeParts.filter(isMultipartPartCommit);\n if (committedParts.length !== input.encryption.partCount) {\n throw new Error(\n `Multipart blob upload incomplete for stage ${status.stageId}: committed ${committedParts.length.toLocaleString()} of ${input.encryption.partCount.toLocaleString()} parts.`,\n );\n }\n\n const completed = await input.apiClient.completeMultipartBlobStage(\n status.stageId,\n {\n parts: committedParts,\n uploadId: status.uploadId,\n },\n );\n if (!completed) {\n throw new Error(\n multipartApiFailureMessage({\n apiClient: input.apiClient,\n fallback: `Multipart blob stage completion failed for stage ${status.stageId} with ${committedParts.length.toLocaleString()} committed parts.`,\n request: {\n method: \"POST\",\n path: `${multipartStagePath(status.stageId)}/complete`,\n },\n }),\n );\n }\n if (\n completed.organizationId !== input.organizationId ||\n completed.stageId !== status.stageId ||\n completed.byteLength !== input.encryption.byteLength ||\n completed.sha256 !== input.encryption.sha256\n ) {\n throw new Error(\n `Multipart blob stage ${status.stageId} completion response does not match its request.`,\n );\n }\n\n return completed.stageId;\n}\n"]}