11import { USE_BLOB_STORAGE , USE_GCS_STORAGE , USE_S3_STORAGE } from '@/lib/uploads/config'
2- import { hasObjectNotFoundLabel } from '@/lib/uploads/core/errors'
32import type { StorageConfig } from '@/lib/uploads/shared/types'
43
54export type { StorageConfig } from '@/lib/uploads/shared/types'
@@ -30,29 +29,6 @@ export function getServePathPrefix(): string {
3029export async function getFileMetadata (
3130 key : string ,
3231 customConfig ?: StorageConfig
33- ) : Promise < Record < string , string > > {
34- try {
35- return await readProviderMetadata ( key , customConfig )
36- } catch ( error ) {
37- /**
38- * A key that no longer resolves is an ordinary outcome — a workspace file is
39- * rewritten under a new key on every content update, so any reader holding the
40- * previous key finds nothing. Report it the way this function already reports
41- * "nothing known about this key" rather than as a failure, so callers fall
42- * through to their own not-found handling instead of an error path.
43- *
44- * Deliberately the labelled check: this dispatches across every provider and so
45- * cannot attribute a bare 404 to the object rather than its bucket. An
46- * unlabelled 404 keeps propagating, exactly as it did before.
47- */
48- if ( hasObjectNotFoundLabel ( error ) ) return { }
49- throw error
50- }
51- }
52-
53- async function readProviderMetadata (
54- key : string ,
55- customConfig ?: StorageConfig
5632) : Promise < Record < string , string > > {
5733 const { getFileMetadataByKey } = await import ( '../server/metadata' )
5834 const metadataRecord = await getFileMetadataByKey ( key )
@@ -68,57 +44,46 @@ async function readProviderMetadata(
6844 }
6945
7046 if ( USE_BLOB_STORAGE ) {
71- const { getBlobServiceClient } = await import ( '@/lib/uploads/providers/blob/client' )
47+ const { headBlobObject } = await import ( '@/lib/uploads/providers/blob/client' )
7248 const { BLOB_CONFIG } = await import ( '@/lib/uploads/config' )
73-
74- let blobServiceClient = await getBlobServiceClient ( )
75- let containerName = BLOB_CONFIG . containerName
76-
77- if ( customConfig ) {
78- const { BlobServiceClient, StorageSharedKeyCredential } = await import ( '@azure/storage-blob' )
79- if ( customConfig . connectionString ) {
80- blobServiceClient = BlobServiceClient . fromConnectionString ( customConfig . connectionString )
81- } else if ( customConfig . accountName && customConfig . accountKey ) {
82- const credential = new StorageSharedKeyCredential (
83- customConfig . accountName ,
84- customConfig . accountKey
85- )
86- blobServiceClient = new BlobServiceClient (
87- `https://${ customConfig . accountName } .blob.core.windows.net` ,
88- credential
89- )
90- }
91- containerName = customConfig . containerName || containerName
92- }
93-
94- const containerClient = blobServiceClient . getContainerClient ( containerName )
95- const blockBlobClient = containerClient . getBlockBlobClient ( key )
96- const properties = await blockBlobClient . getProperties ( )
97- return properties . metadata || { }
49+ /** `headBlobObject` rejects a config that names no credentials, so only pass one that does. */
50+ const credentialed = Boolean (
51+ customConfig ?. connectionString || ( customConfig ?. accountName && customConfig ?. accountKey )
52+ )
53+ const object = await headBlobObject (
54+ key ,
55+ credentialed
56+ ? {
57+ ...customConfig ,
58+ containerName : customConfig ?. containerName || BLOB_CONFIG . containerName ,
59+ }
60+ : undefined
61+ )
62+ return object ?. metadata || { }
9863 }
9964
10065 if ( USE_S3_STORAGE ) {
101- const { getS3Client } = await import ( '@/lib/uploads/providers/s3/client' )
102- const { HeadObjectCommand } = await import ( '@aws-sdk/client-s3' )
66+ const { headS3Object } = await import ( '@/lib/uploads/providers/s3/client' )
10367 const { S3_CONFIG } = await import ( '@/lib/uploads/config' )
104-
105- const s3Client = getS3Client ( )
10668 const bucket = customConfig ?. bucket || S3_CONFIG . bucket
10769
10870 if ( ! bucket ) {
10971 throw new Error ( 'S3 bucket not configured' )
11072 }
11173
112- const command = new HeadObjectCommand ( {
113- Bucket : bucket ,
114- Key : key ,
74+ const object = await headS3Object ( key , {
75+ bucket,
76+ region : customConfig ?. region || S3_CONFIG . region ,
11577 } )
116-
117- const response = await s3Client . send ( command )
118- return response . Metadata || { }
78+ return object ?. metadata || { }
11979 }
12080
12181 if ( USE_GCS_STORAGE ) {
82+ /**
83+ * Unlike the other two, this raises on a missing object rather than reporting
84+ * absence, because GCS answers a missing object and a missing bucket the same
85+ * way and only the caller's own bucket configuration separates them.
86+ */
12287 const { getGcsObjectMetadata } = await import ( '@/lib/uploads/providers/gcs/client' )
12388 return getGcsObjectMetadata (
12489 key ,
0 commit comments