@opengeni/storage 0.2.35 → 0.2.46
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 +9 -12
- package/dist/index.js +18 -12
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/index.ts +20 -12
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { Settings } from
|
|
2
|
-
import { FileAsset } from
|
|
3
|
-
|
|
4
|
-
declare const
|
|
5
|
-
declare const
|
|
6
|
-
|
|
7
|
-
type ObjectHead = {
|
|
1
|
+
import type { Settings } from "@opengeni/config";
|
|
2
|
+
import { type FileAsset } from "@opengeni/contracts";
|
|
3
|
+
export declare const MAX_SINGLE_PUT_SIZE_BYTES = 5000000000;
|
|
4
|
+
export declare const UPLOAD_URL_TTL_SECONDS: number;
|
|
5
|
+
export declare const DOWNLOAD_URL_TTL_SECONDS: number;
|
|
6
|
+
export type ObjectHead = {
|
|
8
7
|
ContentLength?: number;
|
|
9
8
|
ContentType?: string;
|
|
10
9
|
Metadata?: Record<string, string>;
|
|
11
10
|
};
|
|
12
|
-
type ObjectStorage = {
|
|
11
|
+
export type ObjectStorage = {
|
|
13
12
|
bucket: string;
|
|
14
13
|
backend: "s3-compatible" | "aws-s3" | "azure-blob" | "gcs";
|
|
15
14
|
maxSinglePutSizeBytes: number;
|
|
@@ -72,7 +71,5 @@ type ObjectStorage = {
|
|
|
72
71
|
*/
|
|
73
72
|
deleteObject: (key: string) => Promise<void>;
|
|
74
73
|
};
|
|
75
|
-
declare function createObjectStorage(settings: Settings): ObjectStorage | null;
|
|
76
|
-
declare function bytesToDataUrl(bytes: Uint8Array, contentType: string): string;
|
|
77
|
-
|
|
78
|
-
export { DOWNLOAD_URL_TTL_SECONDS, MAX_SINGLE_PUT_SIZE_BYTES, type ObjectHead, type ObjectStorage, UPLOAD_URL_TTL_SECONDS, bytesToDataUrl, createObjectStorage };
|
|
74
|
+
export declare function createObjectStorage(settings: Settings): ObjectStorage | null;
|
|
75
|
+
export declare function bytesToDataUrl(bytes: Uint8Array, contentType: string): string;
|
package/dist/index.js
CHANGED
|
@@ -33,11 +33,10 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
33
33
|
if (settings.objectStorageBackend === "s3-compatible" && (!settings.objectStorageEndpoint || !settings.objectStorageAccessKeyId || !settings.objectStorageSecretAccessKey)) {
|
|
34
34
|
return null;
|
|
35
35
|
}
|
|
36
|
-
const
|
|
36
|
+
const sharedClientConfig = {
|
|
37
37
|
region: settings.objectStorageRegion,
|
|
38
38
|
forcePathStyle: settings.objectStorageForcePathStyle,
|
|
39
39
|
requestChecksumCalculation: "WHEN_REQUIRED",
|
|
40
|
-
...settings.objectStorageEndpoint ? { endpoint: settings.objectStorageEndpoint } : {},
|
|
41
40
|
...settings.objectStorageAccessKeyId && settings.objectStorageSecretAccessKey ? {
|
|
42
41
|
credentials: {
|
|
43
42
|
accessKeyId: settings.objectStorageAccessKeyId,
|
|
@@ -45,7 +44,14 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
45
44
|
}
|
|
46
45
|
} : {}
|
|
47
46
|
};
|
|
48
|
-
const
|
|
47
|
+
const presignClient = new S3Client({
|
|
48
|
+
...sharedClientConfig,
|
|
49
|
+
...settings.objectStorageEndpoint ? { endpoint: settings.objectStorageEndpoint } : {}
|
|
50
|
+
});
|
|
51
|
+
const requestClient = settings.objectStorageInternalEndpoint ? new S3Client({
|
|
52
|
+
...sharedClientConfig,
|
|
53
|
+
endpoint: settings.objectStorageInternalEndpoint
|
|
54
|
+
}) : presignClient;
|
|
49
55
|
return {
|
|
50
56
|
bucket: settings.objectStorageBucket,
|
|
51
57
|
backend: settings.objectStorageBackend === "aws-s3" ? "aws-s3" : "s3-compatible",
|
|
@@ -62,7 +68,7 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
62
68
|
Metadata: args.sha256 ? { sha256: args.sha256 } : void 0
|
|
63
69
|
});
|
|
64
70
|
return {
|
|
65
|
-
url: await getSignedUrl(
|
|
71
|
+
url: await getSignedUrl(presignClient, command, { expiresIn }),
|
|
66
72
|
requiredHeaders,
|
|
67
73
|
expiresAt: new Date(Date.now() + expiresIn * 1e3)
|
|
68
74
|
};
|
|
@@ -71,7 +77,7 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
71
77
|
const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;
|
|
72
78
|
return {
|
|
73
79
|
url: await getSignedUrl(
|
|
74
|
-
|
|
80
|
+
presignClient,
|
|
75
81
|
new GetObjectCommand({
|
|
76
82
|
Bucket: settings.objectStorageBucket,
|
|
77
83
|
Key: args.key
|
|
@@ -82,7 +88,7 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
82
88
|
};
|
|
83
89
|
},
|
|
84
90
|
async putObject(args) {
|
|
85
|
-
await
|
|
91
|
+
await requestClient.send(
|
|
86
92
|
new PutObjectCommand({
|
|
87
93
|
Bucket: settings.objectStorageBucket,
|
|
88
94
|
Key: args.key,
|
|
@@ -93,7 +99,7 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
93
99
|
);
|
|
94
100
|
},
|
|
95
101
|
async headFile(file) {
|
|
96
|
-
const head = await
|
|
102
|
+
const head = await requestClient.send(
|
|
97
103
|
new HeadObjectCommand({
|
|
98
104
|
Bucket: file.bucket,
|
|
99
105
|
Key: file.objectKey
|
|
@@ -107,7 +113,7 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
107
113
|
},
|
|
108
114
|
async fileExists(file) {
|
|
109
115
|
try {
|
|
110
|
-
await
|
|
116
|
+
await requestClient.send(
|
|
111
117
|
new HeadObjectCommand({
|
|
112
118
|
Bucket: file.bucket,
|
|
113
119
|
Key: file.objectKey
|
|
@@ -120,7 +126,7 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
120
126
|
}
|
|
121
127
|
},
|
|
122
128
|
async getFileBytes(file) {
|
|
123
|
-
const result = await
|
|
129
|
+
const result = await requestClient.send(
|
|
124
130
|
new GetObjectCommand({
|
|
125
131
|
Bucket: file.bucket,
|
|
126
132
|
Key: file.objectKey
|
|
@@ -131,7 +137,7 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
131
137
|
async getFileRange(file, range) {
|
|
132
138
|
const length = assertFileByteRange(file, range);
|
|
133
139
|
try {
|
|
134
|
-
const result = await
|
|
140
|
+
const result = await requestClient.send(
|
|
135
141
|
new GetObjectCommand({
|
|
136
142
|
Bucket: file.bucket,
|
|
137
143
|
Key: file.objectKey,
|
|
@@ -146,7 +152,7 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
146
152
|
},
|
|
147
153
|
async getObjectBytes(key) {
|
|
148
154
|
try {
|
|
149
|
-
const result = await
|
|
155
|
+
const result = await requestClient.send(
|
|
150
156
|
new GetObjectCommand({
|
|
151
157
|
Bucket: settings.objectStorageBucket,
|
|
152
158
|
Key: key
|
|
@@ -162,7 +168,7 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
162
168
|
}
|
|
163
169
|
},
|
|
164
170
|
async deleteObject(key) {
|
|
165
|
-
await
|
|
171
|
+
await requestClient.send(
|
|
166
172
|
new DeleteObjectCommand({
|
|
167
173
|
Bucket: settings.objectStorageBucket,
|
|
168
174
|
Key: key
|
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 clientConfig: S3ClientConfig = {\n region: settings.objectStorageRegion,\n forcePathStyle: settings.objectStorageForcePathStyle,\n requestChecksumCalculation: \"WHEN_REQUIRED\",\n ...(settings.objectStorageEndpoint ? { endpoint: settings.objectStorageEndpoint } : {}),\n ...(settings.objectStorageAccessKeyId && settings.objectStorageSecretAccessKey\n ? {\n credentials: {\n accessKeyId: settings.objectStorageAccessKeyId,\n secretAccessKey: settings.objectStorageSecretAccessKey,\n },\n }\n : {}),\n };\n const client = new S3Client(clientConfig);\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(client, 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 client,\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 client.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 client.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 client.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 client.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 client.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 client.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 client.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 */\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":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/storage",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.46",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"provenance": true
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
|
-
"build": "
|
|
30
|
-
"typecheck": "
|
|
29
|
+
"build": "bun ../../scripts/build-typescript-package.ts",
|
|
30
|
+
"typecheck": "tsc --noEmit",
|
|
31
31
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
@@ -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.7.
|
|
39
|
-
"@opengeni/contracts": "^0.
|
|
38
|
+
"@opengeni/config": "^0.7.22",
|
|
39
|
+
"@opengeni/contracts": "^0.26.1"
|
|
40
40
|
}
|
|
41
41
|
}
|
package/src/index.ts
CHANGED
|
@@ -106,11 +106,10 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
106
106
|
) {
|
|
107
107
|
return null;
|
|
108
108
|
}
|
|
109
|
-
const
|
|
109
|
+
const sharedClientConfig: S3ClientConfig = {
|
|
110
110
|
region: settings.objectStorageRegion,
|
|
111
111
|
forcePathStyle: settings.objectStorageForcePathStyle,
|
|
112
112
|
requestChecksumCalculation: "WHEN_REQUIRED",
|
|
113
|
-
...(settings.objectStorageEndpoint ? { endpoint: settings.objectStorageEndpoint } : {}),
|
|
114
113
|
...(settings.objectStorageAccessKeyId && settings.objectStorageSecretAccessKey
|
|
115
114
|
? {
|
|
116
115
|
credentials: {
|
|
@@ -120,7 +119,16 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
120
119
|
}
|
|
121
120
|
: {}),
|
|
122
121
|
};
|
|
123
|
-
const
|
|
122
|
+
const presignClient = new S3Client({
|
|
123
|
+
...sharedClientConfig,
|
|
124
|
+
...(settings.objectStorageEndpoint ? { endpoint: settings.objectStorageEndpoint } : {}),
|
|
125
|
+
});
|
|
126
|
+
const requestClient = settings.objectStorageInternalEndpoint
|
|
127
|
+
? new S3Client({
|
|
128
|
+
...sharedClientConfig,
|
|
129
|
+
endpoint: settings.objectStorageInternalEndpoint,
|
|
130
|
+
})
|
|
131
|
+
: presignClient;
|
|
124
132
|
return {
|
|
125
133
|
bucket: settings.objectStorageBucket,
|
|
126
134
|
backend: settings.objectStorageBackend === "aws-s3" ? "aws-s3" : "s3-compatible",
|
|
@@ -137,7 +145,7 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
137
145
|
Metadata: args.sha256 ? { sha256: args.sha256 } : undefined,
|
|
138
146
|
});
|
|
139
147
|
return {
|
|
140
|
-
url: await getSignedUrl(
|
|
148
|
+
url: await getSignedUrl(presignClient, command, { expiresIn }),
|
|
141
149
|
requiredHeaders,
|
|
142
150
|
expiresAt: new Date(Date.now() + expiresIn * 1000),
|
|
143
151
|
};
|
|
@@ -146,7 +154,7 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
146
154
|
const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;
|
|
147
155
|
return {
|
|
148
156
|
url: await getSignedUrl(
|
|
149
|
-
|
|
157
|
+
presignClient,
|
|
150
158
|
new GetObjectCommand({
|
|
151
159
|
Bucket: settings.objectStorageBucket,
|
|
152
160
|
Key: args.key,
|
|
@@ -161,7 +169,7 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
161
169
|
// A presigned URL buys nothing here — the worker already holds the creds — and
|
|
162
170
|
// on a split public/internal endpoint topology the presigned URL points at the
|
|
163
171
|
// PUBLIC host (no MinIO route → 401). This sends bytes straight to the backend.
|
|
164
|
-
await
|
|
172
|
+
await requestClient.send(
|
|
165
173
|
new PutObjectCommand({
|
|
166
174
|
Bucket: settings.objectStorageBucket,
|
|
167
175
|
Key: args.key,
|
|
@@ -172,7 +180,7 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
172
180
|
);
|
|
173
181
|
},
|
|
174
182
|
async headFile(file) {
|
|
175
|
-
const head = await
|
|
183
|
+
const head = await requestClient.send(
|
|
176
184
|
new HeadObjectCommand({
|
|
177
185
|
Bucket: file.bucket,
|
|
178
186
|
Key: file.objectKey,
|
|
@@ -186,7 +194,7 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
186
194
|
},
|
|
187
195
|
async fileExists(file) {
|
|
188
196
|
try {
|
|
189
|
-
await
|
|
197
|
+
await requestClient.send(
|
|
190
198
|
new HeadObjectCommand({
|
|
191
199
|
Bucket: file.bucket,
|
|
192
200
|
Key: file.objectKey,
|
|
@@ -199,7 +207,7 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
199
207
|
}
|
|
200
208
|
},
|
|
201
209
|
async getFileBytes(file) {
|
|
202
|
-
const result = await
|
|
210
|
+
const result = await requestClient.send(
|
|
203
211
|
new GetObjectCommand({
|
|
204
212
|
Bucket: file.bucket,
|
|
205
213
|
Key: file.objectKey,
|
|
@@ -210,7 +218,7 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
210
218
|
async getFileRange(file, range) {
|
|
211
219
|
const length = assertFileByteRange(file, range);
|
|
212
220
|
try {
|
|
213
|
-
const result = await
|
|
221
|
+
const result = await requestClient.send(
|
|
214
222
|
new GetObjectCommand({
|
|
215
223
|
Bucket: file.bucket,
|
|
216
224
|
Key: file.objectKey,
|
|
@@ -225,7 +233,7 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
225
233
|
},
|
|
226
234
|
async getObjectBytes(key) {
|
|
227
235
|
try {
|
|
228
|
-
const result = await
|
|
236
|
+
const result = await requestClient.send(
|
|
229
237
|
new GetObjectCommand({
|
|
230
238
|
Bucket: settings.objectStorageBucket,
|
|
231
239
|
Key: key,
|
|
@@ -242,7 +250,7 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
242
250
|
},
|
|
243
251
|
async deleteObject(key) {
|
|
244
252
|
// S3 DeleteObject is idempotent — deleting an absent key returns 204.
|
|
245
|
-
await
|
|
253
|
+
await requestClient.send(
|
|
246
254
|
new DeleteObjectCommand({
|
|
247
255
|
Bucket: settings.objectStorageBucket,
|
|
248
256
|
Key: key,
|