Skip to content
Mayura

Integrationsfiles

Azure Blob Storage files

Store files in an Azure Blob Storage container with @mayurajs/filestorage-azure-blob: Shared Key, Entra ID or SAS, preconditions and errors.

Package
@mayurajs/filestorage-azure-blob
Install
npm install mayura @mayurajs/filestorage-azure-blob
Vendor
Microsoft Azure ↗

Azure Blob Storage is Microsoft Azure's object store. @mayurajs/filestorage-azure-blob plugs a container into mayura/files, so you get the same createFileStore API, limits, per-tenant views and file tools as with the built-in S3 backend. It talks to the Blob REST API (service version 2024-11-04) with fetch, signing with Web Crypto: no Azure SDK, no other dependencies, and it runs on every runtime Mayura runs on.

Install

bash
npm install mayura @mayurajs/filestorage-azure-blob

Credentials

Give exactly one of three credentials, all from the storage account in the Azure portal:

  • Account key (accountKey): under Security + networking > Access keys. Requests are signed with Shared Key. The key grants everything in the account.
  • Microsoft Entra ID token (token): a function returning an access token for https://storage.azure.com/, for example from @azure/identity. Give the identity the Storage Blob Data Contributor role on the container.
  • Shared access signature (sas): under Shared access tokens, or generated by your code. The store reads, creates, writes, deletes and lists, so the SAS needs those permissions (sp=rcwdl) on the container.

The package reads nothing from the environment, neither AZURE_STORAGE_CONNECTION_STRING nor AZURE_STORAGE_KEY: pass the credential yourself, for example from AZURE_STORAGE_ACCOUNT, AZURE_STORAGE_KEY (or AZURE_STORAGE_SAS_TOKEN) and AZURE_STORAGE_CONTAINER.

Use it

ts
import { createFileStore } from 'mayura/files';
import { azureBlobFiles } from '@mayurajs/filestorage-azure-blob';

const files = createFileStore(azureBlobFiles({
  account: process.env.AZURE_STORAGE_ACCOUNT ?? 'acmestorage',
  container: process.env.AZURE_STORAGE_CONTAINER ?? 'reports',
  accountKey: process.env.AZURE_STORAGE_KEY ?? '',
}), { maxFileBytes: 10 * 1024 * 1024 });

const written = await files.put('q3/sales.csv', new TextEncoder().encode('order,total\n'), {
  contentType: 'text/csv',
  metadata: { 'run-id': 'run-42' },
  ifNoneMatch: '*',
});
const file = await files.get('q3/sales.csv', { ifMatch: written.etag });
console.log(file?.metadata); // { 'run-id': 'run-42' }

With a SAS, pass sas: 'sv=...&sp=rcwdl&sig=...' (the query string, without a leading ?) instead of accountKey. See Files for every store method, the store's own options and the file tools.

Options

Option Default Meaning
account required The storage account name: 3 to 24 lowercase letters and digits.
container required The container name.
accountKey one of three The base64 account key, for Shared Key signing.
token one of three A function returning a Microsoft Entra ID access token for https://storage.azure.com/, called for each request.
sas one of three A shared access signature: the query string Azure gave, without a leading ?, containing sig.
endpoint https://<account>.blob.core.windows.net Another endpoint: an https origin, or an http one on this machine for Azurite, optionally with the account as its path (http://127.0.0.1:10000/devstoreaccount1).
maxListResponseBytes 16 MiB The largest list response read.
fetch globalThis.fetch For tests and proxies.

Versions and preconditions

A file's etag is Azure's ETag, quoted. The store's conditionalWrites and conditionalDelete are both true, and they are Azure's own conditional headers:

  • ifNoneMatch: '*' sends If-None-Match: *; Azure's answer that the blob already exists becomes CONFLICT.
  • ifMatch on a write, a read or a delete sends If-Match; a stale etag (HTTP 412) or a missing blob becomes CONFLICT.

Writes, metadata and limits

  • Files are block blobs, each written in one request. A delete also removes the blob's snapshots.
  • The media type is the blob's Content-Type. Metadata names must be C# identifiers on Azure, so each key is stored under a name Azure accepts (run-id as m_run_id) and read back as written. Listings return each file's size, etag, media type and time, but not its metadata.
  • Files are as large as the store's maxFileBytes, up to 5 GiB. Each file is held in memory while it is written or read.
  • Ranges are read with x-ms-range; a range that starts at or past the end returns no bytes and the file's size.

Errors

Failures reach you as a FileStoreError (code STORAGE_UNAVAILABLE) with a reason, mapped from Azure's answers. Nothing Azure wrote reaches the message.

Error When
CONFLICT A precondition failed: the blob exists for a create-only write, has another version, or is missing for ifMatch.
reason: 'authentication' The token source threw or returned no usable token (nothing is sent), or Azure answered 401 or 403, for example a wrong key or signature.
reason: 'rate_limited' HTTP 429.
reason: 'timeout' HTTP 408, or the call took longer than the store's timeoutMs.
reason: 'unavailable' HTTP 5xx, or Azure could not be reached.
reason: 'rejected' Azure refused the request for another reason.
reason: 'invalid_response' The reply was not what the Blob API promises.
LIMIT_EXCEEDED A file or range is larger than the limit for the call.

Configuration mistakes, such as two credentials at once, an account key that is not base64 or an http endpoint on another host, fail with INVALID_CONFIG when you call azureBlobFiles().

How it is tested

  • Unit tests run the package against a fake that records every request: Shared Key signatures, block blob headers, metadata names, conditional headers, Entra ID and SAS authentication, error mapping without the service's text, and that nothing is read from the environment.
  • In CI, the file store conformance suite runs against Azurite, Microsoft's Azure Storage emulator, which checks Shared Key signatures: all 13 cases pass, and a request signed with another key is refused with authentication.
  • It has not been run against a real Azure account yet.