@cloudbitmaps/core 0.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +12 -0
  3. package/README.md +22 -0
  4. package/dist/azure/index.cjs +368 -0
  5. package/dist/azure/index.cjs.map +1 -0
  6. package/dist/azure/index.d.cts +48 -0
  7. package/dist/azure/index.d.ts +48 -0
  8. package/dist/azure/index.js +304 -0
  9. package/dist/azure/index.js.map +1 -0
  10. package/dist/cassandra/index.cjs +265 -0
  11. package/dist/cassandra/index.cjs.map +1 -0
  12. package/dist/cassandra/index.d.cts +52 -0
  13. package/dist/cassandra/index.d.ts +52 -0
  14. package/dist/cassandra/index.js +204 -0
  15. package/dist/cassandra/index.js.map +1 -0
  16. package/dist/chunk-2YDULGXS.js +203 -0
  17. package/dist/chunk-2YDULGXS.js.map +1 -0
  18. package/dist/chunk-7LMLYSVJ.js +43 -0
  19. package/dist/chunk-7LMLYSVJ.js.map +1 -0
  20. package/dist/chunk-AS6ODRLT.js +6 -0
  21. package/dist/chunk-AS6ODRLT.js.map +1 -0
  22. package/dist/chunk-NUIDEEFZ.js +91 -0
  23. package/dist/chunk-NUIDEEFZ.js.map +1 -0
  24. package/dist/chunk-SNJVZ227.js +35 -0
  25. package/dist/chunk-SNJVZ227.js.map +1 -0
  26. package/dist/dynamodb/index.cjs +731 -0
  27. package/dist/dynamodb/index.cjs.map +1 -0
  28. package/dist/dynamodb/index.d.cts +106 -0
  29. package/dist/dynamodb/index.d.ts +106 -0
  30. package/dist/dynamodb/index.js +460 -0
  31. package/dist/dynamodb/index.js.map +1 -0
  32. package/dist/gcs/index.cjs +343 -0
  33. package/dist/gcs/index.cjs.map +1 -0
  34. package/dist/gcs/index.d.cts +46 -0
  35. package/dist/gcs/index.d.ts +46 -0
  36. package/dist/gcs/index.js +279 -0
  37. package/dist/gcs/index.js.map +1 -0
  38. package/dist/index.cjs +4031 -0
  39. package/dist/index.cjs.map +1 -0
  40. package/dist/index.d.cts +1945 -0
  41. package/dist/index.d.ts +1945 -0
  42. package/dist/index.js +3642 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/mongodb/index.cjs +260 -0
  45. package/dist/mongodb/index.cjs.map +1 -0
  46. package/dist/mongodb/index.d.cts +45 -0
  47. package/dist/mongodb/index.d.ts +45 -0
  48. package/dist/mongodb/index.js +199 -0
  49. package/dist/mongodb/index.js.map +1 -0
  50. package/dist/mysql/index.cjs +281 -0
  51. package/dist/mysql/index.cjs.map +1 -0
  52. package/dist/mysql/index.d.cts +56 -0
  53. package/dist/mysql/index.d.ts +56 -0
  54. package/dist/mysql/index.js +211 -0
  55. package/dist/mysql/index.js.map +1 -0
  56. package/dist/ports-D3BrJ6ax.d.cts +357 -0
  57. package/dist/ports-D3BrJ6ax.d.ts +357 -0
  58. package/dist/postgres/index.cjs +273 -0
  59. package/dist/postgres/index.cjs.map +1 -0
  60. package/dist/postgres/index.d.cts +54 -0
  61. package/dist/postgres/index.d.ts +54 -0
  62. package/dist/postgres/index.js +203 -0
  63. package/dist/postgres/index.js.map +1 -0
  64. package/dist/redis/index.cjs +257 -0
  65. package/dist/redis/index.cjs.map +1 -0
  66. package/dist/redis/index.d.cts +42 -0
  67. package/dist/redis/index.d.ts +42 -0
  68. package/dist/redis/index.js +197 -0
  69. package/dist/redis/index.js.map +1 -0
  70. package/dist/s3/index.cjs +891 -0
  71. package/dist/s3/index.cjs.map +1 -0
  72. package/dist/s3/index.d.cts +112 -0
  73. package/dist/s3/index.d.ts +112 -0
  74. package/dist/s3/index.js +555 -0
  75. package/dist/s3/index.js.map +1 -0
  76. package/package.json +172 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/errors.ts","../../src/core/validate.ts","../../src/drivers/_shared/keys.ts","../../src/drivers/azure/keys.ts","../../src/drivers/azure/azure-errors.ts","../../src/drivers/azure/cold.ts"],"names":["createHash","randomBytes"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Typed errors — callers learn *why* something failed, never by parsing strings. Retry is the driver\n * decorators' job, not the engine's: a driver classifies its backend's failures into this vocabulary, and\n * `withRetry` decides what is transient.\n */\n\n/**\n * Registry-symbol brands. The package ships as multiple bundles — the core entry and the `./s3` / `./dynamodb`\n * subpaths — and the builder inlines `core/errors` into each. A driver in a subpath bundle therefore throws a\n * *different* class object than the one the core engine/retry code would `instanceof`-check, so `instanceof`\n * silently returns false across that boundary in the published CJS package (defeating OCC/transient retry and\n * compaction race-handling). These `Symbol.for` brands are identity-stable across bundles/realms; classify\n * errors with the exported predicates below (never `instanceof`) anywhere an error may cross the boundary.\n */\nconst ERROR_BRAND: unique symbol = Symbol.for('cloud-roaring.error');\nconst TRANSIENT_BRAND: unique symbol = Symbol.for('cloud-roaring.error.transient');\n\n/** Base class for every error CloudRoaring throws. */\nexport class CloudRoaringError extends Error {\n /** Cross-bundle brand — see the predicates ({@link isCloudRoaringError}, …). Non-enumerable-ish (symbol key ⇒ not in JSON). */\n readonly [ERROR_BRAND] = true as const;\n constructor(message: string) {\n super(message);\n // Subclass name (works under transpilation since we set it explicitly). Also the discriminator the\n // predicates match on — a runtime string, so it survives bundling where the class identity does not.\n this.name = new.target.name;\n }\n}\n\n/** Invalid caller input (bad id, segment name, options). Raised before any storage call. */\nexport class ValidationError extends CloudRoaringError {}\n\n/** An OCC conditional write/delete lost the race — the row changed since it was read. */\nexport class WriteConflictError extends CloudRoaringError {}\n\n/** Bytes from a tier are corrupt, oversized, or fail a checksum/format check. */\nexport class IntegrityError extends CloudRoaringError {}\n\n/**\n * A requested object/row does not exist. Part of the driver error vocabulary; thrown by\n * persistent drivers from Phase 2 — the Phase-1 engine + in-memory drivers return `null` instead.\n */\nexport class NotFoundError extends CloudRoaringError {}\n\n/**\n * This build/configuration cannot perform the requested operation, though nothing is malformed. Two uses:\n * (1) **format** — the bytes are well-formed but unreadable here (an unknown `.crbm` major version, an\n * encrypted file before the crypto path exists) — distinct from `IntegrityError` (corruption); and (2)\n * **store configuration** — an operation this store's wiring doesn't support (e.g. a lifecycle helper like\n * `compact`/`eraseSubject` called on a store built without a raw cold driver + registry). Raised at\n * operation time, before any mutation.\n */\nexport class UnsupportedError extends CloudRoaringError {}\n\n/**\n * A driver cannot meet a capability the chosen topology requires (e.g. a Cold driver without range\n * reads). Raised fail-fast at wiring time, never mid-operation.\n */\nexport class CapabilityError extends CloudRoaringError {}\n\n/**\n * An operation would exceed its per-op **denial-of-wallet budget** — too many backend requests for a single\n * `count`/`iterate`/`intersect`/`subjectReport`/`eraseSubject` call — so it is refused **before** fanning out\n * (Decision #3 / invariant T3). Default-on but generous (normal ops never hit it); tune it\n * per store (`budget`) or per op, or disable with `budget: false`. Deterministic (never retried): the op is too\n * big by policy, not by luck. Each request's bytes are separately capped (the safe-deserialize ceiling), so\n * bounding the request count transitively bounds bytes. Carries the projected count + the limit, never data.\n */\nexport class BudgetExceededError extends CloudRoaringError {}\n\n/**\n * An encrypted segment's data key (DEK) cannot be unwrapped because the keystore holds none of the\n * key-encryption-keys (KEKs) its wrappings reference — the KEK was never configured, rotated away without\n * keeping the old key, or lost. Deterministic (never retried): without a KEK the ciphertext is unreadable by\n * design. The flip side of crypto-shred — when this is *intended* (a destroyed segment) the registry row is\n * already a `destroyed` tombstone; when it's *not*, restore the missing KEK (or its recovery KEK). Carries no\n * key material.\n */\nexport class KeyUnavailableError extends CloudRoaringError {}\n\n/**\n * A **transient** infrastructure fault that is safe to retry — throttling, a 5xx, a dropped connection,\n * a client-side request timeout. Drivers classify their backend's retryable faults and raise this (the\n * SDK-specific knowledge stays in the SDK-specific driver); the retry layer (`core/retry`) retries **only**\n * this class, never a deterministic error like {@link ValidationError}, {@link IntegrityError},\n * {@link NotFoundError}, or {@link WriteConflictError} (retrying those is pointless or wrong). The original\n * error is preserved in `cause` so callers can still inspect it.\n *\n * Note for logging hygiene (threat-model S12): `cause` is the **raw SDK error**, which may carry operational\n * metadata (endpoint host, request IDs, `$metadata`). The library's own `message` is identifier-only and safe\n * to log; if you serialize the whole error *chain*, be aware you're including that metadata.\n */\nexport class TransientError extends CloudRoaringError {\n /** A second brand so the whole transient subtree (incl. {@link TimeoutError}) is classifiable cross-bundle. */\n readonly [TRANSIENT_BRAND] = true as const;\n constructor(message: string, options?: { cause?: unknown }) {\n super(message);\n if (options && 'cause' in options) this.cause = options.cause;\n }\n}\n\n/**\n * A single attempt exceeded its time budget. Subclass of {@link TransientError} so the retry layer treats a\n * timeout as retryable by default — a stalled request often succeeds on a fresh connection. Raised by a\n * driver whose injected client reports a request timeout — setting one on your injected client is the\n * recommended way to bound a hang.\n */\nexport class TimeoutError extends TransientError {}\n\n/**\n * Bundle-safe error predicates — use these, not `instanceof`, wherever an error may cross the core↔driver\n * (`./s3` / `./dynamodb`) boundary (and prefer them in consumer `catch` blocks too, for the same reason). They\n * match the {@link ERROR_BRAND} registry brand + the runtime `name`, both of which survive separate bundling.\n */\nfunction hasBrand(err: unknown, brand: symbol): boolean {\n return (\n typeof err === 'object' && err !== null && (err as Record<symbol, unknown>)[brand] === true\n );\n}\n\n/** Any error thrown by CloudRoaring (any tier, any bundle). */\nexport function isCloudRoaringError(err: unknown): err is CloudRoaringError {\n return hasBrand(err, ERROR_BRAND);\n}\n\n/** An OCC conditional write/delete lost the race — retry the read-modify-write, don't fail. */\nexport function isWriteConflictError(err: unknown): err is WriteConflictError {\n return isCloudRoaringError(err) && err.name === 'WriteConflictError';\n}\n\n/** A retryable transient infrastructure fault (incl. {@link TimeoutError}). The retry layer keys on this. */\nexport function isTransientError(err: unknown): err is TransientError {\n return hasBrand(err, TRANSIENT_BRAND);\n}\n\n/** A requested object/row does not exist. */\nexport function isNotFoundError(err: unknown): err is NotFoundError {\n return isCloudRoaringError(err) && err.name === 'NotFoundError';\n}\n\n/** Corrupt/oversized/failed-checksum bytes from a tier. */\nexport function isIntegrityError(err: unknown): err is IntegrityError {\n return isCloudRoaringError(err) && err.name === 'IntegrityError';\n}\n\n/** Invalid caller input. */\nexport function isValidationError(err: unknown): err is ValidationError {\n return isCloudRoaringError(err) && err.name === 'ValidationError';\n}\n","/**\n * Boundary validation for segment / namespace names (finding S2).\n * Names become object keys, partition keys, and file paths — so they're validated before they\n * ever reach storage: strict charset, bounded length, no path traversal.\n */\nimport { ValidationError } from './errors';\nimport type { ChunkRef, SegmentRef } from './ports';\n\nconst CHUNK_KEY_MAX = 0xffff;\n\n// The locked name grammar: 1 leading alphanumeric + up to 255 more (max 256 chars).\nconst NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;\n\nfunction validatePart(value: string, field: string): void {\n if (typeof value !== 'string' || !NAME.test(value) || value.includes('..')) {\n throw new ValidationError(\n `${field} must match ${String(NAME)} and contain no \"..\": got ${JSON.stringify(value)}`,\n );\n }\n}\n\nexport function validateSegmentRef(ref: SegmentRef): void {\n validatePart(ref.segment, 'segment');\n if (ref.namespace !== undefined) validatePart(ref.namespace, 'namespace');\n}\n\n/** Validate a chunk ref: the segment/namespace grammar plus `chunkKey` ∈ `[0, 65535]` (a u16). */\nexport function validateChunkRef(ref: ChunkRef): void {\n validateSegmentRef(ref);\n if (!Number.isInteger(ref.chunkKey) || ref.chunkKey < 0 || ref.chunkKey > CHUNK_KEY_MAX) {\n throw new ValidationError(\n `chunkKey must be an integer in [0, ${CHUNK_KEY_MAX}]; got ${ref.chunkKey}`,\n );\n }\n}\n","/**\n * Shared key-grammar fragments used by every driver's logical-ref → physical-key mapping (Phase 4c).\n *\n * Extracted once the registry drivers became the 4th/5th consumer of the same `_default` namespace sentinel\n * (it lived copy-pasted in `s3/keys`, `dynamodb/keys`, `localfs/paths`). Pure string logic, no SDK, no I/O —\n * lives in the SDK-free `_shared` bundle so any driver may import it.\n */\n\n/**\n * The physical stand-in for an **absent** namespace. The name grammar forbids a leading underscore\n * so `_default` can never collide with a real\n * namespace — `segment(\"s\")` and `segment(\"s\", { namespace: \"_default\" })` would be a grammar error, not an\n * aliasing hazard.\n */\nexport const DEFAULT_NAMESPACE = '_default';\n\n/** Map an optional namespace to its physical part: the namespace itself, or {@link DEFAULT_NAMESPACE}. */\nexport function namespacePart(namespace: string | undefined): string {\n return namespace ?? DEFAULT_NAMESPACE;\n}\n","/**\n * Logical-ref → Azure blob-name mapping for {@link AzureBlobColdDriver} (Phase 7).\n *\n * Pure string logic, no SDK dependency — unit-testable without Azure or an emulator. Uses the **same\n * backend-agnostic `.crbm` object-name scheme** as the S3 + GCS + LocalFs cold drivers\n * (`<prefix><ns>/segments/<segment>.<gen>.crbm`), so a segment reads identically whichever cold backend holds\n * it. The default (absent) namespace maps to `_default` (the grammar forbids a leading underscore, so it can't\n * collide with a real namespace).\n *\n * NOTE (DRY): this mirrors the pure cold-key builders in `drivers/s3/keys.ts` + `drivers/gcs/keys.ts`. They\n * are deliberately **not** shared across driver folders today — a driver must stay self-contained so it lifts\n * cleanly into its own package at the [Phase 9 split]. At that\n * split the shared cold-key scheme is promoted into `@cloudbitmaps/core`'s driver-kit (imported by every driver\n * package), which is the right home for it; until then a self-contained copy beats a cross-driver import.\n */\nimport { ValidationError } from '@/core/errors';\nimport { validateSegmentRef } from '@/core/validate';\nimport type { GenKey, SegmentRef } from '@/core/ports';\nimport { namespacePart } from '../_shared/keys';\n\nconst SUFFIX = '.crbm';\n\n/** Normalize an optional caller prefix to either `''` or `trimmed/` (no leading/trailing slashes). */\nfunction prefixPart(prefix: string | undefined): string {\n if (prefix === undefined) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed === '' ? '' : `${trimmed}/`;\n}\n\n/**\n * Validate the caller-supplied blob-name prefix (trusted config, but a real containment boundary): reject\n * control characters and `.`/`..` path segments so a prefix can't traverse out of its intended space. Returns\n * it unchanged (the key builders normalize slashes).\n */\nexport function normalizeAzurePrefix(prefix: string | undefined): string | undefined {\n if (prefix === undefined) return undefined;\n for (const ch of prefix) {\n if (ch.charCodeAt(0) < 0x20) {\n throw new ValidationError('prefix must not contain control characters');\n }\n }\n for (const segment of prefix.split('/')) {\n if (segment === '.' || segment === '..') {\n throw new ValidationError(\n `prefix must not contain \".\" or \"..\" path segments: ${JSON.stringify(prefix)}`,\n );\n }\n }\n return prefix;\n}\n\n/**\n * The Azure blob-name prefix shared by all of a segment's generations: `<prefix><ns>/segments/<segment>.`.\n * Used both as the `listBlobsFlat` prefix and as the string stripped by {@link parseGenerationFromName}.\n */\nexport function segmentObjectPrefix(prefix: string | undefined, ref: SegmentRef): string {\n validateSegmentRef(ref);\n return `${prefixPart(prefix)}${namespacePart(ref.namespace)}/segments/${ref.segment}.`;\n}\n\n/** The full Azure blob name of one `.crbm` generation: `<segmentPrefix><gen>.crbm`. */\nexport function coldObjectName(prefix: string | undefined, key: GenKey): string {\n if (!Number.isInteger(key.generation) || key.generation < 0) {\n throw new ValidationError(`generation must be a non-negative integer; got ${key.generation}`);\n }\n return `${segmentObjectPrefix(prefix, key)}${key.generation}${SUFFIX}`;\n}\n\n/**\n * Parse a generation number out of a full blob name, given its segment prefix, or `null` if it doesn't match.\n * Canonical decimal only — no leading zeros (so `…s.07.crbm` can't alias `…s.7.crbm`) and within safe-integer\n * range. Also rejects a *different* segment whose name merely shares the prefix (its middle isn't all digits).\n */\nexport function parseGenerationFromName(segmentPrefix: string, objectName: string): number | null {\n if (!objectName.startsWith(segmentPrefix) || !objectName.endsWith(SUFFIX)) return null;\n const middle = objectName.slice(segmentPrefix.length, objectName.length - SUFFIX.length);\n if (!/^(0|[1-9]\\d*)$/.test(middle)) return null;\n const generation = Number(middle);\n return Number.isSafeInteger(generation) ? generation : null;\n}\n","/**\n * Pure helpers for classifying Azure Blob Storage SDK errors (Phase 7; transient class mirrors S3/GCS).\n *\n * SDK-free + side-effect-free — they only read structural shapes off the thrown value (`err.statusCode`,\n * `err.code`, `err.details.errorCode`), so the Azure-specific translation is unit-testable without a live\n * Azure or an Azurite emulator, and without importing `@azure/storage-blob`. The SDK throws a `RestError`\n * carrying the HTTP status on `.statusCode` (a number) and, usually, the Azure error code string on `.code`\n * / `.details.errorCode` (e.g. `BlobAlreadyExists`, `BlobNotFound`, `InvalidRange`).\n *\n * **Empirically verified against Azurite** (see the driver's write-once note): a conditional\n * `ifNoneMatch: '*'` write that loses the race returns **409 `BlobAlreadyExists`** on BOTH the single-upload\n * and staged-block-commit paths — Azure uses 409 here, NOT the 412 that GCS/S3-`If-None-Match` return. A HEAD\n * (`getProperties`) on a missing blob returns 404 with `.code` *undefined* (no response body), so NotFound is\n * keyed off the status, not the code.\n */\n\n/** The HTTP status of an Azure `RestError`, if present (`err.statusCode` as a number). */\nfunction httpStatus(err: unknown): number | undefined {\n const code = (err as { statusCode?: unknown } | null)?.statusCode;\n return typeof code === 'number' ? code : undefined;\n}\n\n/** The Azure error-code string, from `err.code` or `err.details.errorCode` (either may be absent). */\nfunction azureCode(err: unknown): string | undefined {\n const e = err as { code?: unknown; details?: { errorCode?: unknown } } | null;\n if (typeof e?.code === 'string') return e.code;\n if (typeof e?.details?.errorCode === 'string') return e.details.errorCode;\n return undefined;\n}\n\n/** A network-level error `code` string (e.g. `ECONNRESET`, `ETIMEDOUT`, `EAI_AGAIN`, `EPIPE`). */\nfunction networkCode(err: unknown): string | undefined {\n const code = (err as { code?: unknown } | null)?.code;\n return typeof code === 'string' ? code : undefined;\n}\n\n/**\n * A conditional `ifNoneMatch: '*'` write lost the write-once race — the blob already existed. Azure signals\n * this as **409 `BlobAlreadyExists`** (verified on both the single-upload and `commitBlockList` paths); we\n * also accept **412 `ConditionNotMet`** defensively (the status a specific-ETag precondition would use, and\n * what a non-Azurite backend might return). Both map to `WriteConflictError` (caller OCC), never a blind retry.\n */\nexport function isConditionalConflict(err: unknown): boolean {\n const status = httpStatus(err);\n if (status === 409 || status === 412) return true;\n const code = azureCode(err);\n return code === 'BlobAlreadyExists' || code === 'ConditionNotMet';\n}\n\n/**\n * The blob does not exist. Azure returns **404**; the code is `BlobNotFound` on a GET but *undefined* on a\n * HEAD (`getProperties`) since a HEAD has no response body — so the 404 status is the reliable signal.\n */\nexport function isNotFound(err: unknown): boolean {\n return httpStatus(err) === 404 || azureCode(err) === 'BlobNotFound';\n}\n\n/** A range request started past EOF (HTTP 416 `InvalidRange`). */\nexport function isInvalidRange(err: unknown): boolean {\n return httpStatus(err) === 416 || azureCode(err) === 'InvalidRange';\n}\n\n/**\n * A transient Azure fault that is safe to retry: throttling (429 / `ServerBusy` / `OperationTimedOut`), any\n * 5xx, or a dropped/timed-out socket. Excludes the deterministic, caller-meaningful outcomes (409/412/404/416)\n * — those must never be reclassified as a blind transient (a retried doomed conditional write would just fail\n * again, and mask an OCC conflict).\n */\nexport function isTransient(err: unknown): boolean {\n if (isConditionalConflict(err) || isNotFound(err) || isInvalidRange(err)) return false;\n const status = httpStatus(err);\n if (status === 429 || (status !== undefined && status >= 500 && status < 600)) return true;\n const code = azureCode(err);\n if (code === 'ServerBusy' || code === 'OperationTimedOut' || code === 'InternalError')\n return true;\n const net = networkCode(err);\n return (\n net === 'ECONNRESET' ||\n net === 'ECONNABORTED' ||\n net === 'ETIMEDOUT' ||\n net === 'ESOCKETTIMEDOUT' ||\n net === 'ECONNREFUSED' ||\n net === 'EHOSTUNREACH' ||\n net === 'ENETUNREACH' ||\n net === 'EPIPE' ||\n net === 'EAI_AGAIN' ||\n net === 'ENOTFOUND'\n );\n}\n","/**\n * `AzureBlobColdDriver` — an {@link IColdDriver} over Azure Blob Storage (Phase 7).\n *\n * Uses the official `@azure/storage-blob`, an **optional peer dependency** — only consumers of\n * `cloud-roaring/azure` install it. A `ContainerClient` is **injected** (dependency injection): the driver owns\n * no credential/account/endpoint logic, so it's thin, reuses the caller's client, and is testable against the\n * Azurite emulator (point a `ContainerClient` at its connection string). The container must already exist —\n * the Azure analogue of the S3 bucket / GCS bucket. (Unlike the S3/GCS drivers, which inject a top-level\n * client + a `bucket` *name*, Azure's `ContainerClient` is already container-scoped, so there is no separate\n * container-name option — the caller scopes it. This is the idiomatic Azure handle and keeps the driver pure DI.)\n *\n * Generations are write-once immutable blobs: the conditional **`ifNoneMatch: '*'`** (\"create only if it does\n * not exist\") makes the publish atomic — a second write to the same blob fails with 409 `BlobAlreadyExists` →\n * {@link WriteConflictError}, never a silent overwrite (C13), the Azure analogue of S3's `If-None-Match: *`,\n * GCS's `ifGenerationMatch: 0`, and LocalFs's atomic `link`. **Empirically verified against Azurite** that the\n * precondition is enforced on BOTH upload paths below. **Writes stream in constant memory** (Phase 4f model):\n * a small blob is a single conditional `upload`; a larger one is **staged as blocks** (each `stageBlock`\n * flushes and frees ~one block) finished with a conditional `commitBlockList`, so the daemon's write footprint\n * stays ~one block regardless of segment size, up to the advertised `maxObjectBytes`. Drivers may use\n * `node:crypto`; only `core/` is bound by the determinism lint.\n */\nimport { createHash, randomBytes, type Hash } from 'node:crypto';\nimport type { ContainerClient } from '@azure/storage-blob';\nimport type { BlobSink } from '@/core/blob';\nimport {\n NotFoundError,\n TransientError,\n ValidationError,\n WriteConflictError,\n isNotFoundError,\n isValidationError,\n isWriteConflictError,\n} from '@/core/errors';\nimport type { ColdCaps, GenKey, IColdDriver, SegmentRef } from '@/core/ports';\nimport {\n coldObjectName,\n normalizeAzurePrefix,\n parseGenerationFromName,\n segmentObjectPrefix,\n} from './keys';\nimport { isConditionalConflict, isInvalidRange, isNotFound, isTransient } from './azure-errors';\n\n/** Flush threshold for staged uploads: the sink buffers until `pending` reaches this, then stages it as one\n * block. A blob that never reaches it is a single conditional `upload` instead (no block-list overhead,\n * strongest write-once). It bounds peak write memory to ≈ one block *given codec-granularity writes* (the\n * `.crbm` codec writes small per-chunk buffers, so a block ≈ this size); a single larger `write()` becomes one\n * larger block. Default 8 MiB (≈ S3's part). */\nconst AZURE_BLOCK_BYTES = 8 * 1024 * 1024;\n/** Azure hard limit: a block blob has at most 50,000 committed blocks. This × the block size is the ceiling. */\nconst AZURE_MAX_BLOCKS = 50_000;\n\nconst OCTET_STREAM = { blobContentType: 'application/octet-stream' } as const;\n/** Write-once precondition shared by both upload paths: create only if the blob is absent. */\nconst IF_ABSENT = { conditions: { ifNoneMatch: '*' }, blobHTTPHeaders: OCTET_STREAM } as const;\n\nexport interface AzureBlobColdDriverOptions {\n /** A constructed `@azure/storage-blob` `ContainerClient`, scoped to an existing container (point it at\n * Azurite's connection string for local/integration use). */\n readonly containerClient: ContainerClient;\n /** Optional blob-name prefix under which all objects live (e.g. `cloudroaring/`). */\n readonly prefix?: string;\n /**\n * Largest blob this driver will write/advertise. Default = `blockBytes × 50,000` (≈ 400 GiB at the default\n * 8 MiB block) — the honest ceiling reachable within Azure's 50,000-block limit. Set it higher and\n * `blockBytes` auto-grows so 50,000 blocks still cover it (raising peak write memory to ~one block).\n */\n readonly maxObjectBytes?: number;\n /** Staged block size in bytes (default 8 MiB). Tunes peak write memory. */\n readonly blockBytes?: number;\n}\n\nexport class AzureBlobColdDriver implements IColdDriver {\n private readonly container: ContainerClient;\n private readonly prefix: string | undefined;\n private readonly maxObjectBytes: number;\n private readonly blockBytes: number;\n\n constructor(options: AzureBlobColdDriverOptions) {\n this.container = options.containerClient;\n this.prefix = normalizeAzurePrefix(options.prefix);\n // Fail fast at the boundary: nullish-coalescing only guards `undefined`, so an explicit 0 / negative /\n // fractional value would otherwise slip through and silently reject every write (cap) or corrupt the flush\n // threshold (block size).\n const requestedBlock = options.blockBytes ?? AZURE_BLOCK_BYTES;\n if (!Number.isSafeInteger(requestedBlock) || requestedBlock < 1) {\n throw new ValidationError(\n `blockBytes must be a positive safe integer; got ${requestedBlock}`,\n );\n }\n if (\n options.maxObjectBytes !== undefined &&\n (!Number.isSafeInteger(options.maxObjectBytes) || options.maxObjectBytes < 1)\n ) {\n throw new ValidationError(\n `maxObjectBytes must be a positive safe integer; got ${options.maxObjectBytes}`,\n );\n }\n // Default the object cap to what the requested block size can cover within the 50,000-block limit; if a\n // larger cap is requested, grow the block size so the advertised cap stays honest (and reachable).\n this.maxObjectBytes = options.maxObjectBytes ?? requestedBlock * AZURE_MAX_BLOCKS;\n this.blockBytes = Math.max(requestedBlock, Math.ceil(this.maxObjectBytes / AZURE_MAX_BLOCKS));\n }\n\n capabilities(): ColdCaps {\n return { rangeRead: true, maxObjectBytes: this.maxObjectBytes, conditionalPut: true };\n }\n\n private blob(name: string) {\n return this.container.getBlockBlobClient(name);\n }\n\n async putImmutable(\n key: GenKey,\n write: (sink: BlobSink) => Promise<void>,\n ): Promise<{ size: number; sha256: string }> {\n const objectName = coldObjectName(this.prefix, key); // validates ref + generation\n const sink = new AzureBlockBlobSink(\n this.blob(objectName),\n this.blockBytes,\n this.maxObjectBytes,\n );\n try {\n await write(sink);\n return await sink.finish();\n } catch (err) {\n // A lost write-once race — the blob already existed, so `ifNoneMatch: '*'` failed (409). Correctness is\n // safe with no explicit teardown: staged-but-uncommitted blocks are never a visible blob (a reader only\n // ever sees a committed block list). Azure has no \"abort block list\" API (unlike S3's AbortMultipartUpload\n // / GCS's stream.destroy), so on the staged path they linger as *billed* storage until Azure's uncommitted-\n // block GC reaps them (~7 days). Recommend a container lifecycle rule to auto-delete uncommitted blocks\n // (operational notes); such races are rare (a compaction lease already\n // serializes the normal path).\n if (isConditionalConflict(err)) {\n throw new WriteConflictError(\n `generation already exists (write-once): ${key.segment}.${key.generation}`,\n );\n }\n if (isValidationError(err) || isWriteConflictError(err) || isNotFoundError(err)) throw err;\n throw this.mapError(err);\n }\n }\n\n async getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array> {\n if (!Number.isInteger(offset) || !Number.isInteger(length) || offset < 0 || length < 0) {\n throw new ValidationError(`invalid range offset=${offset} length=${length}`);\n }\n if (length === 0) return new Uint8Array(0);\n const objectName = coldObjectName(this.prefix, key);\n try {\n const res = await this.blob(objectName).download(offset, length);\n const bytes = await collect(res.readableStreamBody);\n // A short read means the range ran past EOF — treat as out-of-bounds, never a partial result. (Azurite\n // returns a clamped-short body here rather than a 416; a start fully past EOF does 416 → mapReadError.)\n if (bytes.length !== length) {\n throw new ValidationError(\n `range [${offset}, ${offset + length}) out of bounds (got ${bytes.length}B)`,\n );\n }\n return bytes;\n } catch (err) {\n throw this.mapReadError(err, key);\n }\n }\n\n async getTail(key: GenKey, maxBytes: number): Promise<{ bytes: Uint8Array; size: number }> {\n const objectName = coldObjectName(this.prefix, key);\n try {\n // Two round-trips (properties for the size, then a ranged download) vs S3's one (suffix-range +\n // Content-Range). This is on the per-*generation* open path, which the reader caches — NOT the per-op hot\n // path (add/has/count/intersect) — so it's amortized; collapsing to one RT is a tracked follow-up.\n const props = await this.blob(objectName).getProperties();\n const size = props.contentLength ?? 0;\n if (!Number.isSafeInteger(size) || size < 0) {\n throw new ValidationError(`Azure returned an invalid blob size: ${String(size)}`);\n }\n if (maxBytes <= 0 || size === 0) return { bytes: new Uint8Array(0), size };\n const take = Math.min(maxBytes, size);\n const res = await this.blob(objectName).download(size - take, take);\n return { bytes: await collect(res.readableStreamBody), size };\n } catch (err) {\n throw this.mapReadError(err, key);\n }\n }\n\n async delete(key: GenKey): Promise<void> {\n // Idempotent: `deleteIfExists` is a no-op (no throw) on an absent blob, so a racing/retried GC sweep is safe.\n try {\n await this.blob(coldObjectName(this.prefix, key)).deleteIfExists();\n } catch (err) {\n throw this.mapError(err);\n }\n }\n\n async *list(ref: SegmentRef): AsyncIterable<GenKey> {\n const prefix = segmentObjectPrefix(this.prefix, ref); // validates ref\n try {\n // The async paging iterator drains every page; a segment has few generations, so the set is small.\n for await (const item of this.container.listBlobsFlat({ prefix })) {\n const generation = parseGenerationFromName(prefix, item.name);\n if (generation !== null) {\n yield { namespace: ref.namespace, segment: ref.segment, generation };\n }\n }\n } catch (err) {\n throw this.mapError(err);\n }\n }\n\n /** Map Azure read errors to the driver vocabulary; pass everything else through {@link mapError}. */\n private mapReadError(err: unknown, key: GenKey): unknown {\n if (isValidationError(err)) return err;\n if (isNotFound(err))\n return new NotFoundError(`no such generation: ${key.segment}.${key.generation}`);\n // A fully out-of-range request (start past EOF) — the BlobReader contract treats range errors as\n // ValidationError, never a short/empty read.\n if (isInvalidRange(err)) {\n return new ValidationError(`range out of bounds for ${key.segment}.${key.generation}`);\n }\n return this.mapError(err);\n }\n\n /**\n * Reclassify a transient Azure fault (throttle/5xx/dropped connection) as a retryable {@link TransientError}\n * so the retry decorator can ride it out; everything else propagates unchanged. The final fallback at every\n * client-call site, so callers + the decorator only ever see typed errors.\n */\n private mapError(err: unknown): unknown {\n if (isTransient(err)) {\n return new TransientError(\n `transient Azure fault: ${(err as { code?: unknown } | null)?.code ?? 'unknown'}`,\n { cause: err },\n );\n }\n return err;\n }\n}\n\ntype BlockBlob = ReturnType<ContainerClient['getBlockBlobClient']>;\n\n/** Concatenate byte chunks of known total length into one buffer. */\nfunction concatBytes(parts: readonly Uint8Array[], total: number): Uint8Array {\n const out = new Uint8Array(total);\n let offset = 0;\n for (const p of parts) {\n out.set(p, offset);\n offset += p.length;\n }\n return out;\n}\n\n/**\n * Streaming {@link BlobSink} that uploads one Azure block blob in **constant memory**. It buffers at most one\n * block: as the codec writes, full blocks are staged via `stageBlock` and freed (the awaited request is the\n * natural backpressure). A small blob that never reaches one block is committed as a single conditional\n * `upload`; a larger one is finished with a conditional `commitBlockList` — **both enforce write-once** via\n * `ifNoneMatch: '*'`. SHA-256 is hashed incrementally.\n */\nclass AzureBlockBlobSink implements BlobSink {\n private readonly hash: Hash = createHash('sha256');\n private readonly pending: Uint8Array[] = [];\n private pendingLen = 0;\n private total = 0;\n private readonly blockIds: string[] = [];\n /**\n * A random per-sink nonce folded into every block id. Azure pools **uncommitted** blocks per *blob name*,\n * keyed by id — so two writers racing the same generation key on the staged path MUST use disjoint id spaces,\n * else one would overwrite the other's staged blocks and a commit could reference an interleaved mix of both\n * writers' bytes (a corrupt blob whose committed content wouldn't match the returned SHA-256). A per-instance\n * nonce makes each writer's ids unique, so the winner commits only its own blocks and the loser cleanly 409s.\n * 6 random bytes → 12 fixed hex chars, keeping every id equal-length (Azure's within-blob id requirement).\n */\n private readonly uploadNonce = randomBytes(6).toString('hex');\n\n constructor(\n private readonly blob: BlockBlob,\n private readonly blockBytes: number,\n private readonly maxObjectBytes: number,\n ) {}\n\n /** Fixed-width (constant length within a blob) AND per-sink-unique block id: `<nonce>-<zero-padded index>`. */\n private blockId(n: number): string {\n return Buffer.from(`${this.uploadNonce}-${String(n).padStart(6, '0')}`).toString('base64');\n }\n\n async write(bytes: Uint8Array): Promise<void> {\n if (bytes.length === 0) return;\n this.total += bytes.length;\n if (this.total > this.maxObjectBytes) {\n // Fail fast + typed, rather than a late opaque Azure error.\n throw new ValidationError(`object exceeds maxObjectBytes ${this.maxObjectBytes}`);\n }\n this.hash.update(bytes);\n // Copy on retain: the SHA-256 is committed to these exact bytes now, but they sit in `pending` across the\n // `write()` boundary until the next flush — the `BlobSink` contract (see core/blob.ts) lets a caller reuse\n // its buffer after `write()` resolves, so a shared reference could let the committed content diverge from\n // the already-hashed bytes. `new Uint8Array(bytes)` copies UNCONDITIONALLY — note `bytes.slice()` would NOT:\n // the payload is a `Buffer` (roaring serialize), whose `slice()` returns an aliasing view, not a copy.\n // (S3/GCS + `BufferSink` share this retain-by-reference pattern; tracked as a cross-driver follow-up.)\n this.pending.push(new Uint8Array(bytes));\n this.pendingLen += bytes.length;\n if (this.pendingLen >= this.blockBytes) await this.flushBlock();\n }\n\n /** Stage the buffered bytes (≥ one block) as a single block, freeing them. */\n private async flushBlock(): Promise<void> {\n if (this.blockIds.length >= AZURE_MAX_BLOCKS) {\n // Unreachable for valid input (the maxObjectBytes byte-cap, sized to ≤ 50,000 blocks, fires first) — a\n // typed guard so the Azure hard limit is never a raw 4xx.\n throw new ValidationError(`upload exceeded the Azure ${AZURE_MAX_BLOCKS}-block limit`);\n }\n const body = concatBytes(this.pending, this.pendingLen);\n this.pending.length = 0;\n this.pendingLen = 0;\n const id = this.blockId(this.blockIds.length);\n await this.blob.stageBlock(id, body, body.length);\n this.blockIds.push(id);\n }\n\n /** Commit the blob: a single conditional `upload` if it fit in one block, else commit the staged block list. */\n async finish(): Promise<{ size: number; sha256: string }> {\n const sha256 = this.hash.digest('hex');\n if (this.blockIds.length === 0) {\n const body = concatBytes(this.pending, this.pendingLen);\n await this.blob.upload(body, body.length, IF_ABSENT); // write-once (single-shot path)\n return { size: this.total, sha256 };\n }\n if (this.pendingLen > 0) await this.flushBlock(); // the final block may be < blockBytes (allowed)\n await this.blob.commitBlockList(this.blockIds, IF_ABSENT); // write-once (staged path)\n return { size: this.total, sha256 };\n }\n}\n\n/** Collect an Azure download's Node readable body into a `Uint8Array`. */\nasync function collect(body: NodeJS.ReadableStream | undefined): Promise<Uint8Array> {\n if (body === undefined) {\n throw new NotFoundError('Azure download returned no body');\n }\n const chunks: Uint8Array[] = [];\n let total = 0;\n for await (const chunk of body) {\n const u8 =\n typeof chunk === 'string' ? new TextEncoder().encode(chunk) : new Uint8Array(chunk as Buffer);\n chunks.push(u8);\n total += u8.length;\n }\n return concatBytes(chunks, total);\n}\n"]}
@@ -0,0 +1,48 @@
1
+ import { ContainerClient } from '@azure/storage-blob';
2
+ import { I as IColdDriver, C as ColdCaps, G as GenKey, B as BlobSink, S as SegmentRef } from '../ports-D3BrJ6ax.cjs';
3
+
4
+ interface AzureBlobColdDriverOptions {
5
+ /** A constructed `@azure/storage-blob` `ContainerClient`, scoped to an existing container (point it at
6
+ * Azurite's connection string for local/integration use). */
7
+ readonly containerClient: ContainerClient;
8
+ /** Optional blob-name prefix under which all objects live (e.g. `cloudroaring/`). */
9
+ readonly prefix?: string;
10
+ /**
11
+ * Largest blob this driver will write/advertise. Default = `blockBytes × 50,000` (≈ 400 GiB at the default
12
+ * 8 MiB block) — the honest ceiling reachable within Azure's 50,000-block limit. Set it higher and
13
+ * `blockBytes` auto-grows so 50,000 blocks still cover it (raising peak write memory to ~one block).
14
+ */
15
+ readonly maxObjectBytes?: number;
16
+ /** Staged block size in bytes (default 8 MiB). Tunes peak write memory. */
17
+ readonly blockBytes?: number;
18
+ }
19
+ declare class AzureBlobColdDriver implements IColdDriver {
20
+ private readonly container;
21
+ private readonly prefix;
22
+ private readonly maxObjectBytes;
23
+ private readonly blockBytes;
24
+ constructor(options: AzureBlobColdDriverOptions);
25
+ capabilities(): ColdCaps;
26
+ private blob;
27
+ putImmutable(key: GenKey, write: (sink: BlobSink) => Promise<void>): Promise<{
28
+ size: number;
29
+ sha256: string;
30
+ }>;
31
+ getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;
32
+ getTail(key: GenKey, maxBytes: number): Promise<{
33
+ bytes: Uint8Array;
34
+ size: number;
35
+ }>;
36
+ delete(key: GenKey): Promise<void>;
37
+ list(ref: SegmentRef): AsyncIterable<GenKey>;
38
+ /** Map Azure read errors to the driver vocabulary; pass everything else through {@link mapError}. */
39
+ private mapReadError;
40
+ /**
41
+ * Reclassify a transient Azure fault (throttle/5xx/dropped connection) as a retryable {@link TransientError}
42
+ * so the retry decorator can ride it out; everything else propagates unchanged. The final fallback at every
43
+ * client-call site, so callers + the decorator only ever see typed errors.
44
+ */
45
+ private mapError;
46
+ }
47
+
48
+ export { AzureBlobColdDriver, type AzureBlobColdDriverOptions };
@@ -0,0 +1,48 @@
1
+ import { ContainerClient } from '@azure/storage-blob';
2
+ import { I as IColdDriver, C as ColdCaps, G as GenKey, B as BlobSink, S as SegmentRef } from '../ports-D3BrJ6ax.js';
3
+
4
+ interface AzureBlobColdDriverOptions {
5
+ /** A constructed `@azure/storage-blob` `ContainerClient`, scoped to an existing container (point it at
6
+ * Azurite's connection string for local/integration use). */
7
+ readonly containerClient: ContainerClient;
8
+ /** Optional blob-name prefix under which all objects live (e.g. `cloudroaring/`). */
9
+ readonly prefix?: string;
10
+ /**
11
+ * Largest blob this driver will write/advertise. Default = `blockBytes × 50,000` (≈ 400 GiB at the default
12
+ * 8 MiB block) — the honest ceiling reachable within Azure's 50,000-block limit. Set it higher and
13
+ * `blockBytes` auto-grows so 50,000 blocks still cover it (raising peak write memory to ~one block).
14
+ */
15
+ readonly maxObjectBytes?: number;
16
+ /** Staged block size in bytes (default 8 MiB). Tunes peak write memory. */
17
+ readonly blockBytes?: number;
18
+ }
19
+ declare class AzureBlobColdDriver implements IColdDriver {
20
+ private readonly container;
21
+ private readonly prefix;
22
+ private readonly maxObjectBytes;
23
+ private readonly blockBytes;
24
+ constructor(options: AzureBlobColdDriverOptions);
25
+ capabilities(): ColdCaps;
26
+ private blob;
27
+ putImmutable(key: GenKey, write: (sink: BlobSink) => Promise<void>): Promise<{
28
+ size: number;
29
+ sha256: string;
30
+ }>;
31
+ getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;
32
+ getTail(key: GenKey, maxBytes: number): Promise<{
33
+ bytes: Uint8Array;
34
+ size: number;
35
+ }>;
36
+ delete(key: GenKey): Promise<void>;
37
+ list(ref: SegmentRef): AsyncIterable<GenKey>;
38
+ /** Map Azure read errors to the driver vocabulary; pass everything else through {@link mapError}. */
39
+ private mapReadError;
40
+ /**
41
+ * Reclassify a transient Azure fault (throttle/5xx/dropped connection) as a retryable {@link TransientError}
42
+ * so the retry decorator can ride it out; everything else propagates unchanged. The final fallback at every
43
+ * client-call site, so callers + the decorator only ever see typed errors.
44
+ */
45
+ private mapError;
46
+ }
47
+
48
+ export { AzureBlobColdDriver, type AzureBlobColdDriverOptions };
@@ -0,0 +1,304 @@
1
+ import { ValidationError, WriteConflictError, isValidationError, isWriteConflictError, isNotFoundError, NotFoundError, TransientError, validateSegmentRef, namespacePart } from '../chunk-NUIDEEFZ.js';
2
+ import { createHash, randomBytes } from 'crypto';
3
+
4
+ // src/drivers/azure/keys.ts
5
+ var SUFFIX = ".crbm";
6
+ function prefixPart(prefix) {
7
+ if (prefix === void 0) return "";
8
+ const trimmed = prefix.replace(/^\/+|\/+$/g, "");
9
+ return trimmed === "" ? "" : `${trimmed}/`;
10
+ }
11
+ function normalizeAzurePrefix(prefix) {
12
+ if (prefix === void 0) return void 0;
13
+ for (const ch of prefix) {
14
+ if (ch.charCodeAt(0) < 32) {
15
+ throw new ValidationError("prefix must not contain control characters");
16
+ }
17
+ }
18
+ for (const segment of prefix.split("/")) {
19
+ if (segment === "." || segment === "..") {
20
+ throw new ValidationError(
21
+ `prefix must not contain "." or ".." path segments: ${JSON.stringify(prefix)}`
22
+ );
23
+ }
24
+ }
25
+ return prefix;
26
+ }
27
+ function segmentObjectPrefix(prefix, ref) {
28
+ validateSegmentRef(ref);
29
+ return `${prefixPart(prefix)}${namespacePart(ref.namespace)}/segments/${ref.segment}.`;
30
+ }
31
+ function coldObjectName(prefix, key) {
32
+ if (!Number.isInteger(key.generation) || key.generation < 0) {
33
+ throw new ValidationError(`generation must be a non-negative integer; got ${key.generation}`);
34
+ }
35
+ return `${segmentObjectPrefix(prefix, key)}${key.generation}${SUFFIX}`;
36
+ }
37
+ function parseGenerationFromName(segmentPrefix, objectName) {
38
+ if (!objectName.startsWith(segmentPrefix) || !objectName.endsWith(SUFFIX)) return null;
39
+ const middle = objectName.slice(segmentPrefix.length, objectName.length - SUFFIX.length);
40
+ if (!/^(0|[1-9]\d*)$/.test(middle)) return null;
41
+ const generation = Number(middle);
42
+ return Number.isSafeInteger(generation) ? generation : null;
43
+ }
44
+
45
+ // src/drivers/azure/azure-errors.ts
46
+ function httpStatus(err) {
47
+ const code = err?.statusCode;
48
+ return typeof code === "number" ? code : void 0;
49
+ }
50
+ function azureCode(err) {
51
+ const e = err;
52
+ if (typeof e?.code === "string") return e.code;
53
+ if (typeof e?.details?.errorCode === "string") return e.details.errorCode;
54
+ return void 0;
55
+ }
56
+ function networkCode(err) {
57
+ const code = err?.code;
58
+ return typeof code === "string" ? code : void 0;
59
+ }
60
+ function isConditionalConflict(err) {
61
+ const status = httpStatus(err);
62
+ if (status === 409 || status === 412) return true;
63
+ const code = azureCode(err);
64
+ return code === "BlobAlreadyExists" || code === "ConditionNotMet";
65
+ }
66
+ function isNotFound(err) {
67
+ return httpStatus(err) === 404 || azureCode(err) === "BlobNotFound";
68
+ }
69
+ function isInvalidRange(err) {
70
+ return httpStatus(err) === 416 || azureCode(err) === "InvalidRange";
71
+ }
72
+ function isTransient(err) {
73
+ if (isConditionalConflict(err) || isNotFound(err) || isInvalidRange(err)) return false;
74
+ const status = httpStatus(err);
75
+ if (status === 429 || status !== void 0 && status >= 500 && status < 600) return true;
76
+ const code = azureCode(err);
77
+ if (code === "ServerBusy" || code === "OperationTimedOut" || code === "InternalError")
78
+ return true;
79
+ const net = networkCode(err);
80
+ return net === "ECONNRESET" || net === "ECONNABORTED" || net === "ETIMEDOUT" || net === "ESOCKETTIMEDOUT" || net === "ECONNREFUSED" || net === "EHOSTUNREACH" || net === "ENETUNREACH" || net === "EPIPE" || net === "EAI_AGAIN" || net === "ENOTFOUND";
81
+ }
82
+
83
+ // src/drivers/azure/cold.ts
84
+ var AZURE_BLOCK_BYTES = 8 * 1024 * 1024;
85
+ var AZURE_MAX_BLOCKS = 5e4;
86
+ var OCTET_STREAM = { blobContentType: "application/octet-stream" };
87
+ var IF_ABSENT = { conditions: { ifNoneMatch: "*" }, blobHTTPHeaders: OCTET_STREAM };
88
+ var AzureBlobColdDriver = class {
89
+ container;
90
+ prefix;
91
+ maxObjectBytes;
92
+ blockBytes;
93
+ constructor(options) {
94
+ this.container = options.containerClient;
95
+ this.prefix = normalizeAzurePrefix(options.prefix);
96
+ const requestedBlock = options.blockBytes ?? AZURE_BLOCK_BYTES;
97
+ if (!Number.isSafeInteger(requestedBlock) || requestedBlock < 1) {
98
+ throw new ValidationError(
99
+ `blockBytes must be a positive safe integer; got ${requestedBlock}`
100
+ );
101
+ }
102
+ if (options.maxObjectBytes !== void 0 && (!Number.isSafeInteger(options.maxObjectBytes) || options.maxObjectBytes < 1)) {
103
+ throw new ValidationError(
104
+ `maxObjectBytes must be a positive safe integer; got ${options.maxObjectBytes}`
105
+ );
106
+ }
107
+ this.maxObjectBytes = options.maxObjectBytes ?? requestedBlock * AZURE_MAX_BLOCKS;
108
+ this.blockBytes = Math.max(requestedBlock, Math.ceil(this.maxObjectBytes / AZURE_MAX_BLOCKS));
109
+ }
110
+ capabilities() {
111
+ return { rangeRead: true, maxObjectBytes: this.maxObjectBytes, conditionalPut: true };
112
+ }
113
+ blob(name) {
114
+ return this.container.getBlockBlobClient(name);
115
+ }
116
+ async putImmutable(key, write) {
117
+ const objectName = coldObjectName(this.prefix, key);
118
+ const sink = new AzureBlockBlobSink(
119
+ this.blob(objectName),
120
+ this.blockBytes,
121
+ this.maxObjectBytes
122
+ );
123
+ try {
124
+ await write(sink);
125
+ return await sink.finish();
126
+ } catch (err) {
127
+ if (isConditionalConflict(err)) {
128
+ throw new WriteConflictError(
129
+ `generation already exists (write-once): ${key.segment}.${key.generation}`
130
+ );
131
+ }
132
+ if (isValidationError(err) || isWriteConflictError(err) || isNotFoundError(err)) throw err;
133
+ throw this.mapError(err);
134
+ }
135
+ }
136
+ async getRange(key, offset, length) {
137
+ if (!Number.isInteger(offset) || !Number.isInteger(length) || offset < 0 || length < 0) {
138
+ throw new ValidationError(`invalid range offset=${offset} length=${length}`);
139
+ }
140
+ if (length === 0) return new Uint8Array(0);
141
+ const objectName = coldObjectName(this.prefix, key);
142
+ try {
143
+ const res = await this.blob(objectName).download(offset, length);
144
+ const bytes = await collect(res.readableStreamBody);
145
+ if (bytes.length !== length) {
146
+ throw new ValidationError(
147
+ `range [${offset}, ${offset + length}) out of bounds (got ${bytes.length}B)`
148
+ );
149
+ }
150
+ return bytes;
151
+ } catch (err) {
152
+ throw this.mapReadError(err, key);
153
+ }
154
+ }
155
+ async getTail(key, maxBytes) {
156
+ const objectName = coldObjectName(this.prefix, key);
157
+ try {
158
+ const props = await this.blob(objectName).getProperties();
159
+ const size = props.contentLength ?? 0;
160
+ if (!Number.isSafeInteger(size) || size < 0) {
161
+ throw new ValidationError(`Azure returned an invalid blob size: ${String(size)}`);
162
+ }
163
+ if (maxBytes <= 0 || size === 0) return { bytes: new Uint8Array(0), size };
164
+ const take = Math.min(maxBytes, size);
165
+ const res = await this.blob(objectName).download(size - take, take);
166
+ return { bytes: await collect(res.readableStreamBody), size };
167
+ } catch (err) {
168
+ throw this.mapReadError(err, key);
169
+ }
170
+ }
171
+ async delete(key) {
172
+ try {
173
+ await this.blob(coldObjectName(this.prefix, key)).deleteIfExists();
174
+ } catch (err) {
175
+ throw this.mapError(err);
176
+ }
177
+ }
178
+ async *list(ref) {
179
+ const prefix = segmentObjectPrefix(this.prefix, ref);
180
+ try {
181
+ for await (const item of this.container.listBlobsFlat({ prefix })) {
182
+ const generation = parseGenerationFromName(prefix, item.name);
183
+ if (generation !== null) {
184
+ yield { namespace: ref.namespace, segment: ref.segment, generation };
185
+ }
186
+ }
187
+ } catch (err) {
188
+ throw this.mapError(err);
189
+ }
190
+ }
191
+ /** Map Azure read errors to the driver vocabulary; pass everything else through {@link mapError}. */
192
+ mapReadError(err, key) {
193
+ if (isValidationError(err)) return err;
194
+ if (isNotFound(err))
195
+ return new NotFoundError(`no such generation: ${key.segment}.${key.generation}`);
196
+ if (isInvalidRange(err)) {
197
+ return new ValidationError(`range out of bounds for ${key.segment}.${key.generation}`);
198
+ }
199
+ return this.mapError(err);
200
+ }
201
+ /**
202
+ * Reclassify a transient Azure fault (throttle/5xx/dropped connection) as a retryable {@link TransientError}
203
+ * so the retry decorator can ride it out; everything else propagates unchanged. The final fallback at every
204
+ * client-call site, so callers + the decorator only ever see typed errors.
205
+ */
206
+ mapError(err) {
207
+ if (isTransient(err)) {
208
+ return new TransientError(
209
+ `transient Azure fault: ${err?.code ?? "unknown"}`,
210
+ { cause: err }
211
+ );
212
+ }
213
+ return err;
214
+ }
215
+ };
216
+ function concatBytes(parts, total) {
217
+ const out = new Uint8Array(total);
218
+ let offset = 0;
219
+ for (const p of parts) {
220
+ out.set(p, offset);
221
+ offset += p.length;
222
+ }
223
+ return out;
224
+ }
225
+ var AzureBlockBlobSink = class {
226
+ constructor(blob, blockBytes, maxObjectBytes) {
227
+ this.blob = blob;
228
+ this.blockBytes = blockBytes;
229
+ this.maxObjectBytes = maxObjectBytes;
230
+ }
231
+ blob;
232
+ blockBytes;
233
+ maxObjectBytes;
234
+ hash = createHash("sha256");
235
+ pending = [];
236
+ pendingLen = 0;
237
+ total = 0;
238
+ blockIds = [];
239
+ /**
240
+ * A random per-sink nonce folded into every block id. Azure pools **uncommitted** blocks per *blob name*,
241
+ * keyed by id — so two writers racing the same generation key on the staged path MUST use disjoint id spaces,
242
+ * else one would overwrite the other's staged blocks and a commit could reference an interleaved mix of both
243
+ * writers' bytes (a corrupt blob whose committed content wouldn't match the returned SHA-256). A per-instance
244
+ * nonce makes each writer's ids unique, so the winner commits only its own blocks and the loser cleanly 409s.
245
+ * 6 random bytes → 12 fixed hex chars, keeping every id equal-length (Azure's within-blob id requirement).
246
+ */
247
+ uploadNonce = randomBytes(6).toString("hex");
248
+ /** Fixed-width (constant length within a blob) AND per-sink-unique block id: `<nonce>-<zero-padded index>`. */
249
+ blockId(n) {
250
+ return Buffer.from(`${this.uploadNonce}-${String(n).padStart(6, "0")}`).toString("base64");
251
+ }
252
+ async write(bytes) {
253
+ if (bytes.length === 0) return;
254
+ this.total += bytes.length;
255
+ if (this.total > this.maxObjectBytes) {
256
+ throw new ValidationError(`object exceeds maxObjectBytes ${this.maxObjectBytes}`);
257
+ }
258
+ this.hash.update(bytes);
259
+ this.pending.push(new Uint8Array(bytes));
260
+ this.pendingLen += bytes.length;
261
+ if (this.pendingLen >= this.blockBytes) await this.flushBlock();
262
+ }
263
+ /** Stage the buffered bytes (≥ one block) as a single block, freeing them. */
264
+ async flushBlock() {
265
+ if (this.blockIds.length >= AZURE_MAX_BLOCKS) {
266
+ throw new ValidationError(`upload exceeded the Azure ${AZURE_MAX_BLOCKS}-block limit`);
267
+ }
268
+ const body = concatBytes(this.pending, this.pendingLen);
269
+ this.pending.length = 0;
270
+ this.pendingLen = 0;
271
+ const id = this.blockId(this.blockIds.length);
272
+ await this.blob.stageBlock(id, body, body.length);
273
+ this.blockIds.push(id);
274
+ }
275
+ /** Commit the blob: a single conditional `upload` if it fit in one block, else commit the staged block list. */
276
+ async finish() {
277
+ const sha256 = this.hash.digest("hex");
278
+ if (this.blockIds.length === 0) {
279
+ const body = concatBytes(this.pending, this.pendingLen);
280
+ await this.blob.upload(body, body.length, IF_ABSENT);
281
+ return { size: this.total, sha256 };
282
+ }
283
+ if (this.pendingLen > 0) await this.flushBlock();
284
+ await this.blob.commitBlockList(this.blockIds, IF_ABSENT);
285
+ return { size: this.total, sha256 };
286
+ }
287
+ };
288
+ async function collect(body) {
289
+ if (body === void 0) {
290
+ throw new NotFoundError("Azure download returned no body");
291
+ }
292
+ const chunks = [];
293
+ let total = 0;
294
+ for await (const chunk of body) {
295
+ const u8 = typeof chunk === "string" ? new TextEncoder().encode(chunk) : new Uint8Array(chunk);
296
+ chunks.push(u8);
297
+ total += u8.length;
298
+ }
299
+ return concatBytes(chunks, total);
300
+ }
301
+
302
+ export { AzureBlobColdDriver };
303
+ //# sourceMappingURL=index.js.map
304
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/drivers/azure/keys.ts","../../src/drivers/azure/azure-errors.ts","../../src/drivers/azure/cold.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Logical-ref → Azure blob-name mapping for {@link AzureBlobColdDriver} (Phase 7).\n *\n * Pure string logic, no SDK dependency — unit-testable without Azure or an emulator. Uses the **same\n * backend-agnostic `.crbm` object-name scheme** as the S3 + GCS + LocalFs cold drivers\n * (`<prefix><ns>/segments/<segment>.<gen>.crbm`), so a segment reads identically whichever cold backend holds\n * it. The default (absent) namespace maps to `_default` (the grammar forbids a leading underscore, so it can't\n * collide with a real namespace).\n *\n * NOTE (DRY): this mirrors the pure cold-key builders in `drivers/s3/keys.ts` + `drivers/gcs/keys.ts`. They\n * are deliberately **not** shared across driver folders today — a driver must stay self-contained so it lifts\n * cleanly into its own package at the [Phase 9 split]. At that\n * split the shared cold-key scheme is promoted into `@cloudbitmaps/core`'s driver-kit (imported by every driver\n * package), which is the right home for it; until then a self-contained copy beats a cross-driver import.\n */\nimport { ValidationError } from '@/core/errors';\nimport { validateSegmentRef } from '@/core/validate';\nimport type { GenKey, SegmentRef } from '@/core/ports';\nimport { namespacePart } from '../_shared/keys';\n\nconst SUFFIX = '.crbm';\n\n/** Normalize an optional caller prefix to either `''` or `trimmed/` (no leading/trailing slashes). */\nfunction prefixPart(prefix: string | undefined): string {\n if (prefix === undefined) return '';\n const trimmed = prefix.replace(/^\\/+|\\/+$/g, '');\n return trimmed === '' ? '' : `${trimmed}/`;\n}\n\n/**\n * Validate the caller-supplied blob-name prefix (trusted config, but a real containment boundary): reject\n * control characters and `.`/`..` path segments so a prefix can't traverse out of its intended space. Returns\n * it unchanged (the key builders normalize slashes).\n */\nexport function normalizeAzurePrefix(prefix: string | undefined): string | undefined {\n if (prefix === undefined) return undefined;\n for (const ch of prefix) {\n if (ch.charCodeAt(0) < 0x20) {\n throw new ValidationError('prefix must not contain control characters');\n }\n }\n for (const segment of prefix.split('/')) {\n if (segment === '.' || segment === '..') {\n throw new ValidationError(\n `prefix must not contain \".\" or \"..\" path segments: ${JSON.stringify(prefix)}`,\n );\n }\n }\n return prefix;\n}\n\n/**\n * The Azure blob-name prefix shared by all of a segment's generations: `<prefix><ns>/segments/<segment>.`.\n * Used both as the `listBlobsFlat` prefix and as the string stripped by {@link parseGenerationFromName}.\n */\nexport function segmentObjectPrefix(prefix: string | undefined, ref: SegmentRef): string {\n validateSegmentRef(ref);\n return `${prefixPart(prefix)}${namespacePart(ref.namespace)}/segments/${ref.segment}.`;\n}\n\n/** The full Azure blob name of one `.crbm` generation: `<segmentPrefix><gen>.crbm`. */\nexport function coldObjectName(prefix: string | undefined, key: GenKey): string {\n if (!Number.isInteger(key.generation) || key.generation < 0) {\n throw new ValidationError(`generation must be a non-negative integer; got ${key.generation}`);\n }\n return `${segmentObjectPrefix(prefix, key)}${key.generation}${SUFFIX}`;\n}\n\n/**\n * Parse a generation number out of a full blob name, given its segment prefix, or `null` if it doesn't match.\n * Canonical decimal only — no leading zeros (so `…s.07.crbm` can't alias `…s.7.crbm`) and within safe-integer\n * range. Also rejects a *different* segment whose name merely shares the prefix (its middle isn't all digits).\n */\nexport function parseGenerationFromName(segmentPrefix: string, objectName: string): number | null {\n if (!objectName.startsWith(segmentPrefix) || !objectName.endsWith(SUFFIX)) return null;\n const middle = objectName.slice(segmentPrefix.length, objectName.length - SUFFIX.length);\n if (!/^(0|[1-9]\\d*)$/.test(middle)) return null;\n const generation = Number(middle);\n return Number.isSafeInteger(generation) ? generation : null;\n}\n","/**\n * Pure helpers for classifying Azure Blob Storage SDK errors (Phase 7; transient class mirrors S3/GCS).\n *\n * SDK-free + side-effect-free — they only read structural shapes off the thrown value (`err.statusCode`,\n * `err.code`, `err.details.errorCode`), so the Azure-specific translation is unit-testable without a live\n * Azure or an Azurite emulator, and without importing `@azure/storage-blob`. The SDK throws a `RestError`\n * carrying the HTTP status on `.statusCode` (a number) and, usually, the Azure error code string on `.code`\n * / `.details.errorCode` (e.g. `BlobAlreadyExists`, `BlobNotFound`, `InvalidRange`).\n *\n * **Empirically verified against Azurite** (see the driver's write-once note): a conditional\n * `ifNoneMatch: '*'` write that loses the race returns **409 `BlobAlreadyExists`** on BOTH the single-upload\n * and staged-block-commit paths — Azure uses 409 here, NOT the 412 that GCS/S3-`If-None-Match` return. A HEAD\n * (`getProperties`) on a missing blob returns 404 with `.code` *undefined* (no response body), so NotFound is\n * keyed off the status, not the code.\n */\n\n/** The HTTP status of an Azure `RestError`, if present (`err.statusCode` as a number). */\nfunction httpStatus(err: unknown): number | undefined {\n const code = (err as { statusCode?: unknown } | null)?.statusCode;\n return typeof code === 'number' ? code : undefined;\n}\n\n/** The Azure error-code string, from `err.code` or `err.details.errorCode` (either may be absent). */\nfunction azureCode(err: unknown): string | undefined {\n const e = err as { code?: unknown; details?: { errorCode?: unknown } } | null;\n if (typeof e?.code === 'string') return e.code;\n if (typeof e?.details?.errorCode === 'string') return e.details.errorCode;\n return undefined;\n}\n\n/** A network-level error `code` string (e.g. `ECONNRESET`, `ETIMEDOUT`, `EAI_AGAIN`, `EPIPE`). */\nfunction networkCode(err: unknown): string | undefined {\n const code = (err as { code?: unknown } | null)?.code;\n return typeof code === 'string' ? code : undefined;\n}\n\n/**\n * A conditional `ifNoneMatch: '*'` write lost the write-once race — the blob already existed. Azure signals\n * this as **409 `BlobAlreadyExists`** (verified on both the single-upload and `commitBlockList` paths); we\n * also accept **412 `ConditionNotMet`** defensively (the status a specific-ETag precondition would use, and\n * what a non-Azurite backend might return). Both map to `WriteConflictError` (caller OCC), never a blind retry.\n */\nexport function isConditionalConflict(err: unknown): boolean {\n const status = httpStatus(err);\n if (status === 409 || status === 412) return true;\n const code = azureCode(err);\n return code === 'BlobAlreadyExists' || code === 'ConditionNotMet';\n}\n\n/**\n * The blob does not exist. Azure returns **404**; the code is `BlobNotFound` on a GET but *undefined* on a\n * HEAD (`getProperties`) since a HEAD has no response body — so the 404 status is the reliable signal.\n */\nexport function isNotFound(err: unknown): boolean {\n return httpStatus(err) === 404 || azureCode(err) === 'BlobNotFound';\n}\n\n/** A range request started past EOF (HTTP 416 `InvalidRange`). */\nexport function isInvalidRange(err: unknown): boolean {\n return httpStatus(err) === 416 || azureCode(err) === 'InvalidRange';\n}\n\n/**\n * A transient Azure fault that is safe to retry: throttling (429 / `ServerBusy` / `OperationTimedOut`), any\n * 5xx, or a dropped/timed-out socket. Excludes the deterministic, caller-meaningful outcomes (409/412/404/416)\n * — those must never be reclassified as a blind transient (a retried doomed conditional write would just fail\n * again, and mask an OCC conflict).\n */\nexport function isTransient(err: unknown): boolean {\n if (isConditionalConflict(err) || isNotFound(err) || isInvalidRange(err)) return false;\n const status = httpStatus(err);\n if (status === 429 || (status !== undefined && status >= 500 && status < 600)) return true;\n const code = azureCode(err);\n if (code === 'ServerBusy' || code === 'OperationTimedOut' || code === 'InternalError')\n return true;\n const net = networkCode(err);\n return (\n net === 'ECONNRESET' ||\n net === 'ECONNABORTED' ||\n net === 'ETIMEDOUT' ||\n net === 'ESOCKETTIMEDOUT' ||\n net === 'ECONNREFUSED' ||\n net === 'EHOSTUNREACH' ||\n net === 'ENETUNREACH' ||\n net === 'EPIPE' ||\n net === 'EAI_AGAIN' ||\n net === 'ENOTFOUND'\n );\n}\n","/**\n * `AzureBlobColdDriver` — an {@link IColdDriver} over Azure Blob Storage (Phase 7).\n *\n * Uses the official `@azure/storage-blob`, an **optional peer dependency** — only consumers of\n * `cloud-roaring/azure` install it. A `ContainerClient` is **injected** (dependency injection): the driver owns\n * no credential/account/endpoint logic, so it's thin, reuses the caller's client, and is testable against the\n * Azurite emulator (point a `ContainerClient` at its connection string). The container must already exist —\n * the Azure analogue of the S3 bucket / GCS bucket. (Unlike the S3/GCS drivers, which inject a top-level\n * client + a `bucket` *name*, Azure's `ContainerClient` is already container-scoped, so there is no separate\n * container-name option — the caller scopes it. This is the idiomatic Azure handle and keeps the driver pure DI.)\n *\n * Generations are write-once immutable blobs: the conditional **`ifNoneMatch: '*'`** (\"create only if it does\n * not exist\") makes the publish atomic — a second write to the same blob fails with 409 `BlobAlreadyExists` →\n * {@link WriteConflictError}, never a silent overwrite (C13), the Azure analogue of S3's `If-None-Match: *`,\n * GCS's `ifGenerationMatch: 0`, and LocalFs's atomic `link`. **Empirically verified against Azurite** that the\n * precondition is enforced on BOTH upload paths below. **Writes stream in constant memory** (Phase 4f model):\n * a small blob is a single conditional `upload`; a larger one is **staged as blocks** (each `stageBlock`\n * flushes and frees ~one block) finished with a conditional `commitBlockList`, so the daemon's write footprint\n * stays ~one block regardless of segment size, up to the advertised `maxObjectBytes`. Drivers may use\n * `node:crypto`; only `core/` is bound by the determinism lint.\n */\nimport { createHash, randomBytes, type Hash } from 'node:crypto';\nimport type { ContainerClient } from '@azure/storage-blob';\nimport type { BlobSink } from '@/core/blob';\nimport {\n NotFoundError,\n TransientError,\n ValidationError,\n WriteConflictError,\n isNotFoundError,\n isValidationError,\n isWriteConflictError,\n} from '@/core/errors';\nimport type { ColdCaps, GenKey, IColdDriver, SegmentRef } from '@/core/ports';\nimport {\n coldObjectName,\n normalizeAzurePrefix,\n parseGenerationFromName,\n segmentObjectPrefix,\n} from './keys';\nimport { isConditionalConflict, isInvalidRange, isNotFound, isTransient } from './azure-errors';\n\n/** Flush threshold for staged uploads: the sink buffers until `pending` reaches this, then stages it as one\n * block. A blob that never reaches it is a single conditional `upload` instead (no block-list overhead,\n * strongest write-once). It bounds peak write memory to ≈ one block *given codec-granularity writes* (the\n * `.crbm` codec writes small per-chunk buffers, so a block ≈ this size); a single larger `write()` becomes one\n * larger block. Default 8 MiB (≈ S3's part). */\nconst AZURE_BLOCK_BYTES = 8 * 1024 * 1024;\n/** Azure hard limit: a block blob has at most 50,000 committed blocks. This × the block size is the ceiling. */\nconst AZURE_MAX_BLOCKS = 50_000;\n\nconst OCTET_STREAM = { blobContentType: 'application/octet-stream' } as const;\n/** Write-once precondition shared by both upload paths: create only if the blob is absent. */\nconst IF_ABSENT = { conditions: { ifNoneMatch: '*' }, blobHTTPHeaders: OCTET_STREAM } as const;\n\nexport interface AzureBlobColdDriverOptions {\n /** A constructed `@azure/storage-blob` `ContainerClient`, scoped to an existing container (point it at\n * Azurite's connection string for local/integration use). */\n readonly containerClient: ContainerClient;\n /** Optional blob-name prefix under which all objects live (e.g. `cloudroaring/`). */\n readonly prefix?: string;\n /**\n * Largest blob this driver will write/advertise. Default = `blockBytes × 50,000` (≈ 400 GiB at the default\n * 8 MiB block) — the honest ceiling reachable within Azure's 50,000-block limit. Set it higher and\n * `blockBytes` auto-grows so 50,000 blocks still cover it (raising peak write memory to ~one block).\n */\n readonly maxObjectBytes?: number;\n /** Staged block size in bytes (default 8 MiB). Tunes peak write memory. */\n readonly blockBytes?: number;\n}\n\nexport class AzureBlobColdDriver implements IColdDriver {\n private readonly container: ContainerClient;\n private readonly prefix: string | undefined;\n private readonly maxObjectBytes: number;\n private readonly blockBytes: number;\n\n constructor(options: AzureBlobColdDriverOptions) {\n this.container = options.containerClient;\n this.prefix = normalizeAzurePrefix(options.prefix);\n // Fail fast at the boundary: nullish-coalescing only guards `undefined`, so an explicit 0 / negative /\n // fractional value would otherwise slip through and silently reject every write (cap) or corrupt the flush\n // threshold (block size).\n const requestedBlock = options.blockBytes ?? AZURE_BLOCK_BYTES;\n if (!Number.isSafeInteger(requestedBlock) || requestedBlock < 1) {\n throw new ValidationError(\n `blockBytes must be a positive safe integer; got ${requestedBlock}`,\n );\n }\n if (\n options.maxObjectBytes !== undefined &&\n (!Number.isSafeInteger(options.maxObjectBytes) || options.maxObjectBytes < 1)\n ) {\n throw new ValidationError(\n `maxObjectBytes must be a positive safe integer; got ${options.maxObjectBytes}`,\n );\n }\n // Default the object cap to what the requested block size can cover within the 50,000-block limit; if a\n // larger cap is requested, grow the block size so the advertised cap stays honest (and reachable).\n this.maxObjectBytes = options.maxObjectBytes ?? requestedBlock * AZURE_MAX_BLOCKS;\n this.blockBytes = Math.max(requestedBlock, Math.ceil(this.maxObjectBytes / AZURE_MAX_BLOCKS));\n }\n\n capabilities(): ColdCaps {\n return { rangeRead: true, maxObjectBytes: this.maxObjectBytes, conditionalPut: true };\n }\n\n private blob(name: string) {\n return this.container.getBlockBlobClient(name);\n }\n\n async putImmutable(\n key: GenKey,\n write: (sink: BlobSink) => Promise<void>,\n ): Promise<{ size: number; sha256: string }> {\n const objectName = coldObjectName(this.prefix, key); // validates ref + generation\n const sink = new AzureBlockBlobSink(\n this.blob(objectName),\n this.blockBytes,\n this.maxObjectBytes,\n );\n try {\n await write(sink);\n return await sink.finish();\n } catch (err) {\n // A lost write-once race — the blob already existed, so `ifNoneMatch: '*'` failed (409). Correctness is\n // safe with no explicit teardown: staged-but-uncommitted blocks are never a visible blob (a reader only\n // ever sees a committed block list). Azure has no \"abort block list\" API (unlike S3's AbortMultipartUpload\n // / GCS's stream.destroy), so on the staged path they linger as *billed* storage until Azure's uncommitted-\n // block GC reaps them (~7 days). Recommend a container lifecycle rule to auto-delete uncommitted blocks\n // (operational notes); such races are rare (a compaction lease already\n // serializes the normal path).\n if (isConditionalConflict(err)) {\n throw new WriteConflictError(\n `generation already exists (write-once): ${key.segment}.${key.generation}`,\n );\n }\n if (isValidationError(err) || isWriteConflictError(err) || isNotFoundError(err)) throw err;\n throw this.mapError(err);\n }\n }\n\n async getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array> {\n if (!Number.isInteger(offset) || !Number.isInteger(length) || offset < 0 || length < 0) {\n throw new ValidationError(`invalid range offset=${offset} length=${length}`);\n }\n if (length === 0) return new Uint8Array(0);\n const objectName = coldObjectName(this.prefix, key);\n try {\n const res = await this.blob(objectName).download(offset, length);\n const bytes = await collect(res.readableStreamBody);\n // A short read means the range ran past EOF — treat as out-of-bounds, never a partial result. (Azurite\n // returns a clamped-short body here rather than a 416; a start fully past EOF does 416 → mapReadError.)\n if (bytes.length !== length) {\n throw new ValidationError(\n `range [${offset}, ${offset + length}) out of bounds (got ${bytes.length}B)`,\n );\n }\n return bytes;\n } catch (err) {\n throw this.mapReadError(err, key);\n }\n }\n\n async getTail(key: GenKey, maxBytes: number): Promise<{ bytes: Uint8Array; size: number }> {\n const objectName = coldObjectName(this.prefix, key);\n try {\n // Two round-trips (properties for the size, then a ranged download) vs S3's one (suffix-range +\n // Content-Range). This is on the per-*generation* open path, which the reader caches — NOT the per-op hot\n // path (add/has/count/intersect) — so it's amortized; collapsing to one RT is a tracked follow-up.\n const props = await this.blob(objectName).getProperties();\n const size = props.contentLength ?? 0;\n if (!Number.isSafeInteger(size) || size < 0) {\n throw new ValidationError(`Azure returned an invalid blob size: ${String(size)}`);\n }\n if (maxBytes <= 0 || size === 0) return { bytes: new Uint8Array(0), size };\n const take = Math.min(maxBytes, size);\n const res = await this.blob(objectName).download(size - take, take);\n return { bytes: await collect(res.readableStreamBody), size };\n } catch (err) {\n throw this.mapReadError(err, key);\n }\n }\n\n async delete(key: GenKey): Promise<void> {\n // Idempotent: `deleteIfExists` is a no-op (no throw) on an absent blob, so a racing/retried GC sweep is safe.\n try {\n await this.blob(coldObjectName(this.prefix, key)).deleteIfExists();\n } catch (err) {\n throw this.mapError(err);\n }\n }\n\n async *list(ref: SegmentRef): AsyncIterable<GenKey> {\n const prefix = segmentObjectPrefix(this.prefix, ref); // validates ref\n try {\n // The async paging iterator drains every page; a segment has few generations, so the set is small.\n for await (const item of this.container.listBlobsFlat({ prefix })) {\n const generation = parseGenerationFromName(prefix, item.name);\n if (generation !== null) {\n yield { namespace: ref.namespace, segment: ref.segment, generation };\n }\n }\n } catch (err) {\n throw this.mapError(err);\n }\n }\n\n /** Map Azure read errors to the driver vocabulary; pass everything else through {@link mapError}. */\n private mapReadError(err: unknown, key: GenKey): unknown {\n if (isValidationError(err)) return err;\n if (isNotFound(err))\n return new NotFoundError(`no such generation: ${key.segment}.${key.generation}`);\n // A fully out-of-range request (start past EOF) — the BlobReader contract treats range errors as\n // ValidationError, never a short/empty read.\n if (isInvalidRange(err)) {\n return new ValidationError(`range out of bounds for ${key.segment}.${key.generation}`);\n }\n return this.mapError(err);\n }\n\n /**\n * Reclassify a transient Azure fault (throttle/5xx/dropped connection) as a retryable {@link TransientError}\n * so the retry decorator can ride it out; everything else propagates unchanged. The final fallback at every\n * client-call site, so callers + the decorator only ever see typed errors.\n */\n private mapError(err: unknown): unknown {\n if (isTransient(err)) {\n return new TransientError(\n `transient Azure fault: ${(err as { code?: unknown } | null)?.code ?? 'unknown'}`,\n { cause: err },\n );\n }\n return err;\n }\n}\n\ntype BlockBlob = ReturnType<ContainerClient['getBlockBlobClient']>;\n\n/** Concatenate byte chunks of known total length into one buffer. */\nfunction concatBytes(parts: readonly Uint8Array[], total: number): Uint8Array {\n const out = new Uint8Array(total);\n let offset = 0;\n for (const p of parts) {\n out.set(p, offset);\n offset += p.length;\n }\n return out;\n}\n\n/**\n * Streaming {@link BlobSink} that uploads one Azure block blob in **constant memory**. It buffers at most one\n * block: as the codec writes, full blocks are staged via `stageBlock` and freed (the awaited request is the\n * natural backpressure). A small blob that never reaches one block is committed as a single conditional\n * `upload`; a larger one is finished with a conditional `commitBlockList` — **both enforce write-once** via\n * `ifNoneMatch: '*'`. SHA-256 is hashed incrementally.\n */\nclass AzureBlockBlobSink implements BlobSink {\n private readonly hash: Hash = createHash('sha256');\n private readonly pending: Uint8Array[] = [];\n private pendingLen = 0;\n private total = 0;\n private readonly blockIds: string[] = [];\n /**\n * A random per-sink nonce folded into every block id. Azure pools **uncommitted** blocks per *blob name*,\n * keyed by id — so two writers racing the same generation key on the staged path MUST use disjoint id spaces,\n * else one would overwrite the other's staged blocks and a commit could reference an interleaved mix of both\n * writers' bytes (a corrupt blob whose committed content wouldn't match the returned SHA-256). A per-instance\n * nonce makes each writer's ids unique, so the winner commits only its own blocks and the loser cleanly 409s.\n * 6 random bytes → 12 fixed hex chars, keeping every id equal-length (Azure's within-blob id requirement).\n */\n private readonly uploadNonce = randomBytes(6).toString('hex');\n\n constructor(\n private readonly blob: BlockBlob,\n private readonly blockBytes: number,\n private readonly maxObjectBytes: number,\n ) {}\n\n /** Fixed-width (constant length within a blob) AND per-sink-unique block id: `<nonce>-<zero-padded index>`. */\n private blockId(n: number): string {\n return Buffer.from(`${this.uploadNonce}-${String(n).padStart(6, '0')}`).toString('base64');\n }\n\n async write(bytes: Uint8Array): Promise<void> {\n if (bytes.length === 0) return;\n this.total += bytes.length;\n if (this.total > this.maxObjectBytes) {\n // Fail fast + typed, rather than a late opaque Azure error.\n throw new ValidationError(`object exceeds maxObjectBytes ${this.maxObjectBytes}`);\n }\n this.hash.update(bytes);\n // Copy on retain: the SHA-256 is committed to these exact bytes now, but they sit in `pending` across the\n // `write()` boundary until the next flush — the `BlobSink` contract (see core/blob.ts) lets a caller reuse\n // its buffer after `write()` resolves, so a shared reference could let the committed content diverge from\n // the already-hashed bytes. `new Uint8Array(bytes)` copies UNCONDITIONALLY — note `bytes.slice()` would NOT:\n // the payload is a `Buffer` (roaring serialize), whose `slice()` returns an aliasing view, not a copy.\n // (S3/GCS + `BufferSink` share this retain-by-reference pattern; tracked as a cross-driver follow-up.)\n this.pending.push(new Uint8Array(bytes));\n this.pendingLen += bytes.length;\n if (this.pendingLen >= this.blockBytes) await this.flushBlock();\n }\n\n /** Stage the buffered bytes (≥ one block) as a single block, freeing them. */\n private async flushBlock(): Promise<void> {\n if (this.blockIds.length >= AZURE_MAX_BLOCKS) {\n // Unreachable for valid input (the maxObjectBytes byte-cap, sized to ≤ 50,000 blocks, fires first) — a\n // typed guard so the Azure hard limit is never a raw 4xx.\n throw new ValidationError(`upload exceeded the Azure ${AZURE_MAX_BLOCKS}-block limit`);\n }\n const body = concatBytes(this.pending, this.pendingLen);\n this.pending.length = 0;\n this.pendingLen = 0;\n const id = this.blockId(this.blockIds.length);\n await this.blob.stageBlock(id, body, body.length);\n this.blockIds.push(id);\n }\n\n /** Commit the blob: a single conditional `upload` if it fit in one block, else commit the staged block list. */\n async finish(): Promise<{ size: number; sha256: string }> {\n const sha256 = this.hash.digest('hex');\n if (this.blockIds.length === 0) {\n const body = concatBytes(this.pending, this.pendingLen);\n await this.blob.upload(body, body.length, IF_ABSENT); // write-once (single-shot path)\n return { size: this.total, sha256 };\n }\n if (this.pendingLen > 0) await this.flushBlock(); // the final block may be < blockBytes (allowed)\n await this.blob.commitBlockList(this.blockIds, IF_ABSENT); // write-once (staged path)\n return { size: this.total, sha256 };\n }\n}\n\n/** Collect an Azure download's Node readable body into a `Uint8Array`. */\nasync function collect(body: NodeJS.ReadableStream | undefined): Promise<Uint8Array> {\n if (body === undefined) {\n throw new NotFoundError('Azure download returned no body');\n }\n const chunks: Uint8Array[] = [];\n let total = 0;\n for await (const chunk of body) {\n const u8 =\n typeof chunk === 'string' ? new TextEncoder().encode(chunk) : new Uint8Array(chunk as Buffer);\n chunks.push(u8);\n total += u8.length;\n }\n return concatBytes(chunks, total);\n}\n"]}