UNPKG

@jsvfs/adapter-azure-blob

Version:
231 lines 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AzureBlobAdapter = void 0; const extras_1 = require("@jsvfs/extras"); const storage_blob_1 = require("@azure/storage-blob"); const helpers_1 = require("./helpers"); /** An adapter for Azure Storage Blobs. */ class AzureBlobAdapter { /** Creates an instance of Azure blob adapter. */ constructor(opts) { const { access } = opts; if (typeof access === 'undefined') throw new Error("Option 'access' cannot be undefined."); if ('connectionString' in access) { this.blobService = storage_blob_1.BlobServiceClient.fromConnectionString(access.connectionString); } else if ('storageAccount' in access && 'storageKey' in access) { this.blobService = new storage_blob_1.BlobServiceClient(`https://${access.storageAccount}.blob.core.windows.net`, new storage_blob_1.StorageSharedKeyCredential(access.storageAccount, access.storageKey)); } else { throw new Error("Option 'access' does not contain either a connection string or an account and key."); } if (helpers_1.isContainerName(opts.container)) { this.root = opts.container; } else { this.root = '/'; } this.containerCache = new Map(); this.include = new extras_1.Matcher(Array.isArray(opts.include) ? opts.include : []); this.flushEnabled = opts.flushEnabled ?? false; this.createIfNotExist = opts.createIfNotExist ?? false; this.handle = 'azure-blob'; this.journal = new extras_1.Journal(); } /** Returns true if root is the storage account. */ get isGlobal() { return this.root === '/'; } /** Snapshot of the underlying file system; an asynchronous iterable which returns an entry of path and data. * @returns {AsyncGenerator<[string, SnapshotEntry]>} The asynchronous iterable to get the snapshot. */ async *snapshot() { for await (const [name, client] of this.listContainers('snapshot')) { try { for await (const blobItem of client.listBlobsFlat()) { const contents = await this.readBlob(client, blobItem.name, 'snapshot'); const snapshotName = this.isGlobal ? '/' + name + '/' + blobItem.name : '/' + blobItem.name; if (this.include.match(snapshotName)) { yield [snapshotName, { type: 'file', contents }]; } } } catch (error) { this.journal.push({ level: 'error', message: `Could not list blobs in container '${name}'.`, op: 'snapshot', error }); } } } /** Read a file from persistent storage. */ async read(path) { const parsed = helpers_1.parse(path, this.root); try { return await this.readBlob(parsed.container, parsed.blobName, 'read'); } catch (error) { this.journal.push({ level: 'error', message: `Could not get contents of blob '${parsed.blobName}' in container '${parsed.container}'.`, op: 'read', error }); return Buffer.alloc(0); } } /** Create a file or write the contents of a file to persistent storage. */ async write(path, contents = Buffer.alloc(0)) { const parsed = helpers_1.parse(path, this.root); const container = await this.getContainer(parsed.container, 'write'); const blobClient = container.getBlockBlobClient(parsed.blobName); try { await blobClient.uploadData(contents); } catch (error) { this.journal.push({ level: 'error', message: `Could not upload blob '${parsed.blobName}' to container '${parsed.container}'.`, op: 'write', error }); } } /** Make a directory or directory tree in persistent storage. Technically unsupported by Microsoft, as 'directories' are virtual. */ async mkdir(path) { } /** Create a link in persistent storage. Definitely unsupported by Microsoft, so we copy the file contents from an existing blob. */ async link(linkPath, linkTarget, type) { const parsedPath = helpers_1.parse(linkPath, this.root); const parsedTarget = helpers_1.parse(linkTarget, this.root); const containerFrom = await this.getContainer(parsedTarget.container, 'link'); const containerTo = await this.getContainer(parsedPath.container, 'link'); const blobFrom = containerFrom.getBlockBlobClient(parsedTarget.blobName); const blobTo = containerTo.getBlockBlobClient(parsedPath.blobName); try { await blobTo.syncCopyFromURL(blobFrom.url); } catch (error) { this.journal.push({ level: 'error', message: `Could not copy blob to '${parsedPath.blobName}' in container '${parsedPath.container}' from '${parsedTarget.blobName}' in container '${parsedTarget.container}'.`, op: 'link', error }); } } /** Remove items from persistent storage. */ async remove(path, type) { const parsed = helpers_1.parse(path, this.root); const container = await this.getContainer(parsed.container, 'remove'); const blobClient = container.getBlockBlobClient(parsed.blobName); switch (type) { case 'file': case 'hardlink': case 'softlink': try { await blobClient.deleteIfExists(); } catch (error) { this.journal.push({ level: 'error', message: `Could not delete blob '${parsed.blobName}' from container '${parsed.container}'.`, op: 'remove', error }); } } } /** Flush the underlying file system to prepare for a commit. */ async flush() { if (this.flushEnabled) { for await (const [name, client] of this.listContainers('flush')) { for await (const blobItem of client.listBlobsFlat()) { const snapshotName = this.isGlobal ? '/' + name + '/' + blobItem.name : '/' + blobItem.name; if (this.include.match(snapshotName)) { const blobClient = client.getBlockBlobClient(blobItem.name); try { await blobClient.deleteIfExists(); } catch (error) { this.journal.push({ level: 'error', message: `Could not delete blob '${blobItem.name}' from container '${name}'.`, op: 'flush', error }); } } } } } } /** Reads a blob from blob storage. */ async readBlob(container, blobName, op) { const containerClient = typeof container === 'string' ? await this.getContainer(container, 'snapshot') : container; const blobClient = containerClient.getBlockBlobClient(blobName); try { return await blobClient.downloadToBuffer(); } catch (error) { this.journal.push({ level: 'error', message: `Could not read blob '${blobName}' from container '${containerClient.containerName}'.`, op, error }); return Buffer.alloc(0); } } /** Get or initialize the given container by name. */ async getContainer(name, op, exists = false) { let containerClient = this.containerCache.get(name); if (typeof containerClient === 'undefined') { containerClient = this.blobService.getContainerClient(name); if (!exists && this.createIfNotExist) { try { await containerClient.createIfNotExists(); } catch (error) { this.journal.push({ level: 'error', message: `Could not create blob container '${name}'.`, op, error }); } } this.containerCache.set(name, containerClient); } return containerClient; } /** List the containers for this instance and optionally cache them. */ async *listContainers(op) { if (this.containerCache.size === 0) { if (this.isGlobal) { for await (const containerItem of this.blobService.listContainers()) { if (typeof containerItem.deleted === 'undefined' || !containerItem.deleted) { yield [containerItem.name, await this.getContainer(containerItem.name, op, true)]; } } } else { yield [this.root, await this.getContainer(this.root, op)]; } } else { for (const item of this.containerCache.entries()) { yield item; } } } } exports.AzureBlobAdapter = AzureBlobAdapter; //# sourceMappingURL=AzureBlobAdapter.js.map