@storybooker/azure
Version:
StoryBooker Adapter for interacting with Azure services.
1 lines • 8.57 kB
Source Map (JSON)
{"version":3,"file":"blob-storage.cjs","names":["#client","containers: string[]","blobClientsToDelete: BlobClient[]","Readable"],"sources":["../src/blob-storage.ts"],"sourcesContent":["import { Readable } from \"node:stream\";\nimport type streamWeb from \"node:stream/web\";\nimport type {\n BlobClient,\n BlobServiceClient,\n BlockBlobClient,\n} from \"@azure/storage-blob\";\nimport type { StorageService } from \"@storybooker/core/types\";\n\nexport class AzureBlobStorageService implements StorageService {\n #client: BlobServiceClient;\n\n constructor(client: BlobServiceClient) {\n this.#client = client;\n }\n\n createContainer: StorageService[\"createContainer\"] = async (\n containerId,\n options,\n ) => {\n const containerName = genContainerNameFromContainerId(containerId);\n await this.#client.createContainer(containerName, {\n abortSignal: options.abortSignal,\n });\n };\n\n deleteContainer: StorageService[\"deleteContainer\"] = async (\n containerId,\n options,\n ) => {\n const containerName = genContainerNameFromContainerId(containerId);\n await this.#client.getContainerClient(containerName).deleteIfExists({\n abortSignal: options.abortSignal,\n });\n };\n\n hasContainer: StorageService[\"hasContainer\"] = async (\n containerId,\n options,\n ) => {\n const containerName = genContainerNameFromContainerId(containerId);\n return await this.#client.getContainerClient(containerName).exists({\n abortSignal: options.abortSignal,\n });\n };\n\n listContainers: StorageService[\"listContainers\"] = async (options) => {\n const containers: string[] = [];\n for await (const item of this.#client.listContainers({\n abortSignal: options.abortSignal,\n })) {\n containers.push(item.name);\n }\n\n return containers;\n };\n\n deleteFiles: StorageService[\"deleteFiles\"] = async (\n containerId,\n filePathsOrPrefix,\n options,\n ) => {\n const containerName = genContainerNameFromContainerId(containerId);\n const containerClient = this.#client.getContainerClient(containerName);\n const blobClientsToDelete: BlobClient[] = [];\n\n if (typeof filePathsOrPrefix === \"string\") {\n for await (const blob of containerClient.listBlobsFlat({\n abortSignal: options.abortSignal,\n prefix: filePathsOrPrefix,\n })) {\n blobClientsToDelete.push(containerClient.getBlobClient(blob.name));\n }\n } else {\n for (const filepath of filePathsOrPrefix) {\n blobClientsToDelete.push(containerClient.getBlobClient(filepath));\n }\n }\n\n if (blobClientsToDelete.length === 0) {\n return;\n }\n\n const response = await containerClient\n .getBlobBatchClient()\n .deleteBlobs(blobClientsToDelete, {\n abortSignal: options.abortSignal,\n });\n\n if (response.errorCode) {\n throw new Error(`Failed to delete blobs: ${response.errorCode}`);\n }\n return;\n };\n\n uploadFiles: StorageService[\"uploadFiles\"] = async (\n containerId,\n files,\n options,\n ) => {\n const containerName = genContainerNameFromContainerId(containerId);\n const containerClient = this.#client.getContainerClient(containerName);\n // oxlint-disable-next-line require-await\n const promises = files.map(async ({ content, path, mimeType }) =>\n uploadFileToBlobStorage(\n containerClient.getBlockBlobClient(path),\n content,\n mimeType,\n options.abortSignal,\n ),\n );\n\n await Promise.allSettled(promises);\n };\n\n hasFile: StorageService[\"hasFile\"] = async (\n containerId,\n filepath,\n options,\n ) => {\n const containerName = genContainerNameFromContainerId(containerId);\n const containerClient = this.#client.getContainerClient(containerName);\n const blockBlobClient = containerClient.getBlockBlobClient(filepath);\n return await blockBlobClient.exists({ abortSignal: options.abortSignal });\n };\n\n downloadFile: StorageService[\"downloadFile\"] = async (\n containerId,\n filepath,\n options,\n ) => {\n const containerName = genContainerNameFromContainerId(containerId);\n const containerClient = this.#client.getContainerClient(containerName);\n const blockBlobClient = containerClient.getBlockBlobClient(filepath);\n\n if (!(await blockBlobClient.exists())) {\n throw new Error(\n `File '${filepath}' not found in container '${containerId}'.`,\n );\n }\n\n const downloadResponse = await blockBlobClient.download(0, undefined, {\n abortSignal: options.abortSignal,\n });\n\n if (!downloadResponse.readableStreamBody) {\n throw new Error(\n `File '${filepath}' in container '${containerId}' is not downloadable.`,\n );\n }\n\n return {\n content: downloadResponse.readableStreamBody as unknown as ReadableStream,\n mimeType: downloadResponse.contentType,\n path: filepath,\n };\n };\n}\n\nfunction genContainerNameFromContainerId(containerId: string): string {\n return containerId\n .replaceAll(/[^\\w-]+/g, \"-\")\n .slice(0, 255)\n .toLowerCase();\n}\n\n// oxlint-disable-next-line max-params\nasync function uploadFileToBlobStorage(\n client: BlockBlobClient,\n data: Blob | string | ReadableStream,\n mimeType: string,\n abortSignal?: AbortSignal,\n): Promise<void> {\n if (typeof data === \"string\") {\n const blob = new Blob([data], { type: mimeType });\n await client.uploadData(blob, {\n abortSignal,\n blobHTTPHeaders: { blobContentType: mimeType },\n });\n return;\n }\n\n if (data instanceof Blob) {\n await client.uploadData(data, {\n abortSignal,\n blobHTTPHeaders: { blobContentType: mimeType },\n });\n return;\n }\n\n if (data instanceof ReadableStream) {\n const stream = data as unknown as streamWeb.ReadableStream;\n await client.uploadStream(Readable.fromWeb(stream), undefined, undefined, {\n abortSignal,\n blobHTTPHeaders: { blobContentType: mimeType },\n });\n return;\n }\n\n throw new Error(`Unknown file type`);\n}\n"],"mappings":";;;;;AASA,IAAa,0BAAb,MAA+D;CAC7D;CAEA,YAAY,QAA2B;yBAIc,OACnD,aACA,YACG;GACH,MAAM,gBAAgB,gCAAgC,YAAY;AAClE,SAAM,MAAKA,OAAQ,gBAAgB,eAAe,EAChD,aAAa,QAAQ,aACtB,CAAC;;yBAGiD,OACnD,aACA,YACG;GACH,MAAM,gBAAgB,gCAAgC,YAAY;AAClE,SAAM,MAAKA,OAAQ,mBAAmB,cAAc,CAAC,eAAe,EAClE,aAAa,QAAQ,aACtB,CAAC;;sBAG2C,OAC7C,aACA,YACG;GACH,MAAM,gBAAgB,gCAAgC,YAAY;AAClE,UAAO,MAAM,MAAKA,OAAQ,mBAAmB,cAAc,CAAC,OAAO,EACjE,aAAa,QAAQ,aACtB,CAAC;;wBAG+C,OAAO,YAAY;GACpE,MAAMC,aAAuB,EAAE;AAC/B,cAAW,MAAM,QAAQ,MAAKD,OAAQ,eAAe,EACnD,aAAa,QAAQ,aACtB,CAAC,CACA,YAAW,KAAK,KAAK,KAAK;AAG5B,UAAO;;qBAGoC,OAC3C,aACA,mBACA,YACG;GACH,MAAM,gBAAgB,gCAAgC,YAAY;GAClE,MAAM,kBAAkB,MAAKA,OAAQ,mBAAmB,cAAc;GACtE,MAAME,sBAAoC,EAAE;AAE5C,OAAI,OAAO,sBAAsB,SAC/B,YAAW,MAAM,QAAQ,gBAAgB,cAAc;IACrD,aAAa,QAAQ;IACrB,QAAQ;IACT,CAAC,CACA,qBAAoB,KAAK,gBAAgB,cAAc,KAAK,KAAK,CAAC;OAGpE,MAAK,MAAM,YAAY,kBACrB,qBAAoB,KAAK,gBAAgB,cAAc,SAAS,CAAC;AAIrE,OAAI,oBAAoB,WAAW,EACjC;GAGF,MAAM,WAAW,MAAM,gBACpB,oBAAoB,CACpB,YAAY,qBAAqB,EAChC,aAAa,QAAQ,aACtB,CAAC;AAEJ,OAAI,SAAS,UACX,OAAM,IAAI,MAAM,2BAA2B,SAAS,YAAY;;qBAKvB,OAC3C,aACA,OACA,YACG;GACH,MAAM,gBAAgB,gCAAgC,YAAY;GAClE,MAAM,kBAAkB,MAAKF,OAAQ,mBAAmB,cAAc;GAEtE,MAAM,WAAW,MAAM,IAAI,OAAO,EAAE,SAAS,MAAM,eACjD,wBACE,gBAAgB,mBAAmB,KAAK,EACxC,SACA,UACA,QAAQ,YACT,CACF;AAED,SAAM,QAAQ,WAAW,SAAS;;iBAGC,OACnC,aACA,UACA,YACG;GACH,MAAM,gBAAgB,gCAAgC,YAAY;AAGlE,UAAO,MAFiB,MAAKA,OAAQ,mBAAmB,cAAc,CAC9B,mBAAmB,SAAS,CACvC,OAAO,EAAE,aAAa,QAAQ,aAAa,CAAC;;sBAG5B,OAC7C,aACA,UACA,YACG;GACH,MAAM,gBAAgB,gCAAgC,YAAY;GAElE,MAAM,kBADkB,MAAKA,OAAQ,mBAAmB,cAAc,CAC9B,mBAAmB,SAAS;AAEpE,OAAI,CAAE,MAAM,gBAAgB,QAAQ,CAClC,OAAM,IAAI,MACR,SAAS,SAAS,4BAA4B,YAAY,IAC3D;GAGH,MAAM,mBAAmB,MAAM,gBAAgB,SAAS,GAAG,QAAW,EACpE,aAAa,QAAQ,aACtB,CAAC;AAEF,OAAI,CAAC,iBAAiB,mBACpB,OAAM,IAAI,MACR,SAAS,SAAS,kBAAkB,YAAY,wBACjD;AAGH,UAAO;IACL,SAAS,iBAAiB;IAC1B,UAAU,iBAAiB;IAC3B,MAAM;IACP;;AA9ID,QAAKA,SAAU;;;AAkJnB,SAAS,gCAAgC,aAA6B;AACpE,QAAO,YACJ,WAAW,YAAY,IAAI,CAC3B,MAAM,GAAG,IAAI,CACb,aAAa;;AAIlB,eAAe,wBACb,QACA,MACA,UACA,aACe;AACf,KAAI,OAAO,SAAS,UAAU;EAC5B,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,EAAE,EAAE,MAAM,UAAU,CAAC;AACjD,QAAM,OAAO,WAAW,MAAM;GAC5B;GACA,iBAAiB,EAAE,iBAAiB,UAAU;GAC/C,CAAC;AACF;;AAGF,KAAI,gBAAgB,MAAM;AACxB,QAAM,OAAO,WAAW,MAAM;GAC5B;GACA,iBAAiB,EAAE,iBAAiB,UAAU;GAC/C,CAAC;AACF;;AAGF,KAAI,gBAAgB,gBAAgB;EAClC,MAAM,SAAS;AACf,QAAM,OAAO,aAAaG,qBAAS,QAAQ,OAAO,EAAE,QAAW,QAAW;GACxE;GACA,iBAAiB,EAAE,iBAAiB,UAAU;GAC/C,CAAC;AACF;;AAGF,OAAM,IAAI,MAAM,oBAAoB"}