UNPKG

@flystorage/azure-storage-blob

Version:

<img src="https://raw.githubusercontent.com/duna-oss/flystorage/main/flystorage.svg" width="50px" height="50px" />

244 lines (243 loc) 9.04 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AzureStorageBlobFileStorage = exports.AzureStorageBlobStorageAdapter = void 0; const file_storage_1 = require("@flystorage/file-storage"); const storage_blob_1 = require("@azure/storage-blob"); const stream_mime_type_1 = require("@flystorage/stream-mime-type"); const node_path_1 = require("node:path"); class AzureStorageBlobStorageAdapter { container; options; prefixer; constructor(container, options = {}) { this.container = container; this.options = options; this.prefixer = new file_storage_1.PathPrefixer(options.prefix || ''); } async copyFile(from, to, options) { const fromUrl = this.blockClient(from).url; await this.blockClient(to).syncCopyFromURL(fromUrl); } async moveFile(from, to, options) { await this.copyFile(from, to, options); await this.deleteFile(from); } async write(path, contents, options) { let mimeType = options.mimeType; let stream = contents; if (mimeType === undefined) { [mimeType, stream] = await this.resolveMimetype(path, contents, options); } const blob = this.blockClient(path); await blob.uploadStream(stream, options.size, this.options.uploadMaxConcurrency, { blobHTTPHeaders: { blobContentType: mimeType, blobCacheControl: options.cacheControl }, }); } blockClient(path) { return this.container.getBlockBlobClient(this.prefixer.prefixFilePath(path)); } async read(path) { const blob = this.blockClient(path); const response = await blob.download(); if (!response.readableStreamBody) { throw new Error('No readable stream body in response.'); } return response.readableStreamBody; } async deleteFile(path) { const blob = this.blockClient(path); await blob.deleteIfExists(); } async createDirectory() { // no-op, directories do not exist. } async stat(path) { const blob = this.blockClient(path); const properties = await blob.getProperties(); return this.mapToStatEntry(path, properties); } mapToStatEntry(path, properties) { return { type: 'file', isFile: true, isDirectory: false, path, mimeType: properties.contentType, size: properties.contentLength, lastModifiedMs: properties.lastModified?.getTime(), }; } list(path, options) { return options.deep ? this.listDeep(path, options) : this.listShallow(path, options); } async *listDeep(path, options) { const directories = new Set(); const listing = this.container.listBlobsFlat({ prefix: this.prefixer.prefixDirectoryPath(path), }); const listedPath = path; for await (const item of listing) { const path = this.prefixer.stripFilePath(item.name); let parentDir = (0, node_path_1.dirname)(path); while (!['.', '', listedPath].includes(parentDir)) { if (directories.has(parentDir)) { break; } yield { type: 'directory', isFile: false, isDirectory: true, path: parentDir, }; directories.add(parentDir); parentDir = (0, node_path_1.dirname)(parentDir); } yield this.mapToStatEntry(path, item.properties); } } async *listShallow(path, options) { const listing = this.container.listBlobsByHierarchy('/', { prefix: this.prefixer.prefixDirectoryPath(path), }); for await (const item of listing) { if (item.kind === 'blob') { yield this.mapToStatEntry(this.prefixer.stripFilePath(item.name), item.properties); } else { yield { path: this.prefixer.stripDirectoryPath(item.name), type: 'directory', isFile: false, isDirectory: true, }; } } } async changeVisibility(path, visibility) { if (this.options.ignoreVisibility !== true) { throw new Error('Not supported by this adapter'); } } async visibility(path) { if (this.options.ignoreVisibility !== true) { throw new Error('Not implemented'); } // default to indicating it ss public because we cannot know if the default is private return this.options.ignoredVisibilityResponse ?? 'public'; } async deleteDirectory(path) { let deletes = []; const batchSize = this.options.deleteDirBatchSize ?? 10; for await (const item of this.list(path, { deep: true })) { if (item.isFile) { deletes.push(this.deleteFile(item.path)); } if (deletes.length >= batchSize) { await Promise.all(deletes); deletes = []; } } await Promise.all(deletes); } async fileExists(path) { return await this.blockClient(path).exists(); } async directoryExists(path) { const listing = this.container.listBlobsFlat({ prefix: this.prefixer.prefixDirectoryPath(path), }).byPage({ maxPageSize: 1, }); return (await listing.next()).value.segment.blobItems.length > 0; } async publicUrl(path, options) { return this.blockClient(path).url; } async temporaryUrl(path, options) { return await this.blockClient(path).generateSasUrl({ expiresOn: (0, file_storage_1.normalizeExpiryToDate)(options.expiresAt), permissions: storage_blob_1.BlobSASPermissions.parse('r'), ...(this.options.temporaryUrlOptions ?? {}), }); } async prepareUpload(path, options) { const headers = {}; headers['x-ms-blob-type'] = options['x-ms-blob-type'] ?? 'BlockBlob'; const config = { expiresOn: (0, file_storage_1.normalizeExpiryToDate)(options.expiresAt), permissions: storage_blob_1.BlobSASPermissions.parse('w'), ...(this.options.temporaryUrlOptions ?? {}), }; const contentType = options['Content-Type'] ?? options.contentType; if (typeof contentType === 'string') { config.contentType = contentType; headers['Content-Type'] = contentType; } const url = await this.blockClient(path).generateSasUrl(config); return { method: 'PUT', provider: 'azure-storage-blob', url, headers }; } async checksum(path, options) { const algo = options.algo ?? 'etag'; if (algo !== 'etag') { throw file_storage_1.ChecksumIsNotAvailable.checksumNotSupported(algo); } const blob = this.blockClient(path); const properties = await blob.getProperties(); const etag = properties.etag; if (etag === undefined) { throw new Error('Etag is not defined on blob properties.'); } return etag; } async mimeType(path, options) { const stat = await this.stat(path); if (stat.isDirectory) { throw new Error('Path is not a file. No mimetype available.'); } if (stat.mimeType === undefined) { throw new Error('Mime-type not found for file.'); } return stat.mimeType; } async lastModified(path) { const stat = await this.stat(path); if (stat.isDirectory) { throw new Error('Path is not a file. No last modified available.'); } if (stat.lastModifiedMs === undefined) { throw new Error('Last modified not found for file.'); } return stat.lastModifiedMs; } async fileSize(path) { const stat = await this.stat(path); if (stat.isDirectory) { throw new Error('Path is not a file. No file size available.'); } if (stat.size === undefined) { throw new Error('File size not found for file.'); } return stat.size; } async resolveMimetype(path, contents, options) { if (options.mimeType) { return [options.mimeType, contents]; } const [mimeType, stream] = await (0, stream_mime_type_1.resolveMimeType)(path, contents); return [mimeType ?? 'application/octet-stream', stream]; } } exports.AzureStorageBlobStorageAdapter = AzureStorageBlobStorageAdapter; /** * BC export * * @deprecated */ class AzureStorageBlobFileStorage extends AzureStorageBlobStorageAdapter { } exports.AzureStorageBlobFileStorage = AzureStorageBlobFileStorage;