@opengeni/storage 0.2.67 → 0.2.75
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.
- package/dist/index.d.ts +3 -0
- package/dist/index.js +17 -11
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +22 -11
package/dist/index.d.ts
CHANGED
|
@@ -54,6 +54,9 @@ export type ObjectStorage = {
|
|
|
54
54
|
* the presigned-URL round-trip, which on split public/internal topologies (a
|
|
55
55
|
* public `objectStorageEndpoint` with no in-cluster route) would otherwise 401.
|
|
56
56
|
* Browser uploads keep using `createPutUrl`; this is the trusted-server twin.
|
|
57
|
+
* Repeating a PUT for the same deterministic key and verified sha256 is
|
|
58
|
+
* permitted: storage backends converge to the same bytes, while the database
|
|
59
|
+
* completion fence remains the source of truth.
|
|
57
60
|
*/
|
|
58
61
|
putObject: (args: {
|
|
59
62
|
key: string;
|
package/dist/index.js
CHANGED
|
@@ -320,8 +320,14 @@ function isGcsNotFound(error) {
|
|
|
320
320
|
}
|
|
321
321
|
function createAzureBlobObjectStorage(settings) {
|
|
322
322
|
const sharedKey = azureSharedKeyCredential(settings);
|
|
323
|
-
const
|
|
324
|
-
const
|
|
323
|
+
const requestServiceClient = settings.objectStorageAzureConnectionString ? BlobServiceClient.fromConnectionString(settings.objectStorageAzureConnectionString) : new BlobServiceClient(azureBlobServiceUrl(settings), sharedKey);
|
|
324
|
+
const presignServiceClient = settings.objectStorageAzureEndpoint ? new BlobServiceClient(azureBlobServiceUrl(settings), sharedKey) : requestServiceClient;
|
|
325
|
+
const requestContainerClient = requestServiceClient.getContainerClient(
|
|
326
|
+
settings.objectStorageBucket
|
|
327
|
+
);
|
|
328
|
+
const presignContainerClient = presignServiceClient.getContainerClient(
|
|
329
|
+
settings.objectStorageBucket
|
|
330
|
+
);
|
|
325
331
|
return {
|
|
326
332
|
bucket: settings.objectStorageBucket,
|
|
327
333
|
backend: "azure-blob",
|
|
@@ -329,7 +335,7 @@ function createAzureBlobObjectStorage(settings) {
|
|
|
329
335
|
async createPutUrl(args) {
|
|
330
336
|
const expiresIn = args.expiresInSeconds ?? UPLOAD_URL_TTL_SECONDS;
|
|
331
337
|
const expiresAt = new Date(Date.now() + expiresIn * 1e3);
|
|
332
|
-
const blobClient =
|
|
338
|
+
const blobClient = presignContainerClient.getBlockBlobClient(args.key);
|
|
333
339
|
const sas = generateBlobSASQueryParameters(
|
|
334
340
|
{
|
|
335
341
|
containerName: settings.objectStorageBucket,
|
|
@@ -353,7 +359,7 @@ function createAzureBlobObjectStorage(settings) {
|
|
|
353
359
|
async createGetUrl(args) {
|
|
354
360
|
const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;
|
|
355
361
|
const expiresAt = new Date(Date.now() + expiresIn * 1e3);
|
|
356
|
-
const blobClient =
|
|
362
|
+
const blobClient = presignContainerClient.getBlobClient(args.key);
|
|
357
363
|
const sas = generateBlobSASQueryParameters(
|
|
358
364
|
{
|
|
359
365
|
containerName: settings.objectStorageBucket,
|
|
@@ -369,7 +375,7 @@ function createAzureBlobObjectStorage(settings) {
|
|
|
369
375
|
};
|
|
370
376
|
},
|
|
371
377
|
async putObject(args) {
|
|
372
|
-
const blobClient =
|
|
378
|
+
const blobClient = requestContainerClient.getBlockBlobClient(args.key);
|
|
373
379
|
const body = Buffer.from(args.body);
|
|
374
380
|
await blobClient.upload(body, body.byteLength, {
|
|
375
381
|
blobHTTPHeaders: { blobContentType: args.contentType },
|
|
@@ -378,12 +384,12 @@ function createAzureBlobObjectStorage(settings) {
|
|
|
378
384
|
},
|
|
379
385
|
async headFile(file) {
|
|
380
386
|
return azureHeadToObjectHead(
|
|
381
|
-
await
|
|
387
|
+
await requestContainerClient.getBlobClient(file.objectKey).getProperties()
|
|
382
388
|
);
|
|
383
389
|
},
|
|
384
390
|
async fileExists(file) {
|
|
385
391
|
try {
|
|
386
|
-
await
|
|
392
|
+
await requestContainerClient.getBlobClient(file.objectKey).getProperties();
|
|
387
393
|
return true;
|
|
388
394
|
} catch (error) {
|
|
389
395
|
if (isAzureNotFound(error)) return false;
|
|
@@ -392,14 +398,14 @@ function createAzureBlobObjectStorage(settings) {
|
|
|
392
398
|
},
|
|
393
399
|
async getFileBytes(file) {
|
|
394
400
|
return await azureDownloadToBytes(
|
|
395
|
-
await
|
|
401
|
+
await requestContainerClient.getBlobClient(file.objectKey).download()
|
|
396
402
|
);
|
|
397
403
|
},
|
|
398
404
|
async getFileRange(file, range) {
|
|
399
405
|
const length = assertFileByteRange(file, range);
|
|
400
406
|
try {
|
|
401
407
|
return await azureDownloadToBoundedBytes(
|
|
402
|
-
await
|
|
408
|
+
await requestContainerClient.getBlobClient(file.objectKey).download(range.start, length),
|
|
403
409
|
file.objectKey,
|
|
404
410
|
length
|
|
405
411
|
);
|
|
@@ -410,7 +416,7 @@ function createAzureBlobObjectStorage(settings) {
|
|
|
410
416
|
},
|
|
411
417
|
async getObjectBytes(key) {
|
|
412
418
|
try {
|
|
413
|
-
const download = await
|
|
419
|
+
const download = await requestContainerClient.getBlobClient(key).download();
|
|
414
420
|
const bytes = await azureDownloadToBytes(download);
|
|
415
421
|
return { bytes, ...download.contentType ? { contentType: download.contentType } : {} };
|
|
416
422
|
} catch (error) {
|
|
@@ -421,7 +427,7 @@ function createAzureBlobObjectStorage(settings) {
|
|
|
421
427
|
}
|
|
422
428
|
},
|
|
423
429
|
async deleteObject(key) {
|
|
424
|
-
await
|
|
430
|
+
await requestContainerClient.getBlockBlobClient(key).deleteIfExists();
|
|
425
431
|
}
|
|
426
432
|
};
|
|
427
433
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Settings } from \"@opengeni/config\";\nimport { RETAINED_OUTPUT_MAX_PAGE_BYTES, type FileAsset } from \"@opengeni/contracts\";\nimport {\n BlobSASPermissions,\n BlobServiceClient,\n generateBlobSASQueryParameters,\n StorageSharedKeyCredential,\n type BlobDownloadResponseParsed,\n type BlobGetPropertiesResponse,\n} from \"@azure/storage-blob\";\nimport {\n DeleteObjectCommand,\n GetObjectCommand,\n HeadObjectCommand,\n PutObjectCommand,\n S3Client,\n type S3ClientConfig,\n} from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport {\n Storage as GcsClient,\n type GetSignedUrlConfig,\n type StorageOptions,\n} from \"@google-cloud/storage\";\n\nexport const MAX_SINGLE_PUT_SIZE_BYTES = 5_000_000_000;\nexport const UPLOAD_URL_TTL_SECONDS = 15 * 60;\nexport const DOWNLOAD_URL_TTL_SECONDS = 5 * 60;\n\nexport type ObjectHead = {\n ContentLength?: number;\n ContentType?: string;\n Metadata?: Record<string, string>;\n};\n\nexport type ObjectStorage = {\n bucket: string;\n backend: \"s3-compatible\" | \"aws-s3\" | \"azure-blob\" | \"gcs\";\n maxSinglePutSizeBytes: number;\n createPutUrl: (args: {\n key: string;\n contentType: string;\n sha256?: string | null;\n expiresInSeconds?: number;\n }) => Promise<{ url: string; requiredHeaders: Record<string, string>; expiresAt: Date }>;\n createGetUrl: (args: {\n key: string;\n expiresInSeconds?: number;\n }) => Promise<{ url: string; expiresAt: Date }>;\n headFile: (file: FileAsset) => Promise<ObjectHead>;\n /** Check provider existence without downloading object bytes. */\n fileExists: (file: FileAsset) => Promise<boolean>;\n getFileBytes: (file: FileAsset) => Promise<Uint8Array>;\n /**\n * Fetch exactly one inclusive byte range without materializing the complete\n * object. Returns null when the provider reports that the object is missing.\n */\n getFileRange: (\n file: FileAsset,\n range: { start: number; end: number },\n ) => Promise<Uint8Array | null>;\n /** Fetch an object by raw storage key (not a tracked FileAsset). Returns null on 404/missing. */\n getObjectBytes: (key: string) => Promise<{ bytes: Uint8Array; contentType?: string } | null>;\n /**\n * SERVER-SIDE authenticated direct PUT (no presign + browser fetch). For an\n * in-process upload from a trusted holder of the storage credentials (e.g. the\n * worker writing a recording), this sends the bytes straight to the storage\n * backend over the configured endpoint with the in-process SDK client — bypassing\n * the presigned-URL round-trip, which on split public/internal topologies (a\n * public `objectStorageEndpoint` with no in-cluster route) would otherwise 401.\n * Browser uploads keep using `createPutUrl`; this is the trusted-server twin.\n */\n putObject: (args: {\n key: string;\n contentType: string;\n body: Uint8Array;\n sha256?: string | null;\n }) => Promise<void>;\n /**\n * SERVER-SIDE authenticated delete of a single object by raw storage key.\n * Idempotent: a missing key is a no-op (S3/GCS/Azure delete-by-key does not\n * 404 on absent objects, or the 404 is swallowed). Used by the Workbench v2\n * capture GC (keep the latest 10) to reap after-image blobs that no\n * surviving revision references. Best-effort at the call site — a failed\n * delete leaves an orphan blob, never corrupts a live capture.\n */\n deleteObject: (key: string) => Promise<void>;\n};\n\nexport function createObjectStorage(settings: Settings): ObjectStorage | null {\n if (settings.objectStorageBackend === \"azure-blob\") {\n return createAzureBlobObjectStorage(settings);\n }\n if (settings.objectStorageBackend === \"gcs\") {\n return createGcsObjectStorage(settings);\n }\n return createS3CompatibleObjectStorage(settings);\n}\n\nfunction createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | null {\n if (\n settings.objectStorageBackend === \"s3-compatible\" &&\n (!settings.objectStorageEndpoint ||\n !settings.objectStorageAccessKeyId ||\n !settings.objectStorageSecretAccessKey)\n ) {\n return null;\n }\n const sharedClientConfig: S3ClientConfig = {\n region: settings.objectStorageRegion,\n forcePathStyle: settings.objectStorageForcePathStyle,\n requestChecksumCalculation: \"WHEN_REQUIRED\",\n ...(settings.objectStorageAccessKeyId && settings.objectStorageSecretAccessKey\n ? {\n credentials: {\n accessKeyId: settings.objectStorageAccessKeyId,\n secretAccessKey: settings.objectStorageSecretAccessKey,\n },\n }\n : {}),\n };\n const presignClient = new S3Client({\n ...sharedClientConfig,\n ...(settings.objectStorageEndpoint ? { endpoint: settings.objectStorageEndpoint } : {}),\n });\n const requestClient = settings.objectStorageInternalEndpoint\n ? new S3Client({\n ...sharedClientConfig,\n endpoint: settings.objectStorageInternalEndpoint,\n })\n : presignClient;\n return {\n bucket: settings.objectStorageBucket,\n backend: settings.objectStorageBackend === \"aws-s3\" ? \"aws-s3\" : \"s3-compatible\",\n maxSinglePutSizeBytes: MAX_SINGLE_PUT_SIZE_BYTES,\n async createPutUrl(args) {\n const expiresIn = args.expiresInSeconds ?? UPLOAD_URL_TTL_SECONDS;\n const requiredHeaders: Record<string, string> = {\n \"content-type\": args.contentType,\n };\n const command = new PutObjectCommand({\n Bucket: settings.objectStorageBucket,\n Key: args.key,\n ContentType: args.contentType,\n Metadata: args.sha256 ? { sha256: args.sha256 } : undefined,\n });\n return {\n url: await getSignedUrl(presignClient, command, { expiresIn }),\n requiredHeaders,\n expiresAt: new Date(Date.now() + expiresIn * 1000),\n };\n },\n async createGetUrl(args) {\n const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;\n return {\n url: await getSignedUrl(\n presignClient,\n new GetObjectCommand({\n Bucket: settings.objectStorageBucket,\n Key: args.key,\n }),\n { expiresIn },\n ),\n expiresAt: new Date(Date.now() + expiresIn * 1000),\n };\n },\n async putObject(args) {\n // Authenticated in-process PUT against the configured (in-cluster) endpoint.\n // A presigned URL buys nothing here — the worker already holds the creds — and\n // on a split public/internal endpoint topology the presigned URL points at the\n // PUBLIC host (no MinIO route → 401). This sends bytes straight to the backend.\n await requestClient.send(\n new PutObjectCommand({\n Bucket: settings.objectStorageBucket,\n Key: args.key,\n ContentType: args.contentType,\n Body: args.body,\n Metadata: args.sha256 ? { sha256: args.sha256 } : undefined,\n }),\n );\n },\n async headFile(file) {\n const head = await requestClient.send(\n new HeadObjectCommand({\n Bucket: file.bucket,\n Key: file.objectKey,\n }),\n );\n return objectHead({\n contentLength: head.ContentLength,\n contentType: head.ContentType,\n metadata: head.Metadata,\n });\n },\n async fileExists(file) {\n try {\n await requestClient.send(\n new HeadObjectCommand({\n Bucket: file.bucket,\n Key: file.objectKey,\n }),\n );\n return true;\n } catch (error) {\n if (isS3NotFound(error)) return false;\n throw error;\n }\n },\n async getFileBytes(file) {\n const result = await requestClient.send(\n new GetObjectCommand({\n Bucket: file.bucket,\n Key: file.objectKey,\n }),\n );\n return await s3BodyToBytes(result.Body, file.objectKey);\n },\n async getFileRange(file, range) {\n const length = assertFileByteRange(file, range);\n try {\n const result = await requestClient.send(\n new GetObjectCommand({\n Bucket: file.bucket,\n Key: file.objectKey,\n Range: `bytes=${range.start}-${range.end}`,\n }),\n );\n return await s3BodyToBoundedBytes(result.Body, file.objectKey, length);\n } catch (error) {\n if (isS3NotFound(error)) return null;\n throw error;\n }\n },\n async getObjectBytes(key) {\n try {\n const result = await requestClient.send(\n new GetObjectCommand({\n Bucket: settings.objectStorageBucket,\n Key: key,\n }),\n );\n const bytes = await s3BodyToBytes(result.Body, key);\n return { bytes, ...(result.ContentType ? { contentType: result.ContentType } : {}) };\n } catch (error) {\n if (isS3NotFound(error)) {\n return null;\n }\n throw error;\n }\n },\n async deleteObject(key) {\n // S3 DeleteObject is idempotent — deleting an absent key returns 204.\n await requestClient.send(\n new DeleteObjectCommand({\n Bucket: settings.objectStorageBucket,\n Key: key,\n }),\n );\n },\n };\n}\n\nasync function s3BodyToBytes(body: unknown, objectKey: string): Promise<Uint8Array> {\n if (!body) {\n throw new Error(`Object body is empty: ${objectKey}`);\n }\n const withTransform = body as { transformToByteArray?: () => Promise<Uint8Array> };\n if (typeof withTransform.transformToByteArray === \"function\") {\n return await withTransform.transformToByteArray();\n }\n const chunks: Uint8Array[] = [];\n for await (const chunk of body as AsyncIterable<Uint8Array | Buffer | string>) {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n }\n return Buffer.concat(chunks);\n}\n\nasync function s3BodyToBoundedBytes(\n body: unknown,\n objectKey: string,\n expectedBytes: number,\n): Promise<Uint8Array> {\n if (!body) {\n throw new Error(`Object body is empty: ${objectKey}`);\n }\n const chunks: Uint8Array[] = [];\n let bytes = 0;\n for await (const chunk of body as AsyncIterable<Uint8Array | Buffer | string>) {\n const normalized = typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n bytes += normalized.byteLength;\n if (bytes > expectedBytes) {\n throw new Error(`Object range exceeded requested length: ${objectKey}`);\n }\n chunks.push(normalized);\n }\n if (bytes !== expectedBytes) {\n throw new Error(\n `Object range length mismatch for ${objectKey}: expected ${expectedBytes}, received ${bytes}`,\n );\n }\n return Buffer.concat(chunks, bytes);\n}\n\nfunction isS3NotFound(error: unknown): boolean {\n if (!error || typeof error !== \"object\") {\n return false;\n }\n const name = \"name\" in error ? (error as { name?: unknown }).name : undefined;\n if (name === \"NoSuchKey\" || name === \"NotFound\") {\n return true;\n }\n const metadata =\n \"$metadata\" in error\n ? (error as { $metadata?: { httpStatusCode?: number } }).$metadata\n : undefined;\n return metadata?.httpStatusCode === 404;\n}\n\nfunction createGcsObjectStorage(settings: Settings): ObjectStorage {\n const client = new GcsClient(gcsClientOptions(settings));\n const bucket = client.bucket(settings.objectStorageBucket);\n return {\n bucket: settings.objectStorageBucket,\n backend: \"gcs\",\n maxSinglePutSizeBytes: MAX_SINGLE_PUT_SIZE_BYTES,\n async createPutUrl(args) {\n const expiresIn = args.expiresInSeconds ?? UPLOAD_URL_TTL_SECONDS;\n const expiresAt = new Date(Date.now() + expiresIn * 1000);\n const config: GetSignedUrlConfig = {\n version: \"v4\",\n action: \"write\",\n expires: expiresAt,\n contentType: args.contentType,\n };\n if (args.sha256) {\n config.extensionHeaders = { \"x-goog-meta-sha256\": args.sha256 };\n }\n const [url] = await bucket.file(args.key).getSignedUrl(config);\n return {\n url,\n requiredHeaders: {\n \"content-type\": args.contentType,\n ...(args.sha256 ? { \"x-goog-meta-sha256\": args.sha256 } : {}),\n },\n expiresAt,\n };\n },\n async createGetUrl(args) {\n const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;\n const expiresAt = new Date(Date.now() + expiresIn * 1000);\n const [url] = await bucket.file(args.key).getSignedUrl({\n version: \"v4\",\n action: \"read\",\n expires: expiresAt,\n });\n return { url, expiresAt };\n },\n async putObject(args) {\n // Authenticated in-process PUT via the GCS SDK (the server holds the creds).\n await bucket.file(args.key).save(Buffer.from(args.body), {\n contentType: args.contentType,\n ...(args.sha256 ? { metadata: { metadata: { sha256: args.sha256 } } } : {}),\n });\n },\n async headFile(file) {\n const [metadata] = await bucket.file(file.objectKey).getMetadata();\n return objectHead({\n contentLength: parseContentLength(metadata.size),\n contentType: metadata.contentType,\n metadata: stringMetadata(metadata.metadata),\n });\n },\n async fileExists(file) {\n try {\n await bucket.file(file.objectKey).getMetadata();\n return true;\n } catch (error) {\n if (isGcsNotFound(error)) return false;\n throw error;\n }\n },\n async getFileBytes(file) {\n const [bytes] = await bucket.file(file.objectKey).download();\n return bytes;\n },\n async getFileRange(file, range) {\n const length = assertFileByteRange(file, range);\n try {\n const [bytes] = await bucket\n .file(file.objectKey)\n .download({ start: range.start, end: range.end });\n return exactRangeBytes(bytes, file.objectKey, length);\n } catch (error) {\n if (isGcsNotFound(error)) return null;\n throw error;\n }\n },\n async getObjectBytes(key) {\n try {\n const [bytes] = await bucket.file(key).download();\n return { bytes };\n } catch (error) {\n if (isGcsNotFound(error)) {\n return null;\n }\n throw error;\n }\n },\n async deleteObject(key) {\n // ignoreNotFound keeps the delete idempotent (a missing blob is a no-op).\n await bucket.file(key).delete({ ignoreNotFound: true });\n },\n };\n}\n\nfunction isGcsNotFound(error: unknown): boolean {\n return Boolean(error) && typeof error === \"object\" && (error as { code?: unknown }).code === 404;\n}\n\nfunction createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null {\n const sharedKey = azureSharedKeyCredential(settings);\n const serviceClient = settings.objectStorageAzureConnectionString\n ? BlobServiceClient.fromConnectionString(settings.objectStorageAzureConnectionString)\n : new BlobServiceClient(azureBlobServiceUrl(settings), sharedKey);\n const containerClient = serviceClient.getContainerClient(settings.objectStorageBucket);\n\n return {\n bucket: settings.objectStorageBucket,\n backend: \"azure-blob\",\n maxSinglePutSizeBytes: MAX_SINGLE_PUT_SIZE_BYTES,\n async createPutUrl(args) {\n const expiresIn = args.expiresInSeconds ?? UPLOAD_URL_TTL_SECONDS;\n const expiresAt = new Date(Date.now() + expiresIn * 1000);\n const blobClient = containerClient.getBlockBlobClient(args.key);\n const sas = generateBlobSASQueryParameters(\n {\n containerName: settings.objectStorageBucket,\n blobName: args.key,\n permissions: BlobSASPermissions.parse(\"cw\"),\n expiresOn: expiresAt,\n contentType: args.contentType,\n },\n sharedKey,\n ).toString();\n return {\n url: `${blobClient.url}?${sas}`,\n requiredHeaders: {\n \"content-type\": args.contentType,\n \"x-ms-blob-type\": \"BlockBlob\",\n ...(args.sha256 ? { \"x-ms-meta-sha256\": args.sha256 } : {}),\n },\n expiresAt,\n };\n },\n async createGetUrl(args) {\n const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;\n const expiresAt = new Date(Date.now() + expiresIn * 1000);\n const blobClient = containerClient.getBlobClient(args.key);\n const sas = generateBlobSASQueryParameters(\n {\n containerName: settings.objectStorageBucket,\n blobName: args.key,\n permissions: BlobSASPermissions.parse(\"r\"),\n expiresOn: expiresAt,\n },\n sharedKey,\n ).toString();\n return {\n url: `${blobClient.url}?${sas}`,\n expiresAt,\n };\n },\n async putObject(args) {\n // Authenticated in-process upload via the shared-key Azure client (no SAS).\n const blobClient = containerClient.getBlockBlobClient(args.key);\n const body = Buffer.from(args.body);\n await blobClient.upload(body, body.byteLength, {\n blobHTTPHeaders: { blobContentType: args.contentType },\n ...(args.sha256 ? { metadata: { sha256: args.sha256 } } : {}),\n });\n },\n async headFile(file) {\n return azureHeadToObjectHead(\n await containerClient.getBlobClient(file.objectKey).getProperties(),\n );\n },\n async fileExists(file) {\n try {\n await containerClient.getBlobClient(file.objectKey).getProperties();\n return true;\n } catch (error) {\n if (isAzureNotFound(error)) return false;\n throw error;\n }\n },\n async getFileBytes(file) {\n return await azureDownloadToBytes(\n await containerClient.getBlobClient(file.objectKey).download(),\n );\n },\n async getFileRange(file, range) {\n const length = assertFileByteRange(file, range);\n try {\n return await azureDownloadToBoundedBytes(\n await containerClient.getBlobClient(file.objectKey).download(range.start, length),\n file.objectKey,\n length,\n );\n } catch (error) {\n if (isAzureNotFound(error)) return null;\n throw error;\n }\n },\n async getObjectBytes(key) {\n try {\n const download = await containerClient.getBlobClient(key).download();\n const bytes = await azureDownloadToBytes(download);\n return { bytes, ...(download.contentType ? { contentType: download.contentType } : {}) };\n } catch (error) {\n if (isAzureNotFound(error)) {\n return null;\n }\n throw error;\n }\n },\n async deleteObject(key) {\n // deleteIfExists keeps the delete idempotent (a missing blob is a no-op).\n await containerClient.getBlockBlobClient(key).deleteIfExists();\n },\n };\n}\n\nfunction isAzureNotFound(error: unknown): boolean {\n return (\n Boolean(error) &&\n typeof error === \"object\" &&\n (error as { statusCode?: unknown }).statusCode === 404\n );\n}\n\nfunction azureSharedKeyCredential(settings: Settings): StorageSharedKeyCredential {\n if (settings.objectStorageAzureConnectionString) {\n const parsed = parseConnectionString(settings.objectStorageAzureConnectionString);\n if (parsed.AccountName && parsed.AccountKey) {\n return new StorageSharedKeyCredential(parsed.AccountName, parsed.AccountKey);\n }\n throw new Error(\n \"Azure Blob connection string must include AccountName and AccountKey to create presigned URLs\",\n );\n }\n if (!settings.objectStorageAzureAccountName || !settings.objectStorageAzureAccountKey) {\n throw new Error(\"Azure Blob storage requires account name and account key\");\n }\n return new StorageSharedKeyCredential(\n settings.objectStorageAzureAccountName,\n settings.objectStorageAzureAccountKey,\n );\n}\n\nfunction azureBlobServiceUrl(settings: Settings): string {\n if (settings.objectStorageAzureEndpoint) {\n return settings.objectStorageAzureEndpoint.replace(/\\/+$/, \"\");\n }\n if (!settings.objectStorageAzureAccountName) {\n throw new Error(\"Azure Blob storage requires account name\");\n }\n return `https://${settings.objectStorageAzureAccountName}.blob.core.windows.net`;\n}\n\nfunction parseConnectionString(value: string): Record<string, string> {\n return Object.fromEntries(\n value\n .split(\";\")\n .map((part) => part.trim())\n .filter(Boolean)\n .map((part) => {\n const index = part.indexOf(\"=\");\n return index === -1 ? [part, \"\"] : [part.slice(0, index), part.slice(index + 1)];\n }),\n );\n}\n\nfunction gcsClientOptions(settings: Settings): StorageOptions {\n const options: StorageOptions = {\n ...(settings.objectStorageGcsProjectId\n ? { projectId: settings.objectStorageGcsProjectId }\n : {}),\n ...(settings.objectStorageGcsKeyFilename\n ? { keyFilename: settings.objectStorageGcsKeyFilename }\n : {}),\n ...(settings.objectStorageGcsApiEndpoint\n ? { apiEndpoint: settings.objectStorageGcsApiEndpoint }\n : {}),\n };\n if (settings.objectStorageGcsCredentialsJson) {\n options.credentials = parseGcsCredentials(settings.objectStorageGcsCredentialsJson);\n }\n return options;\n}\n\nfunction parseGcsCredentials(raw: string): Record<string, string> {\n const parsed = JSON.parse(raw);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\"GCS credentials JSON must be an object\");\n }\n return parsed as Record<string, string>;\n}\n\nfunction parseContentLength(value: string | number | undefined): number | undefined {\n if (typeof value === \"number\") {\n return value;\n }\n if (typeof value !== \"string\" || value.length === 0) {\n return undefined;\n }\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction stringMetadata(\n value: Record<string, string | number | boolean | null> | undefined,\n): Record<string, string> | undefined {\n if (!value) {\n return undefined;\n }\n return Object.fromEntries(\n Object.entries(value).filter(\n (entry): entry is [string, string] => typeof entry[1] === \"string\",\n ),\n );\n}\n\nfunction azureHeadToObjectHead(head: BlobGetPropertiesResponse): ObjectHead {\n return objectHead({\n contentLength: head.contentLength,\n contentType: head.contentType,\n metadata: head.metadata,\n });\n}\n\nasync function azureDownloadToBytes(download: BlobDownloadResponseParsed): Promise<Uint8Array> {\n if (!download.readableStreamBody) {\n throw new Error(\"Azure Blob download response did not include a readable body\");\n }\n const chunks: Uint8Array[] = [];\n for await (const chunk of download.readableStreamBody as AsyncIterable<\n Uint8Array | Buffer | string\n >) {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n }\n return Buffer.concat(chunks);\n}\n\nasync function azureDownloadToBoundedBytes(\n download: BlobDownloadResponseParsed,\n objectKey: string,\n expectedBytes: number,\n): Promise<Uint8Array> {\n if (!download.readableStreamBody) {\n throw new Error(\"Azure Blob download response did not include a readable body\");\n }\n const chunks: Uint8Array[] = [];\n let bytes = 0;\n for await (const chunk of download.readableStreamBody as AsyncIterable<\n Uint8Array | Buffer | string\n >) {\n const normalized = typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n bytes += normalized.byteLength;\n if (bytes > expectedBytes) {\n throw new Error(`Object range exceeded requested length: ${objectKey}`);\n }\n chunks.push(normalized);\n }\n if (bytes !== expectedBytes) {\n throw new Error(\n `Object range length mismatch for ${objectKey}: expected ${expectedBytes}, received ${bytes}`,\n );\n }\n return Buffer.concat(chunks, bytes);\n}\n\nfunction assertFileByteRange(\n file: Pick<FileAsset, \"sizeBytes\" | \"objectKey\">,\n range: { start: number; end: number },\n): number {\n if (\n !Number.isSafeInteger(range.start) ||\n !Number.isSafeInteger(range.end) ||\n range.start < 0 ||\n range.end < range.start ||\n range.end >= file.sizeBytes\n ) {\n throw new RangeError(`Invalid object byte range for ${file.objectKey}`);\n }\n const length = range.end - range.start + 1;\n if (length > RETAINED_OUTPUT_MAX_PAGE_BYTES) {\n throw new RangeError(\n `Object byte range exceeds ${RETAINED_OUTPUT_MAX_PAGE_BYTES} bytes for ${file.objectKey}`,\n );\n }\n return length;\n}\n\nfunction exactRangeBytes(bytes: Uint8Array, objectKey: string, expectedBytes: number): Uint8Array {\n if (bytes.byteLength !== expectedBytes) {\n throw new Error(\n `Object range length mismatch for ${objectKey}: expected ${expectedBytes}, received ${bytes.byteLength}`,\n );\n }\n return bytes;\n}\n\nfunction objectHead(input: {\n contentLength?: number | undefined;\n contentType?: string | undefined;\n metadata?: Record<string, string> | undefined;\n}): ObjectHead {\n return {\n ...(input.contentLength !== undefined ? { ContentLength: input.contentLength } : {}),\n ...(input.contentType !== undefined ? { ContentType: input.contentType } : {}),\n ...(input.metadata !== undefined ? { Metadata: input.metadata } : {}),\n };\n}\n\nexport function bytesToDataUrl(bytes: Uint8Array, contentType: string): string {\n return `data:${contentType};base64,${Buffer.from(bytes).toString(\"base64\")}`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Settings } from \"@opengeni/config\";\nimport { RETAINED_OUTPUT_MAX_PAGE_BYTES, type FileAsset } from \"@opengeni/contracts\";\nimport {\n BlobSASPermissions,\n BlobServiceClient,\n generateBlobSASQueryParameters,\n StorageSharedKeyCredential,\n type BlobDownloadResponseParsed,\n type BlobGetPropertiesResponse,\n} from \"@azure/storage-blob\";\nimport {\n DeleteObjectCommand,\n GetObjectCommand,\n HeadObjectCommand,\n PutObjectCommand,\n S3Client,\n type S3ClientConfig,\n} from \"@aws-sdk/client-s3\";\nimport { getSignedUrl } from \"@aws-sdk/s3-request-presigner\";\nimport {\n Storage as GcsClient,\n type GetSignedUrlConfig,\n type StorageOptions,\n} from \"@google-cloud/storage\";\n\nexport const MAX_SINGLE_PUT_SIZE_BYTES = 5_000_000_000;\nexport const UPLOAD_URL_TTL_SECONDS = 15 * 60;\nexport const DOWNLOAD_URL_TTL_SECONDS = 5 * 60;\n\nexport type ObjectHead = {\n ContentLength?: number;\n ContentType?: string;\n Metadata?: Record<string, string>;\n};\n\nexport type ObjectStorage = {\n bucket: string;\n backend: \"s3-compatible\" | \"aws-s3\" | \"azure-blob\" | \"gcs\";\n maxSinglePutSizeBytes: number;\n createPutUrl: (args: {\n key: string;\n contentType: string;\n sha256?: string | null;\n expiresInSeconds?: number;\n }) => Promise<{ url: string; requiredHeaders: Record<string, string>; expiresAt: Date }>;\n createGetUrl: (args: {\n key: string;\n expiresInSeconds?: number;\n }) => Promise<{ url: string; expiresAt: Date }>;\n headFile: (file: FileAsset) => Promise<ObjectHead>;\n /** Check provider existence without downloading object bytes. */\n fileExists: (file: FileAsset) => Promise<boolean>;\n getFileBytes: (file: FileAsset) => Promise<Uint8Array>;\n /**\n * Fetch exactly one inclusive byte range without materializing the complete\n * object. Returns null when the provider reports that the object is missing.\n */\n getFileRange: (\n file: FileAsset,\n range: { start: number; end: number },\n ) => Promise<Uint8Array | null>;\n /** Fetch an object by raw storage key (not a tracked FileAsset). Returns null on 404/missing. */\n getObjectBytes: (key: string) => Promise<{ bytes: Uint8Array; contentType?: string } | null>;\n /**\n * SERVER-SIDE authenticated direct PUT (no presign + browser fetch). For an\n * in-process upload from a trusted holder of the storage credentials (e.g. the\n * worker writing a recording), this sends the bytes straight to the storage\n * backend over the configured endpoint with the in-process SDK client — bypassing\n * the presigned-URL round-trip, which on split public/internal topologies (a\n * public `objectStorageEndpoint` with no in-cluster route) would otherwise 401.\n * Browser uploads keep using `createPutUrl`; this is the trusted-server twin.\n * Repeating a PUT for the same deterministic key and verified sha256 is\n * permitted: storage backends converge to the same bytes, while the database\n * completion fence remains the source of truth.\n */\n putObject: (args: {\n key: string;\n contentType: string;\n body: Uint8Array;\n sha256?: string | null;\n }) => Promise<void>;\n /**\n * SERVER-SIDE authenticated delete of a single object by raw storage key.\n * Idempotent: a missing key is a no-op (S3/GCS/Azure delete-by-key does not\n * 404 on absent objects, or the 404 is swallowed). Used by the Workbench v2\n * capture GC (keep the latest 10) to reap after-image blobs that no\n * surviving revision references. Best-effort at the call site — a failed\n * delete leaves an orphan blob, never corrupts a live capture.\n */\n deleteObject: (key: string) => Promise<void>;\n};\n\nexport function createObjectStorage(settings: Settings): ObjectStorage | null {\n if (settings.objectStorageBackend === \"azure-blob\") {\n return createAzureBlobObjectStorage(settings);\n }\n if (settings.objectStorageBackend === \"gcs\") {\n return createGcsObjectStorage(settings);\n }\n return createS3CompatibleObjectStorage(settings);\n}\n\nfunction createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | null {\n if (\n settings.objectStorageBackend === \"s3-compatible\" &&\n (!settings.objectStorageEndpoint ||\n !settings.objectStorageAccessKeyId ||\n !settings.objectStorageSecretAccessKey)\n ) {\n return null;\n }\n const sharedClientConfig: S3ClientConfig = {\n region: settings.objectStorageRegion,\n forcePathStyle: settings.objectStorageForcePathStyle,\n requestChecksumCalculation: \"WHEN_REQUIRED\",\n ...(settings.objectStorageAccessKeyId && settings.objectStorageSecretAccessKey\n ? {\n credentials: {\n accessKeyId: settings.objectStorageAccessKeyId,\n secretAccessKey: settings.objectStorageSecretAccessKey,\n },\n }\n : {}),\n };\n const presignClient = new S3Client({\n ...sharedClientConfig,\n ...(settings.objectStorageEndpoint ? { endpoint: settings.objectStorageEndpoint } : {}),\n });\n const requestClient = settings.objectStorageInternalEndpoint\n ? new S3Client({\n ...sharedClientConfig,\n endpoint: settings.objectStorageInternalEndpoint,\n })\n : presignClient;\n return {\n bucket: settings.objectStorageBucket,\n backend: settings.objectStorageBackend === \"aws-s3\" ? \"aws-s3\" : \"s3-compatible\",\n maxSinglePutSizeBytes: MAX_SINGLE_PUT_SIZE_BYTES,\n async createPutUrl(args) {\n const expiresIn = args.expiresInSeconds ?? UPLOAD_URL_TTL_SECONDS;\n const requiredHeaders: Record<string, string> = {\n \"content-type\": args.contentType,\n };\n const command = new PutObjectCommand({\n Bucket: settings.objectStorageBucket,\n Key: args.key,\n ContentType: args.contentType,\n Metadata: args.sha256 ? { sha256: args.sha256 } : undefined,\n });\n return {\n url: await getSignedUrl(presignClient, command, { expiresIn }),\n requiredHeaders,\n expiresAt: new Date(Date.now() + expiresIn * 1000),\n };\n },\n async createGetUrl(args) {\n const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;\n return {\n url: await getSignedUrl(\n presignClient,\n new GetObjectCommand({\n Bucket: settings.objectStorageBucket,\n Key: args.key,\n }),\n { expiresIn },\n ),\n expiresAt: new Date(Date.now() + expiresIn * 1000),\n };\n },\n async putObject(args) {\n // Authenticated in-process PUT against the configured (in-cluster) endpoint.\n // A presigned URL buys nothing here — the worker already holds the creds — and\n // on a split public/internal endpoint topology the presigned URL points at the\n // PUBLIC host (no MinIO route → 401). This sends bytes straight to the backend.\n await requestClient.send(\n new PutObjectCommand({\n Bucket: settings.objectStorageBucket,\n Key: args.key,\n ContentType: args.contentType,\n Body: args.body,\n Metadata: args.sha256 ? { sha256: args.sha256 } : undefined,\n }),\n );\n },\n async headFile(file) {\n const head = await requestClient.send(\n new HeadObjectCommand({\n Bucket: file.bucket,\n Key: file.objectKey,\n }),\n );\n return objectHead({\n contentLength: head.ContentLength,\n contentType: head.ContentType,\n metadata: head.Metadata,\n });\n },\n async fileExists(file) {\n try {\n await requestClient.send(\n new HeadObjectCommand({\n Bucket: file.bucket,\n Key: file.objectKey,\n }),\n );\n return true;\n } catch (error) {\n if (isS3NotFound(error)) return false;\n throw error;\n }\n },\n async getFileBytes(file) {\n const result = await requestClient.send(\n new GetObjectCommand({\n Bucket: file.bucket,\n Key: file.objectKey,\n }),\n );\n return await s3BodyToBytes(result.Body, file.objectKey);\n },\n async getFileRange(file, range) {\n const length = assertFileByteRange(file, range);\n try {\n const result = await requestClient.send(\n new GetObjectCommand({\n Bucket: file.bucket,\n Key: file.objectKey,\n Range: `bytes=${range.start}-${range.end}`,\n }),\n );\n return await s3BodyToBoundedBytes(result.Body, file.objectKey, length);\n } catch (error) {\n if (isS3NotFound(error)) return null;\n throw error;\n }\n },\n async getObjectBytes(key) {\n try {\n const result = await requestClient.send(\n new GetObjectCommand({\n Bucket: settings.objectStorageBucket,\n Key: key,\n }),\n );\n const bytes = await s3BodyToBytes(result.Body, key);\n return { bytes, ...(result.ContentType ? { contentType: result.ContentType } : {}) };\n } catch (error) {\n if (isS3NotFound(error)) {\n return null;\n }\n throw error;\n }\n },\n async deleteObject(key) {\n // S3 DeleteObject is idempotent — deleting an absent key returns 204.\n await requestClient.send(\n new DeleteObjectCommand({\n Bucket: settings.objectStorageBucket,\n Key: key,\n }),\n );\n },\n };\n}\n\nasync function s3BodyToBytes(body: unknown, objectKey: string): Promise<Uint8Array> {\n if (!body) {\n throw new Error(`Object body is empty: ${objectKey}`);\n }\n const withTransform = body as { transformToByteArray?: () => Promise<Uint8Array> };\n if (typeof withTransform.transformToByteArray === \"function\") {\n return await withTransform.transformToByteArray();\n }\n const chunks: Uint8Array[] = [];\n for await (const chunk of body as AsyncIterable<Uint8Array | Buffer | string>) {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n }\n return Buffer.concat(chunks);\n}\n\nasync function s3BodyToBoundedBytes(\n body: unknown,\n objectKey: string,\n expectedBytes: number,\n): Promise<Uint8Array> {\n if (!body) {\n throw new Error(`Object body is empty: ${objectKey}`);\n }\n const chunks: Uint8Array[] = [];\n let bytes = 0;\n for await (const chunk of body as AsyncIterable<Uint8Array | Buffer | string>) {\n const normalized = typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n bytes += normalized.byteLength;\n if (bytes > expectedBytes) {\n throw new Error(`Object range exceeded requested length: ${objectKey}`);\n }\n chunks.push(normalized);\n }\n if (bytes !== expectedBytes) {\n throw new Error(\n `Object range length mismatch for ${objectKey}: expected ${expectedBytes}, received ${bytes}`,\n );\n }\n return Buffer.concat(chunks, bytes);\n}\n\nfunction isS3NotFound(error: unknown): boolean {\n if (!error || typeof error !== \"object\") {\n return false;\n }\n const name = \"name\" in error ? (error as { name?: unknown }).name : undefined;\n if (name === \"NoSuchKey\" || name === \"NotFound\") {\n return true;\n }\n const metadata =\n \"$metadata\" in error\n ? (error as { $metadata?: { httpStatusCode?: number } }).$metadata\n : undefined;\n return metadata?.httpStatusCode === 404;\n}\n\nfunction createGcsObjectStorage(settings: Settings): ObjectStorage {\n const client = new GcsClient(gcsClientOptions(settings));\n const bucket = client.bucket(settings.objectStorageBucket);\n return {\n bucket: settings.objectStorageBucket,\n backend: \"gcs\",\n maxSinglePutSizeBytes: MAX_SINGLE_PUT_SIZE_BYTES,\n async createPutUrl(args) {\n const expiresIn = args.expiresInSeconds ?? UPLOAD_URL_TTL_SECONDS;\n const expiresAt = new Date(Date.now() + expiresIn * 1000);\n const config: GetSignedUrlConfig = {\n version: \"v4\",\n action: \"write\",\n expires: expiresAt,\n contentType: args.contentType,\n };\n if (args.sha256) {\n config.extensionHeaders = { \"x-goog-meta-sha256\": args.sha256 };\n }\n const [url] = await bucket.file(args.key).getSignedUrl(config);\n return {\n url,\n requiredHeaders: {\n \"content-type\": args.contentType,\n ...(args.sha256 ? { \"x-goog-meta-sha256\": args.sha256 } : {}),\n },\n expiresAt,\n };\n },\n async createGetUrl(args) {\n const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;\n const expiresAt = new Date(Date.now() + expiresIn * 1000);\n const [url] = await bucket.file(args.key).getSignedUrl({\n version: \"v4\",\n action: \"read\",\n expires: expiresAt,\n });\n return { url, expiresAt };\n },\n async putObject(args) {\n // Authenticated in-process PUT via the GCS SDK (the server holds the creds).\n await bucket.file(args.key).save(Buffer.from(args.body), {\n contentType: args.contentType,\n ...(args.sha256 ? { metadata: { metadata: { sha256: args.sha256 } } } : {}),\n });\n },\n async headFile(file) {\n const [metadata] = await bucket.file(file.objectKey).getMetadata();\n return objectHead({\n contentLength: parseContentLength(metadata.size),\n contentType: metadata.contentType,\n metadata: stringMetadata(metadata.metadata),\n });\n },\n async fileExists(file) {\n try {\n await bucket.file(file.objectKey).getMetadata();\n return true;\n } catch (error) {\n if (isGcsNotFound(error)) return false;\n throw error;\n }\n },\n async getFileBytes(file) {\n const [bytes] = await bucket.file(file.objectKey).download();\n return bytes;\n },\n async getFileRange(file, range) {\n const length = assertFileByteRange(file, range);\n try {\n const [bytes] = await bucket\n .file(file.objectKey)\n .download({ start: range.start, end: range.end });\n return exactRangeBytes(bytes, file.objectKey, length);\n } catch (error) {\n if (isGcsNotFound(error)) return null;\n throw error;\n }\n },\n async getObjectBytes(key) {\n try {\n const [bytes] = await bucket.file(key).download();\n return { bytes };\n } catch (error) {\n if (isGcsNotFound(error)) {\n return null;\n }\n throw error;\n }\n },\n async deleteObject(key) {\n // ignoreNotFound keeps the delete idempotent (a missing blob is a no-op).\n await bucket.file(key).delete({ ignoreNotFound: true });\n },\n };\n}\n\nfunction isGcsNotFound(error: unknown): boolean {\n return Boolean(error) && typeof error === \"object\" && (error as { code?: unknown }).code === 404;\n}\n\nfunction createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null {\n const sharedKey = azureSharedKeyCredential(settings);\n const requestServiceClient = settings.objectStorageAzureConnectionString\n ? BlobServiceClient.fromConnectionString(settings.objectStorageAzureConnectionString)\n : new BlobServiceClient(azureBlobServiceUrl(settings), sharedKey);\n const presignServiceClient = settings.objectStorageAzureEndpoint\n ? new BlobServiceClient(azureBlobServiceUrl(settings), sharedKey)\n : requestServiceClient;\n const requestContainerClient = requestServiceClient.getContainerClient(\n settings.objectStorageBucket,\n );\n const presignContainerClient = presignServiceClient.getContainerClient(\n settings.objectStorageBucket,\n );\n\n return {\n bucket: settings.objectStorageBucket,\n backend: \"azure-blob\",\n maxSinglePutSizeBytes: MAX_SINGLE_PUT_SIZE_BYTES,\n async createPutUrl(args) {\n const expiresIn = args.expiresInSeconds ?? UPLOAD_URL_TTL_SECONDS;\n const expiresAt = new Date(Date.now() + expiresIn * 1000);\n const blobClient = presignContainerClient.getBlockBlobClient(args.key);\n const sas = generateBlobSASQueryParameters(\n {\n containerName: settings.objectStorageBucket,\n blobName: args.key,\n permissions: BlobSASPermissions.parse(\"cw\"),\n expiresOn: expiresAt,\n contentType: args.contentType,\n },\n sharedKey,\n ).toString();\n return {\n url: `${blobClient.url}?${sas}`,\n requiredHeaders: {\n \"content-type\": args.contentType,\n \"x-ms-blob-type\": \"BlockBlob\",\n ...(args.sha256 ? { \"x-ms-meta-sha256\": args.sha256 } : {}),\n },\n expiresAt,\n };\n },\n async createGetUrl(args) {\n const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;\n const expiresAt = new Date(Date.now() + expiresIn * 1000);\n const blobClient = presignContainerClient.getBlobClient(args.key);\n const sas = generateBlobSASQueryParameters(\n {\n containerName: settings.objectStorageBucket,\n blobName: args.key,\n permissions: BlobSASPermissions.parse(\"r\"),\n expiresOn: expiresAt,\n },\n sharedKey,\n ).toString();\n return {\n url: `${blobClient.url}?${sas}`,\n expiresAt,\n };\n },\n async putObject(args) {\n // Authenticated in-process upload via the shared-key Azure client (no SAS).\n const blobClient = requestContainerClient.getBlockBlobClient(args.key);\n const body = Buffer.from(args.body);\n await blobClient.upload(body, body.byteLength, {\n blobHTTPHeaders: { blobContentType: args.contentType },\n ...(args.sha256 ? { metadata: { sha256: args.sha256 } } : {}),\n });\n },\n async headFile(file) {\n return azureHeadToObjectHead(\n await requestContainerClient.getBlobClient(file.objectKey).getProperties(),\n );\n },\n async fileExists(file) {\n try {\n await requestContainerClient.getBlobClient(file.objectKey).getProperties();\n return true;\n } catch (error) {\n if (isAzureNotFound(error)) return false;\n throw error;\n }\n },\n async getFileBytes(file) {\n return await azureDownloadToBytes(\n await requestContainerClient.getBlobClient(file.objectKey).download(),\n );\n },\n async getFileRange(file, range) {\n const length = assertFileByteRange(file, range);\n try {\n return await azureDownloadToBoundedBytes(\n await requestContainerClient.getBlobClient(file.objectKey).download(range.start, length),\n file.objectKey,\n length,\n );\n } catch (error) {\n if (isAzureNotFound(error)) return null;\n throw error;\n }\n },\n async getObjectBytes(key) {\n try {\n const download = await requestContainerClient.getBlobClient(key).download();\n const bytes = await azureDownloadToBytes(download);\n return { bytes, ...(download.contentType ? { contentType: download.contentType } : {}) };\n } catch (error) {\n if (isAzureNotFound(error)) {\n return null;\n }\n throw error;\n }\n },\n async deleteObject(key) {\n // deleteIfExists keeps the delete idempotent (a missing blob is a no-op).\n await requestContainerClient.getBlockBlobClient(key).deleteIfExists();\n },\n };\n}\n\nfunction isAzureNotFound(error: unknown): boolean {\n return (\n Boolean(error) &&\n typeof error === \"object\" &&\n (error as { statusCode?: unknown }).statusCode === 404\n );\n}\n\nfunction azureSharedKeyCredential(settings: Settings): StorageSharedKeyCredential {\n if (settings.objectStorageAzureConnectionString) {\n const parsed = parseConnectionString(settings.objectStorageAzureConnectionString);\n if (parsed.AccountName && parsed.AccountKey) {\n return new StorageSharedKeyCredential(parsed.AccountName, parsed.AccountKey);\n }\n throw new Error(\n \"Azure Blob connection string must include AccountName and AccountKey to create presigned URLs\",\n );\n }\n if (!settings.objectStorageAzureAccountName || !settings.objectStorageAzureAccountKey) {\n throw new Error(\"Azure Blob storage requires account name and account key\");\n }\n return new StorageSharedKeyCredential(\n settings.objectStorageAzureAccountName,\n settings.objectStorageAzureAccountKey,\n );\n}\n\nfunction azureBlobServiceUrl(settings: Settings): string {\n if (settings.objectStorageAzureEndpoint) {\n return settings.objectStorageAzureEndpoint.replace(/\\/+$/, \"\");\n }\n if (!settings.objectStorageAzureAccountName) {\n throw new Error(\"Azure Blob storage requires account name\");\n }\n return `https://${settings.objectStorageAzureAccountName}.blob.core.windows.net`;\n}\n\nfunction parseConnectionString(value: string): Record<string, string> {\n return Object.fromEntries(\n value\n .split(\";\")\n .map((part) => part.trim())\n .filter(Boolean)\n .map((part) => {\n const index = part.indexOf(\"=\");\n return index === -1 ? [part, \"\"] : [part.slice(0, index), part.slice(index + 1)];\n }),\n );\n}\n\nfunction gcsClientOptions(settings: Settings): StorageOptions {\n const options: StorageOptions = {\n ...(settings.objectStorageGcsProjectId\n ? { projectId: settings.objectStorageGcsProjectId }\n : {}),\n ...(settings.objectStorageGcsKeyFilename\n ? { keyFilename: settings.objectStorageGcsKeyFilename }\n : {}),\n ...(settings.objectStorageGcsApiEndpoint\n ? { apiEndpoint: settings.objectStorageGcsApiEndpoint }\n : {}),\n };\n if (settings.objectStorageGcsCredentialsJson) {\n options.credentials = parseGcsCredentials(settings.objectStorageGcsCredentialsJson);\n }\n return options;\n}\n\nfunction parseGcsCredentials(raw: string): Record<string, string> {\n const parsed = JSON.parse(raw);\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\"GCS credentials JSON must be an object\");\n }\n return parsed as Record<string, string>;\n}\n\nfunction parseContentLength(value: string | number | undefined): number | undefined {\n if (typeof value === \"number\") {\n return value;\n }\n if (typeof value !== \"string\" || value.length === 0) {\n return undefined;\n }\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : undefined;\n}\n\nfunction stringMetadata(\n value: Record<string, string | number | boolean | null> | undefined,\n): Record<string, string> | undefined {\n if (!value) {\n return undefined;\n }\n return Object.fromEntries(\n Object.entries(value).filter(\n (entry): entry is [string, string] => typeof entry[1] === \"string\",\n ),\n );\n}\n\nfunction azureHeadToObjectHead(head: BlobGetPropertiesResponse): ObjectHead {\n return objectHead({\n contentLength: head.contentLength,\n contentType: head.contentType,\n metadata: head.metadata,\n });\n}\n\nasync function azureDownloadToBytes(download: BlobDownloadResponseParsed): Promise<Uint8Array> {\n if (!download.readableStreamBody) {\n throw new Error(\"Azure Blob download response did not include a readable body\");\n }\n const chunks: Uint8Array[] = [];\n for await (const chunk of download.readableStreamBody as AsyncIterable<\n Uint8Array | Buffer | string\n >) {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n }\n return Buffer.concat(chunks);\n}\n\nasync function azureDownloadToBoundedBytes(\n download: BlobDownloadResponseParsed,\n objectKey: string,\n expectedBytes: number,\n): Promise<Uint8Array> {\n if (!download.readableStreamBody) {\n throw new Error(\"Azure Blob download response did not include a readable body\");\n }\n const chunks: Uint8Array[] = [];\n let bytes = 0;\n for await (const chunk of download.readableStreamBody as AsyncIterable<\n Uint8Array | Buffer | string\n >) {\n const normalized = typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n bytes += normalized.byteLength;\n if (bytes > expectedBytes) {\n throw new Error(`Object range exceeded requested length: ${objectKey}`);\n }\n chunks.push(normalized);\n }\n if (bytes !== expectedBytes) {\n throw new Error(\n `Object range length mismatch for ${objectKey}: expected ${expectedBytes}, received ${bytes}`,\n );\n }\n return Buffer.concat(chunks, bytes);\n}\n\nfunction assertFileByteRange(\n file: Pick<FileAsset, \"sizeBytes\" | \"objectKey\">,\n range: { start: number; end: number },\n): number {\n if (\n !Number.isSafeInteger(range.start) ||\n !Number.isSafeInteger(range.end) ||\n range.start < 0 ||\n range.end < range.start ||\n range.end >= file.sizeBytes\n ) {\n throw new RangeError(`Invalid object byte range for ${file.objectKey}`);\n }\n const length = range.end - range.start + 1;\n if (length > RETAINED_OUTPUT_MAX_PAGE_BYTES) {\n throw new RangeError(\n `Object byte range exceeds ${RETAINED_OUTPUT_MAX_PAGE_BYTES} bytes for ${file.objectKey}`,\n );\n }\n return length;\n}\n\nfunction exactRangeBytes(bytes: Uint8Array, objectKey: string, expectedBytes: number): Uint8Array {\n if (bytes.byteLength !== expectedBytes) {\n throw new Error(\n `Object range length mismatch for ${objectKey}: expected ${expectedBytes}, received ${bytes.byteLength}`,\n );\n }\n return bytes;\n}\n\nfunction objectHead(input: {\n contentLength?: number | undefined;\n contentType?: string | undefined;\n metadata?: Record<string, string> | undefined;\n}): ObjectHead {\n return {\n ...(input.contentLength !== undefined ? { ContentLength: input.contentLength } : {}),\n ...(input.contentType !== undefined ? { ContentType: input.contentType } : {}),\n ...(input.metadata !== undefined ? { Metadata: input.metadata } : {}),\n };\n}\n\nexport function bytesToDataUrl(bytes: Uint8Array, contentType: string): string {\n return `data:${contentType};base64,${Buffer.from(bytes).toString(\"base64\")}`;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/storage",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.75",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@aws-sdk/s3-request-presigner": "^3.1044.0",
|
|
36
36
|
"@azure/storage-blob": "^12.29.1",
|
|
37
37
|
"@google-cloud/storage": "^7.19.0",
|
|
38
|
-
"@opengeni/config": "^0.
|
|
39
|
-
"@opengeni/contracts": "^0.
|
|
38
|
+
"@opengeni/config": "^0.12.1",
|
|
39
|
+
"@opengeni/contracts": "^0.40.0"
|
|
40
40
|
}
|
|
41
41
|
}
|
package/src/index.ts
CHANGED
|
@@ -69,6 +69,9 @@ export type ObjectStorage = {
|
|
|
69
69
|
* the presigned-URL round-trip, which on split public/internal topologies (a
|
|
70
70
|
* public `objectStorageEndpoint` with no in-cluster route) would otherwise 401.
|
|
71
71
|
* Browser uploads keep using `createPutUrl`; this is the trusted-server twin.
|
|
72
|
+
* Repeating a PUT for the same deterministic key and verified sha256 is
|
|
73
|
+
* permitted: storage backends converge to the same bytes, while the database
|
|
74
|
+
* completion fence remains the source of truth.
|
|
72
75
|
*/
|
|
73
76
|
putObject: (args: {
|
|
74
77
|
key: string;
|
|
@@ -419,10 +422,18 @@ function isGcsNotFound(error: unknown): boolean {
|
|
|
419
422
|
|
|
420
423
|
function createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null {
|
|
421
424
|
const sharedKey = azureSharedKeyCredential(settings);
|
|
422
|
-
const
|
|
425
|
+
const requestServiceClient = settings.objectStorageAzureConnectionString
|
|
423
426
|
? BlobServiceClient.fromConnectionString(settings.objectStorageAzureConnectionString)
|
|
424
427
|
: new BlobServiceClient(azureBlobServiceUrl(settings), sharedKey);
|
|
425
|
-
const
|
|
428
|
+
const presignServiceClient = settings.objectStorageAzureEndpoint
|
|
429
|
+
? new BlobServiceClient(azureBlobServiceUrl(settings), sharedKey)
|
|
430
|
+
: requestServiceClient;
|
|
431
|
+
const requestContainerClient = requestServiceClient.getContainerClient(
|
|
432
|
+
settings.objectStorageBucket,
|
|
433
|
+
);
|
|
434
|
+
const presignContainerClient = presignServiceClient.getContainerClient(
|
|
435
|
+
settings.objectStorageBucket,
|
|
436
|
+
);
|
|
426
437
|
|
|
427
438
|
return {
|
|
428
439
|
bucket: settings.objectStorageBucket,
|
|
@@ -431,7 +442,7 @@ function createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null
|
|
|
431
442
|
async createPutUrl(args) {
|
|
432
443
|
const expiresIn = args.expiresInSeconds ?? UPLOAD_URL_TTL_SECONDS;
|
|
433
444
|
const expiresAt = new Date(Date.now() + expiresIn * 1000);
|
|
434
|
-
const blobClient =
|
|
445
|
+
const blobClient = presignContainerClient.getBlockBlobClient(args.key);
|
|
435
446
|
const sas = generateBlobSASQueryParameters(
|
|
436
447
|
{
|
|
437
448
|
containerName: settings.objectStorageBucket,
|
|
@@ -455,7 +466,7 @@ function createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null
|
|
|
455
466
|
async createGetUrl(args) {
|
|
456
467
|
const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;
|
|
457
468
|
const expiresAt = new Date(Date.now() + expiresIn * 1000);
|
|
458
|
-
const blobClient =
|
|
469
|
+
const blobClient = presignContainerClient.getBlobClient(args.key);
|
|
459
470
|
const sas = generateBlobSASQueryParameters(
|
|
460
471
|
{
|
|
461
472
|
containerName: settings.objectStorageBucket,
|
|
@@ -472,7 +483,7 @@ function createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null
|
|
|
472
483
|
},
|
|
473
484
|
async putObject(args) {
|
|
474
485
|
// Authenticated in-process upload via the shared-key Azure client (no SAS).
|
|
475
|
-
const blobClient =
|
|
486
|
+
const blobClient = requestContainerClient.getBlockBlobClient(args.key);
|
|
476
487
|
const body = Buffer.from(args.body);
|
|
477
488
|
await blobClient.upload(body, body.byteLength, {
|
|
478
489
|
blobHTTPHeaders: { blobContentType: args.contentType },
|
|
@@ -481,12 +492,12 @@ function createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null
|
|
|
481
492
|
},
|
|
482
493
|
async headFile(file) {
|
|
483
494
|
return azureHeadToObjectHead(
|
|
484
|
-
await
|
|
495
|
+
await requestContainerClient.getBlobClient(file.objectKey).getProperties(),
|
|
485
496
|
);
|
|
486
497
|
},
|
|
487
498
|
async fileExists(file) {
|
|
488
499
|
try {
|
|
489
|
-
await
|
|
500
|
+
await requestContainerClient.getBlobClient(file.objectKey).getProperties();
|
|
490
501
|
return true;
|
|
491
502
|
} catch (error) {
|
|
492
503
|
if (isAzureNotFound(error)) return false;
|
|
@@ -495,14 +506,14 @@ function createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null
|
|
|
495
506
|
},
|
|
496
507
|
async getFileBytes(file) {
|
|
497
508
|
return await azureDownloadToBytes(
|
|
498
|
-
await
|
|
509
|
+
await requestContainerClient.getBlobClient(file.objectKey).download(),
|
|
499
510
|
);
|
|
500
511
|
},
|
|
501
512
|
async getFileRange(file, range) {
|
|
502
513
|
const length = assertFileByteRange(file, range);
|
|
503
514
|
try {
|
|
504
515
|
return await azureDownloadToBoundedBytes(
|
|
505
|
-
await
|
|
516
|
+
await requestContainerClient.getBlobClient(file.objectKey).download(range.start, length),
|
|
506
517
|
file.objectKey,
|
|
507
518
|
length,
|
|
508
519
|
);
|
|
@@ -513,7 +524,7 @@ function createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null
|
|
|
513
524
|
},
|
|
514
525
|
async getObjectBytes(key) {
|
|
515
526
|
try {
|
|
516
|
-
const download = await
|
|
527
|
+
const download = await requestContainerClient.getBlobClient(key).download();
|
|
517
528
|
const bytes = await azureDownloadToBytes(download);
|
|
518
529
|
return { bytes, ...(download.contentType ? { contentType: download.contentType } : {}) };
|
|
519
530
|
} catch (error) {
|
|
@@ -525,7 +536,7 @@ function createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null
|
|
|
525
536
|
},
|
|
526
537
|
async deleteObject(key) {
|
|
527
538
|
// deleteIfExists keeps the delete idempotent (a missing blob is a no-op).
|
|
528
|
-
await
|
|
539
|
+
await requestContainerClient.getBlockBlobClient(key).deleteIfExists();
|
|
529
540
|
},
|
|
530
541
|
};
|
|
531
542
|
}
|