@opengeni/storage 0.2.7 → 0.2.9
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 +14 -0
- package/dist/index.js +165 -56
- package/dist/index.js.map +1 -1
- package/package.json +14 -14
- package/src/index.ts +235 -70
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,11 @@ type ObjectStorage = {
|
|
|
32
32
|
}>;
|
|
33
33
|
headFile: (file: FileAsset) => Promise<ObjectHead>;
|
|
34
34
|
getFileBytes: (file: FileAsset) => Promise<Uint8Array>;
|
|
35
|
+
/** Fetch an object by raw storage key (not a tracked FileAsset). Returns null on 404/missing. */
|
|
36
|
+
getObjectBytes: (key: string) => Promise<{
|
|
37
|
+
bytes: Uint8Array;
|
|
38
|
+
contentType?: string;
|
|
39
|
+
} | null>;
|
|
35
40
|
/**
|
|
36
41
|
* SERVER-SIDE authenticated direct PUT (no presign + browser fetch). For an
|
|
37
42
|
* in-process upload from a trusted holder of the storage credentials (e.g. the
|
|
@@ -47,6 +52,15 @@ type ObjectStorage = {
|
|
|
47
52
|
body: Uint8Array;
|
|
48
53
|
sha256?: string | null;
|
|
49
54
|
}) => Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* SERVER-SIDE authenticated delete of a single object by raw storage key.
|
|
57
|
+
* Idempotent: a missing key is a no-op (S3/GCS/Azure delete-by-key does not
|
|
58
|
+
* 404 on absent objects, or the 404 is swallowed). Used by the Workbench v2
|
|
59
|
+
* capture GC (dossier §10.1 keep-latest-10) to reap after-image blobs that no
|
|
60
|
+
* surviving revision references. Best-effort at the call site — a failed
|
|
61
|
+
* delete leaves an orphan blob, never corrupts a live capture.
|
|
62
|
+
*/
|
|
63
|
+
deleteObject: (key: string) => Promise<void>;
|
|
50
64
|
};
|
|
51
65
|
declare function createObjectStorage(settings: Settings): ObjectStorage | null;
|
|
52
66
|
declare function bytesToDataUrl(bytes: Uint8Array, contentType: string): string;
|
package/dist/index.js
CHANGED
|
@@ -6,13 +6,16 @@ import {
|
|
|
6
6
|
StorageSharedKeyCredential
|
|
7
7
|
} from "@azure/storage-blob";
|
|
8
8
|
import {
|
|
9
|
+
DeleteObjectCommand,
|
|
9
10
|
GetObjectCommand,
|
|
10
11
|
HeadObjectCommand,
|
|
11
12
|
PutObjectCommand,
|
|
12
13
|
S3Client
|
|
13
14
|
} from "@aws-sdk/client-s3";
|
|
14
15
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
Storage as GcsClient
|
|
18
|
+
} from "@google-cloud/storage";
|
|
16
19
|
var MAX_SINGLE_PUT_SIZE_BYTES = 5e9;
|
|
17
20
|
var UPLOAD_URL_TTL_SECONDS = 15 * 60;
|
|
18
21
|
var DOWNLOAD_URL_TTL_SECONDS = 5 * 60;
|
|
@@ -34,10 +37,12 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
34
37
|
forcePathStyle: settings.objectStorageForcePathStyle,
|
|
35
38
|
requestChecksumCalculation: "WHEN_REQUIRED",
|
|
36
39
|
...settings.objectStorageEndpoint ? { endpoint: settings.objectStorageEndpoint } : {},
|
|
37
|
-
...settings.objectStorageAccessKeyId && settings.objectStorageSecretAccessKey ? {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
...settings.objectStorageAccessKeyId && settings.objectStorageSecretAccessKey ? {
|
|
41
|
+
credentials: {
|
|
42
|
+
accessKeyId: settings.objectStorageAccessKeyId,
|
|
43
|
+
secretAccessKey: settings.objectStorageSecretAccessKey
|
|
44
|
+
}
|
|
45
|
+
} : {}
|
|
41
46
|
};
|
|
42
47
|
const client = new S3Client(clientConfig);
|
|
43
48
|
return {
|
|
@@ -64,27 +69,35 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
64
69
|
async createGetUrl(args) {
|
|
65
70
|
const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;
|
|
66
71
|
return {
|
|
67
|
-
url: await getSignedUrl(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
url: await getSignedUrl(
|
|
73
|
+
client,
|
|
74
|
+
new GetObjectCommand({
|
|
75
|
+
Bucket: settings.objectStorageBucket,
|
|
76
|
+
Key: args.key
|
|
77
|
+
}),
|
|
78
|
+
{ expiresIn }
|
|
79
|
+
),
|
|
71
80
|
expiresAt: new Date(Date.now() + expiresIn * 1e3)
|
|
72
81
|
};
|
|
73
82
|
},
|
|
74
83
|
async putObject(args) {
|
|
75
|
-
await client.send(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
await client.send(
|
|
85
|
+
new PutObjectCommand({
|
|
86
|
+
Bucket: settings.objectStorageBucket,
|
|
87
|
+
Key: args.key,
|
|
88
|
+
ContentType: args.contentType,
|
|
89
|
+
Body: args.body,
|
|
90
|
+
Metadata: args.sha256 ? { sha256: args.sha256 } : void 0
|
|
91
|
+
})
|
|
92
|
+
);
|
|
82
93
|
},
|
|
83
94
|
async headFile(file) {
|
|
84
|
-
const head = await client.send(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
const head = await client.send(
|
|
96
|
+
new HeadObjectCommand({
|
|
97
|
+
Bucket: file.bucket,
|
|
98
|
+
Key: file.objectKey
|
|
99
|
+
})
|
|
100
|
+
);
|
|
88
101
|
return objectHead({
|
|
89
102
|
contentLength: head.ContentLength,
|
|
90
103
|
contentType: head.ContentType,
|
|
@@ -92,24 +105,66 @@ function createS3CompatibleObjectStorage(settings) {
|
|
|
92
105
|
});
|
|
93
106
|
},
|
|
94
107
|
async getFileBytes(file) {
|
|
95
|
-
const result = await client.send(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
const result = await client.send(
|
|
109
|
+
new GetObjectCommand({
|
|
110
|
+
Bucket: file.bucket,
|
|
111
|
+
Key: file.objectKey
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
return await s3BodyToBytes(result.Body, file.objectKey);
|
|
115
|
+
},
|
|
116
|
+
async getObjectBytes(key) {
|
|
117
|
+
try {
|
|
118
|
+
const result = await client.send(
|
|
119
|
+
new GetObjectCommand({
|
|
120
|
+
Bucket: settings.objectStorageBucket,
|
|
121
|
+
Key: key
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
const bytes = await s3BodyToBytes(result.Body, key);
|
|
125
|
+
return { bytes, ...result.ContentType ? { contentType: result.ContentType } : {} };
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (isS3NotFound(error)) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
108
131
|
}
|
|
109
|
-
|
|
132
|
+
},
|
|
133
|
+
async deleteObject(key) {
|
|
134
|
+
await client.send(
|
|
135
|
+
new DeleteObjectCommand({
|
|
136
|
+
Bucket: settings.objectStorageBucket,
|
|
137
|
+
Key: key
|
|
138
|
+
})
|
|
139
|
+
);
|
|
110
140
|
}
|
|
111
141
|
};
|
|
112
142
|
}
|
|
143
|
+
async function s3BodyToBytes(body, objectKey) {
|
|
144
|
+
if (!body) {
|
|
145
|
+
throw new Error(`Object body is empty: ${objectKey}`);
|
|
146
|
+
}
|
|
147
|
+
const withTransform = body;
|
|
148
|
+
if (typeof withTransform.transformToByteArray === "function") {
|
|
149
|
+
return await withTransform.transformToByteArray();
|
|
150
|
+
}
|
|
151
|
+
const chunks = [];
|
|
152
|
+
for await (const chunk of body) {
|
|
153
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
154
|
+
}
|
|
155
|
+
return Buffer.concat(chunks);
|
|
156
|
+
}
|
|
157
|
+
function isS3NotFound(error) {
|
|
158
|
+
if (!error || typeof error !== "object") {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
const name = "name" in error ? error.name : void 0;
|
|
162
|
+
if (name === "NoSuchKey" || name === "NotFound") {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
const metadata = "$metadata" in error ? error.$metadata : void 0;
|
|
166
|
+
return metadata?.httpStatusCode === 404;
|
|
167
|
+
}
|
|
113
168
|
function createGcsObjectStorage(settings) {
|
|
114
169
|
const client = new GcsClient(gcsClientOptions(settings));
|
|
115
170
|
const bucket = client.bucket(settings.objectStorageBucket);
|
|
@@ -166,9 +221,26 @@ function createGcsObjectStorage(settings) {
|
|
|
166
221
|
async getFileBytes(file) {
|
|
167
222
|
const [bytes] = await bucket.file(file.objectKey).download();
|
|
168
223
|
return bytes;
|
|
224
|
+
},
|
|
225
|
+
async getObjectBytes(key) {
|
|
226
|
+
try {
|
|
227
|
+
const [bytes] = await bucket.file(key).download();
|
|
228
|
+
return { bytes };
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (isGcsNotFound(error)) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
async deleteObject(key) {
|
|
237
|
+
await bucket.file(key).delete({ ignoreNotFound: true });
|
|
169
238
|
}
|
|
170
239
|
};
|
|
171
240
|
}
|
|
241
|
+
function isGcsNotFound(error) {
|
|
242
|
+
return Boolean(error) && typeof error === "object" && error.code === 404;
|
|
243
|
+
}
|
|
172
244
|
function createAzureBlobObjectStorage(settings) {
|
|
173
245
|
const sharedKey = azureSharedKeyCredential(settings);
|
|
174
246
|
const serviceClient = settings.objectStorageAzureConnectionString ? BlobServiceClient.fromConnectionString(settings.objectStorageAzureConnectionString) : new BlobServiceClient(azureBlobServiceUrl(settings), sharedKey);
|
|
@@ -181,13 +253,16 @@ function createAzureBlobObjectStorage(settings) {
|
|
|
181
253
|
const expiresIn = args.expiresInSeconds ?? UPLOAD_URL_TTL_SECONDS;
|
|
182
254
|
const expiresAt = new Date(Date.now() + expiresIn * 1e3);
|
|
183
255
|
const blobClient = containerClient.getBlockBlobClient(args.key);
|
|
184
|
-
const sas = generateBlobSASQueryParameters(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
256
|
+
const sas = generateBlobSASQueryParameters(
|
|
257
|
+
{
|
|
258
|
+
containerName: settings.objectStorageBucket,
|
|
259
|
+
blobName: args.key,
|
|
260
|
+
permissions: BlobSASPermissions.parse("cw"),
|
|
261
|
+
expiresOn: expiresAt,
|
|
262
|
+
contentType: args.contentType
|
|
263
|
+
},
|
|
264
|
+
sharedKey
|
|
265
|
+
).toString();
|
|
191
266
|
return {
|
|
192
267
|
url: `${blobClient.url}?${sas}`,
|
|
193
268
|
requiredHeaders: {
|
|
@@ -202,12 +277,15 @@ function createAzureBlobObjectStorage(settings) {
|
|
|
202
277
|
const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;
|
|
203
278
|
const expiresAt = new Date(Date.now() + expiresIn * 1e3);
|
|
204
279
|
const blobClient = containerClient.getBlobClient(args.key);
|
|
205
|
-
const sas = generateBlobSASQueryParameters(
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
280
|
+
const sas = generateBlobSASQueryParameters(
|
|
281
|
+
{
|
|
282
|
+
containerName: settings.objectStorageBucket,
|
|
283
|
+
blobName: args.key,
|
|
284
|
+
permissions: BlobSASPermissions.parse("r"),
|
|
285
|
+
expiresOn: expiresAt
|
|
286
|
+
},
|
|
287
|
+
sharedKey
|
|
288
|
+
).toString();
|
|
211
289
|
return {
|
|
212
290
|
url: `${blobClient.url}?${sas}`,
|
|
213
291
|
expiresAt
|
|
@@ -222,25 +300,52 @@ function createAzureBlobObjectStorage(settings) {
|
|
|
222
300
|
});
|
|
223
301
|
},
|
|
224
302
|
async headFile(file) {
|
|
225
|
-
return azureHeadToObjectHead(
|
|
303
|
+
return azureHeadToObjectHead(
|
|
304
|
+
await containerClient.getBlobClient(file.objectKey).getProperties()
|
|
305
|
+
);
|
|
226
306
|
},
|
|
227
307
|
async getFileBytes(file) {
|
|
228
|
-
return await azureDownloadToBytes(
|
|
308
|
+
return await azureDownloadToBytes(
|
|
309
|
+
await containerClient.getBlobClient(file.objectKey).download()
|
|
310
|
+
);
|
|
311
|
+
},
|
|
312
|
+
async getObjectBytes(key) {
|
|
313
|
+
try {
|
|
314
|
+
const download = await containerClient.getBlobClient(key).download();
|
|
315
|
+
const bytes = await azureDownloadToBytes(download);
|
|
316
|
+
return { bytes, ...download.contentType ? { contentType: download.contentType } : {} };
|
|
317
|
+
} catch (error) {
|
|
318
|
+
if (isAzureNotFound(error)) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
async deleteObject(key) {
|
|
325
|
+
await containerClient.getBlockBlobClient(key).deleteIfExists();
|
|
229
326
|
}
|
|
230
327
|
};
|
|
231
328
|
}
|
|
329
|
+
function isAzureNotFound(error) {
|
|
330
|
+
return Boolean(error) && typeof error === "object" && error.statusCode === 404;
|
|
331
|
+
}
|
|
232
332
|
function azureSharedKeyCredential(settings) {
|
|
233
333
|
if (settings.objectStorageAzureConnectionString) {
|
|
234
334
|
const parsed = parseConnectionString(settings.objectStorageAzureConnectionString);
|
|
235
335
|
if (parsed.AccountName && parsed.AccountKey) {
|
|
236
336
|
return new StorageSharedKeyCredential(parsed.AccountName, parsed.AccountKey);
|
|
237
337
|
}
|
|
238
|
-
throw new Error(
|
|
338
|
+
throw new Error(
|
|
339
|
+
"Azure Blob connection string must include AccountName and AccountKey to create presigned URLs"
|
|
340
|
+
);
|
|
239
341
|
}
|
|
240
342
|
if (!settings.objectStorageAzureAccountName || !settings.objectStorageAzureAccountKey) {
|
|
241
343
|
throw new Error("Azure Blob storage requires account name and account key");
|
|
242
344
|
}
|
|
243
|
-
return new StorageSharedKeyCredential(
|
|
345
|
+
return new StorageSharedKeyCredential(
|
|
346
|
+
settings.objectStorageAzureAccountName,
|
|
347
|
+
settings.objectStorageAzureAccountKey
|
|
348
|
+
);
|
|
244
349
|
}
|
|
245
350
|
function azureBlobServiceUrl(settings) {
|
|
246
351
|
if (settings.objectStorageAzureEndpoint) {
|
|
@@ -252,10 +357,12 @@ function azureBlobServiceUrl(settings) {
|
|
|
252
357
|
return `https://${settings.objectStorageAzureAccountName}.blob.core.windows.net`;
|
|
253
358
|
}
|
|
254
359
|
function parseConnectionString(value) {
|
|
255
|
-
return Object.fromEntries(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
360
|
+
return Object.fromEntries(
|
|
361
|
+
value.split(";").map((part) => part.trim()).filter(Boolean).map((part) => {
|
|
362
|
+
const index = part.indexOf("=");
|
|
363
|
+
return index === -1 ? [part, ""] : [part.slice(0, index), part.slice(index + 1)];
|
|
364
|
+
})
|
|
365
|
+
);
|
|
259
366
|
}
|
|
260
367
|
function gcsClientOptions(settings) {
|
|
261
368
|
const options = {
|
|
@@ -290,7 +397,9 @@ function stringMetadata(value) {
|
|
|
290
397
|
return void 0;
|
|
291
398
|
}
|
|
292
399
|
return Object.fromEntries(
|
|
293
|
-
Object.entries(value).filter(
|
|
400
|
+
Object.entries(value).filter(
|
|
401
|
+
(entry) => typeof entry[1] === "string"
|
|
402
|
+
)
|
|
294
403
|
);
|
|
295
404
|
}
|
|
296
405
|
function azureHeadToObjectHead(head) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Settings } from \"@opengeni/config\";\nimport type { FileAsset } from \"@opengeni/contracts\";\nimport {\n BlobSASPermissions,\n BlobServiceClient,\n BlockBlobClient,\n generateBlobSASQueryParameters,\n StorageSharedKeyCredential,\n type BlobDownloadResponseParsed,\n type BlobGetPropertiesResponse,\n} from \"@azure/storage-blob\";\nimport {\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 { Storage as GcsClient, type GetSignedUrlConfig, type StorageOptions } 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: { key: string; contentType: string; sha256?: string | null; expiresInSeconds?: number }) => Promise<{ url: string; requiredHeaders: Record<string, string>; expiresAt: Date }>;\n createGetUrl: (args: { key: string; expiresInSeconds?: number }) => Promise<{ url: string; expiresAt: Date }>;\n headFile: (file: FileAsset) => Promise<ObjectHead>;\n getFileBytes: (file: FileAsset) => Promise<Uint8Array>;\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: { key: string; contentType: string; body: Uint8Array; sha256?: string | null }) => 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 (settings.objectStorageBackend === \"s3-compatible\" && (!settings.objectStorageEndpoint || !settings.objectStorageAccessKeyId || !settings.objectStorageSecretAccessKey)) {\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 ? { credentials: {\n accessKeyId: settings.objectStorageAccessKeyId,\n secretAccessKey: settings.objectStorageSecretAccessKey,\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(client, new GetObjectCommand({\n Bucket: settings.objectStorageBucket,\n Key: args.key,\n }), { expiresIn }),\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(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 async headFile(file) {\n const head = await client.send(new HeadObjectCommand({\n Bucket: file.bucket,\n Key: file.objectKey,\n }));\n return objectHead({\n contentLength: head.ContentLength,\n contentType: head.ContentType,\n metadata: head.Metadata,\n });\n },\n async getFileBytes(file) {\n const result = await client.send(new GetObjectCommand({\n Bucket: file.bucket,\n Key: file.objectKey,\n }));\n if (!result.Body) {\n throw new Error(`Object body is empty: ${file.objectKey}`);\n }\n if (typeof result.Body.transformToByteArray === \"function\") {\n return await result.Body.transformToByteArray();\n }\n const chunks: Uint8Array[] = [];\n for await (const chunk of result.Body as AsyncIterable<Uint8Array | Buffer | string>) {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n }\n return Buffer.concat(chunks);\n },\n };\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 getFileBytes(file) {\n const [bytes] = await bucket.file(file.objectKey).download();\n return bytes;\n },\n };\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 containerName: settings.objectStorageBucket,\n blobName: args.key,\n permissions: BlobSASPermissions.parse(\"cw\"),\n expiresOn: expiresAt,\n contentType: args.contentType,\n }, sharedKey).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 containerName: settings.objectStorageBucket,\n blobName: args.key,\n permissions: BlobSASPermissions.parse(\"r\"),\n expiresOn: expiresAt,\n }, sharedKey).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(await containerClient.getBlobClient(file.objectKey).getProperties());\n },\n async getFileBytes(file) {\n return await azureDownloadToBytes(await containerClient.getBlobClient(file.objectKey).download());\n },\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(\"Azure Blob connection string must include AccountName and AccountKey to create presigned URLs\");\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(settings.objectStorageAzureAccountName, settings.objectStorageAzureAccountKey);\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(value.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\nfunction gcsClientOptions(settings: Settings): StorageOptions {\n const options: StorageOptions = {\n ...(settings.objectStorageGcsProjectId ? { projectId: settings.objectStorageGcsProjectId } : {}),\n ...(settings.objectStorageGcsKeyFilename ? { keyFilename: settings.objectStorageGcsKeyFilename } : {}),\n ...(settings.objectStorageGcsApiEndpoint ? { apiEndpoint: settings.objectStorageGcsApiEndpoint } : {}),\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(value: Record<string, string | number | boolean | null> | undefined): Record<string, string> | undefined {\n if (!value) {\n return undefined;\n }\n return Object.fromEntries(\n Object.entries(value)\n .filter((entry): entry is [string, string] => typeof entry[1] === \"string\"),\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<Uint8Array | Buffer | string>) {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : chunk);\n }\n return Buffer.concat(chunks);\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 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 getFileBytes: (file: FileAsset) => Promise<Uint8Array>;\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 (dossier §10.1 keep-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 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 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\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 getFileBytes(file) {\n const [bytes] = await bucket.file(file.objectKey).download();\n return bytes;\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 getFileBytes(file) {\n return await azureDownloadToBytes(\n await containerClient.getBlobClient(file.objectKey).download(),\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\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,8 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/storage",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "0.2.9",
|
|
5
4
|
"license": "Apache-2.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/Cloudgeni-ai/opengeni.git",
|
|
8
|
+
"directory": "packages/storage"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
6
15
|
"main": "./dist/index.js",
|
|
7
16
|
"module": "./dist/index.js",
|
|
8
17
|
"types": "./dist/index.d.ts",
|
|
@@ -12,17 +21,13 @@
|
|
|
12
21
|
"import": "./dist/index.js"
|
|
13
22
|
}
|
|
14
23
|
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist",
|
|
17
|
-
"src"
|
|
18
|
-
],
|
|
19
24
|
"publishConfig": {
|
|
20
25
|
"access": "public",
|
|
21
26
|
"provenance": true
|
|
22
27
|
},
|
|
23
28
|
"scripts": {
|
|
24
29
|
"build": "tsup",
|
|
25
|
-
"typecheck": "
|
|
30
|
+
"typecheck": "tsgo --noEmit",
|
|
26
31
|
"prepublishOnly": "bash ../../scripts/prepublish-guard"
|
|
27
32
|
},
|
|
28
33
|
"dependencies": {
|
|
@@ -30,12 +35,7 @@
|
|
|
30
35
|
"@aws-sdk/s3-request-presigner": "^3.1044.0",
|
|
31
36
|
"@azure/storage-blob": "^12.29.1",
|
|
32
37
|
"@google-cloud/storage": "^7.19.0",
|
|
33
|
-
"@opengeni/config": "^0.
|
|
34
|
-
"@opengeni/contracts": "^0.
|
|
35
|
-
},
|
|
36
|
-
"repository": {
|
|
37
|
-
"type": "git",
|
|
38
|
-
"url": "git+https://github.com/Cloudgeni-ai/opengeni.git",
|
|
39
|
-
"directory": "packages/storage"
|
|
38
|
+
"@opengeni/config": "^0.5.0",
|
|
39
|
+
"@opengeni/contracts": "^0.10.0"
|
|
40
40
|
}
|
|
41
41
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,13 +3,13 @@ import type { FileAsset } from "@opengeni/contracts";
|
|
|
3
3
|
import {
|
|
4
4
|
BlobSASPermissions,
|
|
5
5
|
BlobServiceClient,
|
|
6
|
-
BlockBlobClient,
|
|
7
6
|
generateBlobSASQueryParameters,
|
|
8
7
|
StorageSharedKeyCredential,
|
|
9
8
|
type BlobDownloadResponseParsed,
|
|
10
9
|
type BlobGetPropertiesResponse,
|
|
11
10
|
} from "@azure/storage-blob";
|
|
12
11
|
import {
|
|
12
|
+
DeleteObjectCommand,
|
|
13
13
|
GetObjectCommand,
|
|
14
14
|
HeadObjectCommand,
|
|
15
15
|
PutObjectCommand,
|
|
@@ -17,7 +17,11 @@ import {
|
|
|
17
17
|
type S3ClientConfig,
|
|
18
18
|
} from "@aws-sdk/client-s3";
|
|
19
19
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
Storage as GcsClient,
|
|
22
|
+
type GetSignedUrlConfig,
|
|
23
|
+
type StorageOptions,
|
|
24
|
+
} from "@google-cloud/storage";
|
|
21
25
|
|
|
22
26
|
export const MAX_SINGLE_PUT_SIZE_BYTES = 5_000_000_000;
|
|
23
27
|
export const UPLOAD_URL_TTL_SECONDS = 15 * 60;
|
|
@@ -33,10 +37,20 @@ export type ObjectStorage = {
|
|
|
33
37
|
bucket: string;
|
|
34
38
|
backend: "s3-compatible" | "aws-s3" | "azure-blob" | "gcs";
|
|
35
39
|
maxSinglePutSizeBytes: number;
|
|
36
|
-
createPutUrl: (args: {
|
|
37
|
-
|
|
40
|
+
createPutUrl: (args: {
|
|
41
|
+
key: string;
|
|
42
|
+
contentType: string;
|
|
43
|
+
sha256?: string | null;
|
|
44
|
+
expiresInSeconds?: number;
|
|
45
|
+
}) => Promise<{ url: string; requiredHeaders: Record<string, string>; expiresAt: Date }>;
|
|
46
|
+
createGetUrl: (args: {
|
|
47
|
+
key: string;
|
|
48
|
+
expiresInSeconds?: number;
|
|
49
|
+
}) => Promise<{ url: string; expiresAt: Date }>;
|
|
38
50
|
headFile: (file: FileAsset) => Promise<ObjectHead>;
|
|
39
51
|
getFileBytes: (file: FileAsset) => Promise<Uint8Array>;
|
|
52
|
+
/** Fetch an object by raw storage key (not a tracked FileAsset). Returns null on 404/missing. */
|
|
53
|
+
getObjectBytes: (key: string) => Promise<{ bytes: Uint8Array; contentType?: string } | null>;
|
|
40
54
|
/**
|
|
41
55
|
* SERVER-SIDE authenticated direct PUT (no presign + browser fetch). For an
|
|
42
56
|
* in-process upload from a trusted holder of the storage credentials (e.g. the
|
|
@@ -46,7 +60,21 @@ export type ObjectStorage = {
|
|
|
46
60
|
* public `objectStorageEndpoint` with no in-cluster route) would otherwise 401.
|
|
47
61
|
* Browser uploads keep using `createPutUrl`; this is the trusted-server twin.
|
|
48
62
|
*/
|
|
49
|
-
putObject: (args: {
|
|
63
|
+
putObject: (args: {
|
|
64
|
+
key: string;
|
|
65
|
+
contentType: string;
|
|
66
|
+
body: Uint8Array;
|
|
67
|
+
sha256?: string | null;
|
|
68
|
+
}) => Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* SERVER-SIDE authenticated delete of a single object by raw storage key.
|
|
71
|
+
* Idempotent: a missing key is a no-op (S3/GCS/Azure delete-by-key does not
|
|
72
|
+
* 404 on absent objects, or the 404 is swallowed). Used by the Workbench v2
|
|
73
|
+
* capture GC (dossier §10.1 keep-latest-10) to reap after-image blobs that no
|
|
74
|
+
* surviving revision references. Best-effort at the call site — a failed
|
|
75
|
+
* delete leaves an orphan blob, never corrupts a live capture.
|
|
76
|
+
*/
|
|
77
|
+
deleteObject: (key: string) => Promise<void>;
|
|
50
78
|
};
|
|
51
79
|
|
|
52
80
|
export function createObjectStorage(settings: Settings): ObjectStorage | null {
|
|
@@ -60,7 +88,12 @@ export function createObjectStorage(settings: Settings): ObjectStorage | null {
|
|
|
60
88
|
}
|
|
61
89
|
|
|
62
90
|
function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | null {
|
|
63
|
-
if (
|
|
91
|
+
if (
|
|
92
|
+
settings.objectStorageBackend === "s3-compatible" &&
|
|
93
|
+
(!settings.objectStorageEndpoint ||
|
|
94
|
+
!settings.objectStorageAccessKeyId ||
|
|
95
|
+
!settings.objectStorageSecretAccessKey)
|
|
96
|
+
) {
|
|
64
97
|
return null;
|
|
65
98
|
}
|
|
66
99
|
const clientConfig: S3ClientConfig = {
|
|
@@ -68,10 +101,14 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
68
101
|
forcePathStyle: settings.objectStorageForcePathStyle,
|
|
69
102
|
requestChecksumCalculation: "WHEN_REQUIRED",
|
|
70
103
|
...(settings.objectStorageEndpoint ? { endpoint: settings.objectStorageEndpoint } : {}),
|
|
71
|
-
...(settings.objectStorageAccessKeyId && settings.objectStorageSecretAccessKey
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
104
|
+
...(settings.objectStorageAccessKeyId && settings.objectStorageSecretAccessKey
|
|
105
|
+
? {
|
|
106
|
+
credentials: {
|
|
107
|
+
accessKeyId: settings.objectStorageAccessKeyId,
|
|
108
|
+
secretAccessKey: settings.objectStorageSecretAccessKey,
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
: {}),
|
|
75
112
|
};
|
|
76
113
|
const client = new S3Client(clientConfig);
|
|
77
114
|
return {
|
|
@@ -98,10 +135,14 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
98
135
|
async createGetUrl(args) {
|
|
99
136
|
const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;
|
|
100
137
|
return {
|
|
101
|
-
url: await getSignedUrl(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
138
|
+
url: await getSignedUrl(
|
|
139
|
+
client,
|
|
140
|
+
new GetObjectCommand({
|
|
141
|
+
Bucket: settings.objectStorageBucket,
|
|
142
|
+
Key: args.key,
|
|
143
|
+
}),
|
|
144
|
+
{ expiresIn },
|
|
145
|
+
),
|
|
105
146
|
expiresAt: new Date(Date.now() + expiresIn * 1000),
|
|
106
147
|
};
|
|
107
148
|
},
|
|
@@ -110,19 +151,23 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
110
151
|
// A presigned URL buys nothing here — the worker already holds the creds — and
|
|
111
152
|
// on a split public/internal endpoint topology the presigned URL points at the
|
|
112
153
|
// PUBLIC host (no MinIO route → 401). This sends bytes straight to the backend.
|
|
113
|
-
await client.send(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
154
|
+
await client.send(
|
|
155
|
+
new PutObjectCommand({
|
|
156
|
+
Bucket: settings.objectStorageBucket,
|
|
157
|
+
Key: args.key,
|
|
158
|
+
ContentType: args.contentType,
|
|
159
|
+
Body: args.body,
|
|
160
|
+
Metadata: args.sha256 ? { sha256: args.sha256 } : undefined,
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
120
163
|
},
|
|
121
164
|
async headFile(file) {
|
|
122
|
-
const head = await client.send(
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
165
|
+
const head = await client.send(
|
|
166
|
+
new HeadObjectCommand({
|
|
167
|
+
Bucket: file.bucket,
|
|
168
|
+
Key: file.objectKey,
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
126
171
|
return objectHead({
|
|
127
172
|
contentLength: head.ContentLength,
|
|
128
173
|
contentType: head.ContentType,
|
|
@@ -130,25 +175,73 @@ function createS3CompatibleObjectStorage(settings: Settings): ObjectStorage | nu
|
|
|
130
175
|
});
|
|
131
176
|
},
|
|
132
177
|
async getFileBytes(file) {
|
|
133
|
-
const result = await client.send(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
178
|
+
const result = await client.send(
|
|
179
|
+
new GetObjectCommand({
|
|
180
|
+
Bucket: file.bucket,
|
|
181
|
+
Key: file.objectKey,
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
return await s3BodyToBytes(result.Body, file.objectKey);
|
|
185
|
+
},
|
|
186
|
+
async getObjectBytes(key) {
|
|
187
|
+
try {
|
|
188
|
+
const result = await client.send(
|
|
189
|
+
new GetObjectCommand({
|
|
190
|
+
Bucket: settings.objectStorageBucket,
|
|
191
|
+
Key: key,
|
|
192
|
+
}),
|
|
193
|
+
);
|
|
194
|
+
const bytes = await s3BodyToBytes(result.Body, key);
|
|
195
|
+
return { bytes, ...(result.ContentType ? { contentType: result.ContentType } : {}) };
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (isS3NotFound(error)) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
throw error;
|
|
146
201
|
}
|
|
147
|
-
|
|
202
|
+
},
|
|
203
|
+
async deleteObject(key) {
|
|
204
|
+
// S3 DeleteObject is idempotent — deleting an absent key returns 204.
|
|
205
|
+
await client.send(
|
|
206
|
+
new DeleteObjectCommand({
|
|
207
|
+
Bucket: settings.objectStorageBucket,
|
|
208
|
+
Key: key,
|
|
209
|
+
}),
|
|
210
|
+
);
|
|
148
211
|
},
|
|
149
212
|
};
|
|
150
213
|
}
|
|
151
214
|
|
|
215
|
+
async function s3BodyToBytes(body: unknown, objectKey: string): Promise<Uint8Array> {
|
|
216
|
+
if (!body) {
|
|
217
|
+
throw new Error(`Object body is empty: ${objectKey}`);
|
|
218
|
+
}
|
|
219
|
+
const withTransform = body as { transformToByteArray?: () => Promise<Uint8Array> };
|
|
220
|
+
if (typeof withTransform.transformToByteArray === "function") {
|
|
221
|
+
return await withTransform.transformToByteArray();
|
|
222
|
+
}
|
|
223
|
+
const chunks: Uint8Array[] = [];
|
|
224
|
+
for await (const chunk of body as AsyncIterable<Uint8Array | Buffer | string>) {
|
|
225
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
226
|
+
}
|
|
227
|
+
return Buffer.concat(chunks);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function isS3NotFound(error: unknown): boolean {
|
|
231
|
+
if (!error || typeof error !== "object") {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
const name = "name" in error ? (error as { name?: unknown }).name : undefined;
|
|
235
|
+
if (name === "NoSuchKey" || name === "NotFound") {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
const metadata =
|
|
239
|
+
"$metadata" in error
|
|
240
|
+
? (error as { $metadata?: { httpStatusCode?: number } }).$metadata
|
|
241
|
+
: undefined;
|
|
242
|
+
return metadata?.httpStatusCode === 404;
|
|
243
|
+
}
|
|
244
|
+
|
|
152
245
|
function createGcsObjectStorage(settings: Settings): ObjectStorage {
|
|
153
246
|
const client = new GcsClient(gcsClientOptions(settings));
|
|
154
247
|
const bucket = client.bucket(settings.objectStorageBucket);
|
|
@@ -207,9 +300,28 @@ function createGcsObjectStorage(settings: Settings): ObjectStorage {
|
|
|
207
300
|
const [bytes] = await bucket.file(file.objectKey).download();
|
|
208
301
|
return bytes;
|
|
209
302
|
},
|
|
303
|
+
async getObjectBytes(key) {
|
|
304
|
+
try {
|
|
305
|
+
const [bytes] = await bucket.file(key).download();
|
|
306
|
+
return { bytes };
|
|
307
|
+
} catch (error) {
|
|
308
|
+
if (isGcsNotFound(error)) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
async deleteObject(key) {
|
|
315
|
+
// ignoreNotFound keeps the delete idempotent (a missing blob is a no-op).
|
|
316
|
+
await bucket.file(key).delete({ ignoreNotFound: true });
|
|
317
|
+
},
|
|
210
318
|
};
|
|
211
319
|
}
|
|
212
320
|
|
|
321
|
+
function isGcsNotFound(error: unknown): boolean {
|
|
322
|
+
return Boolean(error) && typeof error === "object" && (error as { code?: unknown }).code === 404;
|
|
323
|
+
}
|
|
324
|
+
|
|
213
325
|
function createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null {
|
|
214
326
|
const sharedKey = azureSharedKeyCredential(settings);
|
|
215
327
|
const serviceClient = settings.objectStorageAzureConnectionString
|
|
@@ -225,13 +337,16 @@ function createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null
|
|
|
225
337
|
const expiresIn = args.expiresInSeconds ?? UPLOAD_URL_TTL_SECONDS;
|
|
226
338
|
const expiresAt = new Date(Date.now() + expiresIn * 1000);
|
|
227
339
|
const blobClient = containerClient.getBlockBlobClient(args.key);
|
|
228
|
-
const sas = generateBlobSASQueryParameters(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
340
|
+
const sas = generateBlobSASQueryParameters(
|
|
341
|
+
{
|
|
342
|
+
containerName: settings.objectStorageBucket,
|
|
343
|
+
blobName: args.key,
|
|
344
|
+
permissions: BlobSASPermissions.parse("cw"),
|
|
345
|
+
expiresOn: expiresAt,
|
|
346
|
+
contentType: args.contentType,
|
|
347
|
+
},
|
|
348
|
+
sharedKey,
|
|
349
|
+
).toString();
|
|
235
350
|
return {
|
|
236
351
|
url: `${blobClient.url}?${sas}`,
|
|
237
352
|
requiredHeaders: {
|
|
@@ -246,12 +361,15 @@ function createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null
|
|
|
246
361
|
const expiresIn = args.expiresInSeconds ?? DOWNLOAD_URL_TTL_SECONDS;
|
|
247
362
|
const expiresAt = new Date(Date.now() + expiresIn * 1000);
|
|
248
363
|
const blobClient = containerClient.getBlobClient(args.key);
|
|
249
|
-
const sas = generateBlobSASQueryParameters(
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
364
|
+
const sas = generateBlobSASQueryParameters(
|
|
365
|
+
{
|
|
366
|
+
containerName: settings.objectStorageBucket,
|
|
367
|
+
blobName: args.key,
|
|
368
|
+
permissions: BlobSASPermissions.parse("r"),
|
|
369
|
+
expiresOn: expiresAt,
|
|
370
|
+
},
|
|
371
|
+
sharedKey,
|
|
372
|
+
).toString();
|
|
255
373
|
return {
|
|
256
374
|
url: `${blobClient.url}?${sas}`,
|
|
257
375
|
expiresAt,
|
|
@@ -267,26 +385,59 @@ function createAzureBlobObjectStorage(settings: Settings): ObjectStorage | null
|
|
|
267
385
|
});
|
|
268
386
|
},
|
|
269
387
|
async headFile(file) {
|
|
270
|
-
return azureHeadToObjectHead(
|
|
388
|
+
return azureHeadToObjectHead(
|
|
389
|
+
await containerClient.getBlobClient(file.objectKey).getProperties(),
|
|
390
|
+
);
|
|
271
391
|
},
|
|
272
392
|
async getFileBytes(file) {
|
|
273
|
-
return await azureDownloadToBytes(
|
|
393
|
+
return await azureDownloadToBytes(
|
|
394
|
+
await containerClient.getBlobClient(file.objectKey).download(),
|
|
395
|
+
);
|
|
396
|
+
},
|
|
397
|
+
async getObjectBytes(key) {
|
|
398
|
+
try {
|
|
399
|
+
const download = await containerClient.getBlobClient(key).download();
|
|
400
|
+
const bytes = await azureDownloadToBytes(download);
|
|
401
|
+
return { bytes, ...(download.contentType ? { contentType: download.contentType } : {}) };
|
|
402
|
+
} catch (error) {
|
|
403
|
+
if (isAzureNotFound(error)) {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
async deleteObject(key) {
|
|
410
|
+
// deleteIfExists keeps the delete idempotent (a missing blob is a no-op).
|
|
411
|
+
await containerClient.getBlockBlobClient(key).deleteIfExists();
|
|
274
412
|
},
|
|
275
413
|
};
|
|
276
414
|
}
|
|
277
415
|
|
|
416
|
+
function isAzureNotFound(error: unknown): boolean {
|
|
417
|
+
return (
|
|
418
|
+
Boolean(error) &&
|
|
419
|
+
typeof error === "object" &&
|
|
420
|
+
(error as { statusCode?: unknown }).statusCode === 404
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
278
424
|
function azureSharedKeyCredential(settings: Settings): StorageSharedKeyCredential {
|
|
279
425
|
if (settings.objectStorageAzureConnectionString) {
|
|
280
426
|
const parsed = parseConnectionString(settings.objectStorageAzureConnectionString);
|
|
281
427
|
if (parsed.AccountName && parsed.AccountKey) {
|
|
282
428
|
return new StorageSharedKeyCredential(parsed.AccountName, parsed.AccountKey);
|
|
283
429
|
}
|
|
284
|
-
throw new Error(
|
|
430
|
+
throw new Error(
|
|
431
|
+
"Azure Blob connection string must include AccountName and AccountKey to create presigned URLs",
|
|
432
|
+
);
|
|
285
433
|
}
|
|
286
434
|
if (!settings.objectStorageAzureAccountName || !settings.objectStorageAzureAccountKey) {
|
|
287
435
|
throw new Error("Azure Blob storage requires account name and account key");
|
|
288
436
|
}
|
|
289
|
-
return new StorageSharedKeyCredential(
|
|
437
|
+
return new StorageSharedKeyCredential(
|
|
438
|
+
settings.objectStorageAzureAccountName,
|
|
439
|
+
settings.objectStorageAzureAccountKey,
|
|
440
|
+
);
|
|
290
441
|
}
|
|
291
442
|
|
|
292
443
|
function azureBlobServiceUrl(settings: Settings): string {
|
|
@@ -300,20 +451,29 @@ function azureBlobServiceUrl(settings: Settings): string {
|
|
|
300
451
|
}
|
|
301
452
|
|
|
302
453
|
function parseConnectionString(value: string): Record<string, string> {
|
|
303
|
-
return Object.fromEntries(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
454
|
+
return Object.fromEntries(
|
|
455
|
+
value
|
|
456
|
+
.split(";")
|
|
457
|
+
.map((part) => part.trim())
|
|
458
|
+
.filter(Boolean)
|
|
459
|
+
.map((part) => {
|
|
460
|
+
const index = part.indexOf("=");
|
|
461
|
+
return index === -1 ? [part, ""] : [part.slice(0, index), part.slice(index + 1)];
|
|
462
|
+
}),
|
|
463
|
+
);
|
|
310
464
|
}
|
|
311
465
|
|
|
312
466
|
function gcsClientOptions(settings: Settings): StorageOptions {
|
|
313
467
|
const options: StorageOptions = {
|
|
314
|
-
...(settings.objectStorageGcsProjectId
|
|
315
|
-
|
|
316
|
-
|
|
468
|
+
...(settings.objectStorageGcsProjectId
|
|
469
|
+
? { projectId: settings.objectStorageGcsProjectId }
|
|
470
|
+
: {}),
|
|
471
|
+
...(settings.objectStorageGcsKeyFilename
|
|
472
|
+
? { keyFilename: settings.objectStorageGcsKeyFilename }
|
|
473
|
+
: {}),
|
|
474
|
+
...(settings.objectStorageGcsApiEndpoint
|
|
475
|
+
? { apiEndpoint: settings.objectStorageGcsApiEndpoint }
|
|
476
|
+
: {}),
|
|
317
477
|
};
|
|
318
478
|
if (settings.objectStorageGcsCredentialsJson) {
|
|
319
479
|
options.credentials = parseGcsCredentials(settings.objectStorageGcsCredentialsJson);
|
|
@@ -340,13 +500,16 @@ function parseContentLength(value: string | number | undefined): number | undefi
|
|
|
340
500
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
341
501
|
}
|
|
342
502
|
|
|
343
|
-
function stringMetadata(
|
|
503
|
+
function stringMetadata(
|
|
504
|
+
value: Record<string, string | number | boolean | null> | undefined,
|
|
505
|
+
): Record<string, string> | undefined {
|
|
344
506
|
if (!value) {
|
|
345
507
|
return undefined;
|
|
346
508
|
}
|
|
347
509
|
return Object.fromEntries(
|
|
348
|
-
Object.entries(value)
|
|
349
|
-
|
|
510
|
+
Object.entries(value).filter(
|
|
511
|
+
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
512
|
+
),
|
|
350
513
|
);
|
|
351
514
|
}
|
|
352
515
|
|
|
@@ -363,7 +526,9 @@ async function azureDownloadToBytes(download: BlobDownloadResponseParsed): Promi
|
|
|
363
526
|
throw new Error("Azure Blob download response did not include a readable body");
|
|
364
527
|
}
|
|
365
528
|
const chunks: Uint8Array[] = [];
|
|
366
|
-
for await (const chunk of download.readableStreamBody as AsyncIterable<
|
|
529
|
+
for await (const chunk of download.readableStreamBody as AsyncIterable<
|
|
530
|
+
Uint8Array | Buffer | string
|
|
531
|
+
>) {
|
|
367
532
|
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
368
533
|
}
|
|
369
534
|
return Buffer.concat(chunks);
|