Azure Blob Datastore
Stores swamp runtime data in Azure Blob Storage using native blob-lease distributed locking (the lease ID doubles as the fencing-token nonce) and ETag-conditional writes on a shard-first path index for optimistic concurrency. Emits OpenTelemetry spans for blob operations, lock acquisition/release, and push/pull sync so datastore activity is visible in traces when a TracerProvider is configured. Supports Shared Key (account key or connection string) and Azure AD service-principal authentication. No Azure SDK dependency — talks to the Blob REST API directly via fetch.
2026.08.28.1
Changed: Normalized the extension license to Apache-2.0 and corrected the copyright holder to "Sean Escriva". Extensions that previously shipped an MIT LICENSE.md are now Apache-2.0, consistent with the repository root and every other extension. No code or behavioral changes.
Upgrade note: License text only. No API, schema, or runtime behavior changed.
2026.08.26.3
Fixed: Restored inline npm:zod@4.4.3 import specifiers so the registry
quality scorer can resolve dependencies and score the extension. An earlier
release used a bare "zod" import-map specifier, which published but scored as
unscored.
Changed: Retained explicit compilerOptions.strict in deno.json. No
behavioral or schema changes.
Stores swamp runtime data in Azure Blob Storage with native blob-lease distributed locking and ETag-conditional shard-index writes.
Config Fields
| Field | Type | Description |
|---|---|---|
| container | string | Existing Azure Blob container name. Not auto-created — see README. |
| prefix | string | Blob-name prefix namespace within the container, so multiple swamp datastores can share one container |
2026.08.26.3
Fixed: Restored inline npm:zod@4.4.3 import specifiers so the registry
quality scorer can resolve dependencies and score the extension. An earlier
release used a bare "zod" import-map specifier, which published but scored as
unscored.
Changed: Retained explicit compilerOptions.strict in deno.json. No
behavioral or schema changes.
2026.08.26.1
Changed: Normalized deno.json configuration for repo-wide consistency:
added explicit compilerOptions.strict and migrated zod dependency to the
import map (bare "zod" specifier instead of inline npm:zod@4.4.3). No
behavioral changes — runtime resolution is identical.
2026.08.25.1
Changed: Updated labels for improved extension discoverability. Added cross-cutting category labels (security, observability, finops, infrastructure, networking, compliance, devops, ai, incident-response) where applicable.
updated labels
2026.08.24.1
Added Troubleshooting section documenting silent lease heartbeat/release, non-retried network errors, container pre-existence requirement, sovereign cloud endpoint configuration, ETag conflict retries, dirty paths cap, and best-effort release.
2026.08.21.1
Changed: Error messages name the operation and blob path that failed, instead of a bare HTTP status or raw network error.
- Lock operations (
acquire,renew,release,stampMetadata,createLockBlob,inspect) now wrap transport-level failures — DNS errors, connection resets, timeouts — with the specific lease action and blob path in flight (e.g.Azure Blob lease.acquire request failed for /container/prefix/_locks/my-lock.lock: <reason>). Previously these surfaced as an unlabeled fetch error with no indication of which lock was affected. Lease acquire failed,Failed to create lock blob, andFailed to stamp lock metadataerrors now include the blob path, not just the HTTP status.- Sync operations (
listIndexShards,getShard,updateShard,fetchContent,readCommitSeq,incrementCommitSeq, blob upload) now include the response body in their error messages, not just the HTTP status code — the body carries Azure's actual error code (e.g.ContainerNotFound,AuthenticationFailed), which was previously discarded. - The container health check (
createVerifier) now reports which operation and container failed (e.g.getContainerProperties failed for container "swamp-datastore": <reason>) instead of the bareString(error).
No changes to request/response shapes, retry behavior, or existing successful-path behavior.
2026.07.29.1
Added: Targeted shard fetch for dirty-path pushes. Computes shard keys for changed paths and fetches only those shards (typically 5-10) instead of all 256.
Added: Atomic commitSeq counter blob (_meta/commit_seq) incremented via
ETag-CAS on push. Pull fast-path compares a monotonic integer immune to client
clock skew.
Changed: Blob uploads run with bounded concurrency of 12 workers instead of sequentially.
Changed: Shard CAS updates are batched by shard key — one read-modify-write per distinct shard instead of one per file.
Changed: Pull captures commitSeq before fetching shard data (not after) to
prevent TOCTOU races where concurrent pushes during the fetch window could be
silently missed.
Fixed: Removed unused localFiles array in queryShardsByPaths that
misled readers about what drives the output filter.
2026.07.27.1
Changed: Bump @opentelemetry/api 1.9.0 → 1.9.1
2026.07.25.1
Added: OpenTelemetry spans for every layer of the datastore. Blob REST
calls emit one span each (Azure Blob putBlob, Azure Blob lease.acquire, …)
carrying container, blob key, HTTP method, response status, body size, and the
x-ms-request-id. The lock emits azure-blob-datastore lock acquire /
release / withLock / inspect / forceRelease, with acquire recording
wait duration and whether it contended. The sync service emits
azure-blob-datastore pullChanged / pushChanged / hydrateFile /
preparePush / commitPush with file counts and fast-path indicators, plus
spans on the multi-round-trip internals (listIndexShards,
queryAllFileMeta, updateShard).
Added: Retries are recorded as retry span events on the enclosing
operation — both the 429/5xx backoff in retryableRequest and the ETag
conflict loop in updateShard, which retries independently of it.
Changed: pullChanged reports datastore.files_pulled and
datastore.files_deleted separately. The internal pull counter increments for
both a downloaded file and a local file removed by a remote tombstone, so
reporting it as a pull count would have overstated downloads whenever
tombstones were applied.
Changed: Nothing observable without tracing configured. The extension
depends on @opentelemetry/api only; the host process owns the
TracerProvider, and every span is a no-op when none is registered. Existing
behaviour and return values are unchanged.
Note on secrets: Shared Key signatures, AAD client secrets, and bearer
tokens are never recorded as span attributes. The AAD token exchange emits a
span carrying only its response status — on failure the response body is
deliberately dropped from the error message, because recordException would
otherwise put the token endpoint's raw response into the trace.
Note: Lock heartbeat renewals run detached from the acquiring span. A span
created inside the renewal timer would otherwise be parented to an
already-ended lock acquire span, which trace backends render as a broken
trace.
2026.07.29.1
Added: Targeted shard fetch for dirty-path pushes. Computes shard keys for changed paths and fetches only those shards (typically 5-10) instead of all 256.
Added: Atomic commitSeq counter blob (_meta/commit_seq) incremented via
ETag-CAS on push. Pull fast-path compares a monotonic integer immune to client
clock skew.
Changed: Blob uploads run with bounded concurrency of 12 workers instead of sequentially.
Changed: Shard CAS updates are batched by shard key — one read-modify-write per distinct shard instead of one per file.
Changed: Pull captures commitSeq before fetching shard data (not after) to
prevent TOCTOU races where concurrent pushes during the fetch window could be
silently missed.
Fixed: Removed unused localFiles array in queryShardsByPaths that
misled readers about what drives the output filter.
2026.07.27.1
Changed: Bump @opentelemetry/api 1.9.0 → 1.9.1
2026.07.25.1
Added: OpenTelemetry spans for every layer of the datastore. Blob REST
calls emit one span each (Azure Blob putBlob, Azure Blob lease.acquire, …)
carrying container, blob key, HTTP method, response status, body size, and the
x-ms-request-id. The lock emits azure-blob-datastore lock acquire /
release / withLock / inspect / forceRelease, with acquire recording
wait duration and whether it contended. The sync service emits
azure-blob-datastore pullChanged / pushChanged / hydrateFile /
preparePush / commitPush with file counts and fast-path indicators, plus
spans on the multi-round-trip internals (listIndexShards,
queryAllFileMeta, updateShard).
Added: Retries are recorded as retry span events on the enclosing
operation — both the 429/5xx backoff in retryableRequest and the ETag
conflict loop in updateShard, which retries independently of it.
Changed: pullChanged reports datastore.files_pulled and
datastore.files_deleted separately. The internal pull counter increments for
both a downloaded file and a local file removed by a remote tombstone, so
reporting it as a pull count would have overstated downloads whenever
tombstones were applied.
Changed: Nothing observable without tracing configured. The extension
depends on @opentelemetry/api only; the host process owns the
TracerProvider, and every span is a no-op when none is registered. Existing
behaviour and return values are unchanged.
Note on secrets: Shared Key signatures, AAD client secrets, and bearer
tokens are never recorded as span attributes. The AAD token exchange emits a
span carrying only its response status — on failure the response body is
deliberately dropped from the error message, because recordException would
otherwise put the token endpoint's raw response into the trace.
Note: Lock heartbeat renewals run detached from the acquiring span. A span
created inside the renewal timer would otherwise be parented to an
already-ended lock acquire span, which trace backends render as a broken
trace.
2026.07.27.1
Changed: Bump @opentelemetry/api 1.9.0 → 1.9.1
2026.07.25.1
Added: OpenTelemetry spans for every layer of the datastore. Blob REST
calls emit one span each (Azure Blob putBlob, Azure Blob lease.acquire, …)
carrying container, blob key, HTTP method, response status, body size, and the
x-ms-request-id. The lock emits azure-blob-datastore lock acquire /
release / withLock / inspect / forceRelease, with acquire recording
wait duration and whether it contended. The sync service emits
azure-blob-datastore pullChanged / pushChanged / hydrateFile /
preparePush / commitPush with file counts and fast-path indicators, plus
spans on the multi-round-trip internals (listIndexShards,
queryAllFileMeta, updateShard).
Added: Retries are recorded as retry span events on the enclosing
operation — both the 429/5xx backoff in retryableRequest and the ETag
conflict loop in updateShard, which retries independently of it.
Changed: pullChanged reports datastore.files_pulled and
datastore.files_deleted separately. The internal pull counter increments for
both a downloaded file and a local file removed by a remote tombstone, so
reporting it as a pull count would have overstated downloads whenever
tombstones were applied.
Changed: Nothing observable without tracing configured. The extension
depends on @opentelemetry/api only; the host process owns the
TracerProvider, and every span is a no-op when none is registered. Existing
behaviour and return values are unchanged.
Note on secrets: Shared Key signatures, AAD client secrets, and bearer
tokens are never recorded as span attributes. The AAD token exchange emits a
span carrying only its response status — on failure the response body is
deliberately dropped from the error message, because recordException would
otherwise put the token endpoint's raw response into the trace.
Note: Lock heartbeat renewals run detached from the acquiring span. A span
created inside the renewal timer would otherwise be parented to an
already-ended lock acquire span, which trace backends render as a broken
trace.
2026.07.25.1
Added: OpenTelemetry spans for every layer of the datastore. Blob REST
calls emit one span each (Azure Blob putBlob, Azure Blob lease.acquire, …)
carrying container, blob key, HTTP method, response status, body size, and the
x-ms-request-id. The lock emits azure-blob-datastore lock acquire /
release / withLock / inspect / forceRelease, with acquire recording
wait duration and whether it contended. The sync service emits
azure-blob-datastore pullChanged / pushChanged / hydrateFile /
preparePush / commitPush with file counts and fast-path indicators, plus
spans on the multi-round-trip internals (listIndexShards,
queryAllFileMeta, updateShard).
Added: Retries are recorded as retry span events on the enclosing
operation — both the 429/5xx backoff in retryableRequest and the ETag
conflict loop in updateShard, which retries independently of it.
Changed: pullChanged reports datastore.files_pulled and
datastore.files_deleted separately. The internal pull counter increments for
both a downloaded file and a local file removed by a remote tombstone, so
reporting it as a pull count would have overstated downloads whenever
tombstones were applied.
Changed: Nothing observable without tracing configured. The extension
depends on @opentelemetry/api only; the host process owns the
TracerProvider, and every span is a no-op when none is registered. Existing
behaviour and return values are unchanged.
Note on secrets: Shared Key signatures, AAD client secrets, and bearer
tokens are never recorded as span attributes. The AAD token exchange emits a
span carrying only its response status — on failure the response body is
deliberately dropped from the error message, because recordException would
otherwise put the token endpoint's raw response into the trace.
Note: Lock heartbeat renewals run detached from the acquiring span. A span
created inside the renewal timer would otherwise be parented to an
already-ended lock acquire span, which trace backends render as a broken
trace.
updated labels
2026.07.22.1
Added: Initial release of @webframp/azure-blob-datastore. Stores swamp
runtime data in Azure Blob Storage with native blob-lease distributed locking
(the Azure lease ID doubles as the fencing-token nonce — Azure enforces the
compare-and-swap server-side, no custom CAS logic needed), ETag-conditional
writes on a shard-first path index for optimistic concurrency, and two-phase
sync (preparePush/commitPush).
Added: Three explicit authentication modes — connectionString,
sharedKey (account name/key), and servicePrincipal (Azure AD
client-credentials). DefaultAzureCredential/managed-identity chains are
intentionally not supported, matching this repo's preference for explicit
config over ambient credential discovery.
Upgrade note: No @azure/* SDK dependency — this extension talks to the
Blob REST API directly via fetch, the same zero-dependency approach already
used by @webframp/gitlab-datastore and @webframp/azure/openai-usage.
Fixed-duration leases (15-60s, clamped from the caller's ttlMs) with
heartbeat renewal are used instead of Azure's infinite-lease option, so a
crashed holder's lock still self-expires — matching the failure-mode parity
of the postgres/valkey datastores.
- Has README or module doc2/2earned
- README has a code example1/1earned
- README is substantive1/1earned
- Most symbols documented1/1earned
- No slow types (deprecated)1/1earned
- Dependencies pass trust audit2/2earned
- Has description1/1earned
- Platform support declared (or universal)2/2earned
- License declared1/1earned
- Verified public repository2/2earned