@cloudbitmaps/gcs 0.18.1 → 0.18.3

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.
package/dist/index.js CHANGED
@@ -23,23 +23,8 @@ import {
23
23
  prefixPart,
24
24
  validateSegmentRef
25
25
  } from "@cloudbitmaps/core/driver-kit";
26
+ import { normalizeObjectPrefix } from "@cloudbitmaps/core/driver-kit";
26
27
  var SUFFIX = ".crbm";
27
- function normalizeGcsPrefix(prefix) {
28
- if (prefix === void 0) return void 0;
29
- for (const ch of prefix) {
30
- if (ch.charCodeAt(0) < 32) {
31
- throw new ValidationError("prefix must not contain control characters");
32
- }
33
- }
34
- for (const segment of prefix.split("/")) {
35
- if (segment === "." || segment === "..") {
36
- throw new ValidationError(
37
- `prefix must not contain "." or ".." path segments: ${JSON.stringify(prefix)}`
38
- );
39
- }
40
- }
41
- return prefix;
42
- }
43
28
  function segmentObjectPrefix(prefix, ref) {
44
29
  validateSegmentRef(ref);
45
30
  return `${prefixPart(prefix)}${namespaceKeyPart(ref.namespace)}/segments/${encodeNameForKey(ref.segment)}.`;
@@ -211,6 +196,86 @@ async function retryDownload(download, deadline) {
211
196
  }
212
197
  }
213
198
 
199
+ // src/scrub-error.ts
200
+ var DROPPED = /* @__PURE__ */ new Set([
201
+ "request",
202
+ "agent",
203
+ "socket",
204
+ "_httpMessage",
205
+ "connection",
206
+ "req",
207
+ "res",
208
+ "config"
209
+ ]);
210
+ var CREDENTIAL_NAME = /^(?:proxy-)?authorization$|^cookie$|^set-cookie$|^x-api-key$/i;
211
+ var CREDENTIAL_LINES = /^((?:proxy-)?authorization|cookie|set-cookie|x-api-key):[^\r\n]*/gim;
212
+ var REDACTED = "[redacted]";
213
+ var isPlain = (value) => {
214
+ const proto = Object.getPrototypeOf(value);
215
+ return proto === Object.prototype || proto === null;
216
+ };
217
+ function responseSummary(response) {
218
+ const out = {};
219
+ for (const key of ["status", "statusText"]) {
220
+ const value = response[key];
221
+ if (typeof value === "number" || typeof value === "string") out[key] = value;
222
+ }
223
+ return out;
224
+ }
225
+ function clean(value, copies) {
226
+ if (typeof value === "string") return value.replace(CREDENTIAL_LINES, `$1: ${REDACTED}`);
227
+ if (typeof value !== "object" || value === null) {
228
+ return typeof value === "function" || typeof value === "symbol" ? void 0 : value;
229
+ }
230
+ if (copies.has(value)) return copies.get(value);
231
+ if (Array.isArray(value)) {
232
+ const list = [];
233
+ copies.set(value, list);
234
+ for (const item of value) list.push(clean(item, copies));
235
+ return list;
236
+ }
237
+ const isError = value instanceof Error;
238
+ if (!isError && !isPlain(value)) return void 0;
239
+ const copy = Object.create(Object.getPrototypeOf(value));
240
+ copies.set(value, copy);
241
+ for (const key of Reflect.ownKeys(value)) {
242
+ if (typeof key === "string" && DROPPED.has(key)) continue;
243
+ let held;
244
+ try {
245
+ held = value[key];
246
+ } catch {
247
+ continue;
248
+ }
249
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
250
+ let kept;
251
+ if (typeof key === "string" && CREDENTIAL_NAME.test(key)) kept = held == null ? held : REDACTED;
252
+ else if (key === "response" && typeof held === "object" && held !== null) {
253
+ kept = responseSummary(held);
254
+ } else kept = clean(held, copies);
255
+ if (kept === void 0 && held !== void 0) continue;
256
+ Object.defineProperty(copy, key, {
257
+ value: kept,
258
+ writable: true,
259
+ configurable: true,
260
+ enumerable: descriptor?.enumerable ?? true
261
+ });
262
+ }
263
+ return copy;
264
+ }
265
+ function holdsTransport(err, seen) {
266
+ if (seen.has(err)) return false;
267
+ seen.add(err);
268
+ if (Object.getOwnPropertyNames(err).some((key) => DROPPED.has(key) || key === "response")) {
269
+ return true;
270
+ }
271
+ const cause = err.cause;
272
+ return typeof cause === "object" && cause !== null && holdsTransport(cause, seen);
273
+ }
274
+ function scrubCredentials(err) {
275
+ if (typeof err !== "object" || err === null) return err;
276
+ return holdsTransport(err, /* @__PURE__ */ new Set()) ? clean(err, /* @__PURE__ */ new WeakMap()) : err;
277
+ }
278
+
214
279
  // src/send-once.ts
215
280
  import { once } from "node:events";
216
281
  async function saveOnce(file, body, options) {
@@ -313,7 +378,7 @@ var GcsStorageDriver = class {
313
378
  this.clock = options.clock ?? REAL_TIME;
314
379
  this.readStorage = options.readStorage ?? options.storage;
315
380
  this.bucket = options.bucket;
316
- this.prefix = normalizeGcsPrefix(options.prefix);
381
+ this.prefix = normalizeObjectPrefix(options.prefix);
317
382
  for (const [name, value] of [
318
383
  ["maxObjectBytes", options.maxObjectBytes],
319
384
  ["simpleUploadThresholdBytes", options.simpleUploadThresholdBytes]
@@ -532,6 +597,7 @@ var GcsStorageDriver = class {
532
597
  }
533
598
  /** Map GCS read errors to the driver vocabulary; pass everything else through {@link mapError}. */
534
599
  mapReadError(err, key) {
600
+ err = scrubCredentials(err);
535
601
  if (isValidationError(err)) return err;
536
602
  if (isNotFound(err))
537
603
  return new NotFoundError(`no such generation: ${key.segment}.${key.generation}`);
@@ -556,6 +622,7 @@ var GcsStorageDriver = class {
556
622
  * in transit arrive typed, and anything else (a checksum mismatch, a credentials failure) as the SDK raised it.
557
623
  */
558
624
  mapError(err) {
625
+ err = scrubCredentials(err);
559
626
  if (isTransient(err)) {
560
627
  return new TransientError(
561
628
  `transient GCS fault: ${err?.code ?? "unknown"}`,
@@ -709,7 +776,7 @@ import {
709
776
  TransientError as TransientError2,
710
777
  ValidationError as ValidationError4,
711
778
  WriteConflictError as WriteConflictError2,
712
- normalizeObjectPrefix
779
+ normalizeObjectPrefix as normalizeObjectPrefix2
713
780
  } from "@cloudbitmaps/core/driver-kit";
714
781
  var GcsRegistryStore = class {
715
782
  constructor(storage, readStorage, bucket, readTimeoutMs, conditionalDelete) {
@@ -810,6 +877,7 @@ function generationFence(version, key) {
810
877
  return generation;
811
878
  }
812
879
  function mapReadError(err) {
880
+ err = scrubCredentials(err);
813
881
  if (err instanceof ReadTimedOut) return new TransientError2(err.message, { cause: err });
814
882
  if (isTransportFault(err)) {
815
883
  return new TransientError2(`transient GCS fault: ${String(err.code)}`, {
@@ -819,6 +887,7 @@ function mapReadError(err) {
819
887
  return mapError(err);
820
888
  }
821
889
  function mapError(err) {
890
+ err = scrubCredentials(err);
822
891
  if (isTransient(err)) {
823
892
  return new TransientError2(
824
893
  `transient GCS fault: ${err?.code ?? err?.name ?? "unknown"}`,
@@ -846,7 +915,7 @@ var GcsRegistryDriver = class extends ObjectStoreRegistry {
846
915
  resolveReadTimeoutMs(options.readTimeoutMs),
847
916
  resolveConditionalDelete(options.conditionalDelete)
848
917
  ),
849
- normalizeObjectPrefix(options.prefix),
918
+ normalizeObjectPrefix2(options.prefix),
850
919
  options.now ?? (() => Date.now())
851
920
  );
852
921
  }
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/backend.ts", "../src/storage.ts", "../src/keys.ts", "../src/gcs-errors.ts", "../src/read-timeout.ts", "../src/download-retry.ts", "../src/send-once.ts", "../src/read-once.ts", "../src/registry.ts"],
4
- "sourcesContent": ["/**\n * `GcsStorage` \u2014 the Google Cloud Storage backend as one object: generations and pointer, one bucket, stated once.\n *\n * See {@link StorageBackend} for why the two halves are configured together rather than separately.\n *\n * **A note on the word.** `@google-cloud/storage` names its own client class `Storage`, so the driver option\n * that takes it is also called `storage` \u2014 which, since this library calls its durable tier storage too, reads\n * as the same word meaning two things. That is why this class builds the client itself: `new GcsStorage({ bucket })`\n * never makes you write `{ storage: storage }`. Supply `client` when you need your own.\n */\nimport { STORAGE_BACKEND, ValidationError, brandAsBackend } from '@cloudbitmaps/core/driver-kit';\nimport type {\n IRegistryDriver,\n IStorageDriver,\n StorageBackend,\n} from '@cloudbitmaps/core/driver-kit';\nimport { Storage as GcsClient } from '@google-cloud/storage';\nimport { GcsStorageDriver } from './storage';\nimport { GcsRegistryDriver } from './registry';\n\nexport interface GcsStorageOptions {\n /** Target bucket (must already exist). */\n readonly bucket: string;\n /** Optional object-name prefix under which everything lives \u2014 generations and the registry alike. */\n readonly prefix?: string;\n /**\n * A constructed `@google-cloud/storage` client. One is built from the ambient credentials when absent. Its retry\n * options apply to every request except the single-request conditional writes, which the driver sends without\n * that retry whatever they say: a registry row once, and a generation's object again only after a throttle, under\n * its own backoff. In `@google-cloud/storage` 7.x and 8.x a download the SDK retries after a 408, 429, 500, 502, 503 or 504 can crash the process\n * with `ERR_STREAM_UNABLE_TO_PIPE`, so build it with `retryOptions: { autoRetry: false }`; the driver retries\n * downloads itself. That also turns off the SDK's retries of listings, metadata reads and resumable uploads on\n * this client, which the store does not retry. The client built here needs none of this: only its downloads are sent once.\n */\n readonly client?: GcsClient;\n /** Project id for the client built when `client` is absent (refused beside `client`). Falls back to the SDK's own resolution. */\n readonly projectId?: string;\n /** Endpoint override \u2014 point it at fake-gcs-server locally. Refused beside `client`, which carries its own. */\n readonly apiEndpoint?: string;\n /** Largest object the backend will write and advertise (default = GCS's 5 TiB max). Must be a positive safe integer. */\n readonly maxObjectBytes?: number;\n /** Bytes at/under which a single non-resumable upload is used instead of a resumable stream (default 8 MiB).\n * Must be a positive safe integer. */\n readonly simpleUploadThresholdBytes?: number;\n /** Injected clock for the registry's `createdAt`/`updatedAt`; defaults to `Date.now`. */\n readonly now?: () => number;\n /**\n * Cut off a read that has run this long, in milliseconds. `0`, the default, sets no timeout. A client's own `timeout`\n * does not bound a download on `@google-cloud/storage` 8.x (the SDK hands it to an HTTP client that has no such\n * option), so this is what does.\n *\n * It bounds each read as a whole \u2014 a generation's tail (with the metadata read it falls back on for an empty\n * object), a range of it, a registry row \u2014 with one deadline across every attempt the driver makes and the backoff\n * between them. The clock starts at the call into the driver, so a credential fetch and any wait for a socket count\n * (Node's agents set no socket limit unless your process sets one), and runs until the whole body has arrived. It\n * counts time the process spends busy too: Node runs a due timer before it reads a socket, so a synchronous stretch\n * longer than the timeout fails the reads in flight even when their responses have arrived. When it passes, the read\n * throws `TransientError` naming the read and the timeout and no further attempt starts, for the store's read retry\n * to run again: about 8.35 s in all at `2_000` with the default retry policy. Uploads, deletes, listings and the\n * conditional writes are not timed. The SDK cannot cancel a request whose response has not begun, so a read timed\n * out before its server answers leaves that connection open until the server answers or closes it: one per read, up\n * to four per call through the store's retry. A non-negative safe integer no larger than 2,147,483,647.\n */\n readonly readTimeoutMs?: number;\n /**\n * Whether the registry removes a deleted row for good, by an object delete sent with `ifGenerationMatch`, rather\n * than leaving a tombstone a full listing reads forever. Defaults to `false`, on the public endpoint too: whether real\n * GCS applies `ifGenerationMatch` to a delete has not been verified by a run against the service, and fake-gcs-server\n * accepts the precondition and ignores it, so CI cannot show it. Set `true` to remove rows for good.\n */\n readonly conditionalDelete?: boolean;\n}\n\n/**\n * The keys `new GcsStorage(options)` takes. Any other is refused by name rather than ignored: an ignored client key\n * \u2014 `storage`, as the lower-level storage driver calls it \u2014 falls back to ambient credentials and the **public** endpoint, and\n * for a client pointed at an emulator that is production traffic from a wiring typo.\n */\nexport const GCS_STORAGE_OPTION_KEYS = [\n 'bucket',\n 'prefix',\n 'client',\n 'projectId',\n 'apiEndpoint',\n 'maxObjectBytes',\n 'simpleUploadThresholdBytes',\n 'now',\n 'readTimeoutMs',\n 'conditionalDelete',\n] as const;\n\n/** The settings that build a client, which a supplied `client` already carries and so cannot be given beside. */\nconst CLIENT_SETTINGS = ['projectId', 'apiEndpoint'] as const;\n\n/** Refuse an options bag that is not an object, or that holds a key not in `keys`, naming each such key. */\nfunction refuseUnknown(\n name: string,\n options: unknown,\n keys: readonly string[],\n hint: string,\n): void {\n if (options === null || typeof options !== 'object') {\n throw new ValidationError(\n `${name} needs an options object \u2014 got ${options === null ? 'null' : typeof options}`,\n );\n }\n const unknown = Object.keys(options).filter((k) => !keys.includes(k));\n if (unknown.length > 0) {\n const list = (ks: readonly string[]): string => ks.map((k) => `\\`${k}\\``).join(', ');\n throw new ValidationError(\n `${name} does not take ${list(unknown)}. It takes ${list(keys)}; ${hint}.`,\n );\n }\n}\n\nexport class GcsStorage implements StorageBackend {\n /** Cross-bundle brand, stamped non-enumerably in the constructor so a spread cannot carry it. */\n declare readonly [STORAGE_BACKEND]: true;\n readonly storage: IStorageDriver;\n readonly registry: IRegistryDriver;\n /** The client both halves share \u2014 built here unless one was supplied. */\n readonly client: GcsClient;\n\n constructor(options: GcsStorageOptions) {\n let readClient: GcsClient;\n refuseUnknown(\n 'GcsStorage',\n options,\n GCS_STORAGE_OPTION_KEYS,\n 'a @google-cloud/storage client goes in `client`',\n );\n if (options.client !== undefined && options.client !== null) {\n // A supplied client already carries its project and endpoint, so a setting beside it is ignored, and\n // ignoring it leaves the store talking to somewhere the caller did not mean: an `apiEndpoint` meant for an\n // emulator, silently dropped, is production traffic from a client that was built for the public endpoint.\n // Refuse instead of picking one.\n const ignored = CLIENT_SETTINGS.filter((k) => options[k] !== undefined);\n if (ignored.length > 0) {\n throw new ValidationError(\n `GcsStorage takes \\`client\\` OR ${CLIENT_SETTINGS.map((k) => `\\`${k}\\``).join(' / ')}, not both \u2014 ` +\n `got \\`client\\` with ${ignored.map((k) => `\\`${k}\\``).join(', ')}; ` +\n 'the `client` already carries them, so configure them on the client, or drop `client`',\n );\n }\n this.client = options.client;\n readClient = options.client;\n } else {\n const settings = {\n ...(options.projectId === undefined ? {} : { projectId: options.projectId }),\n ...(options.apiEndpoint === undefined ? {} : { apiEndpoint: options.apiEndpoint }),\n };\n this.client = new GcsClient(settings);\n // A second client, for downloads only. `@google-cloud/storage` 7.x and 8.x retry a failed download by default, and\n // when the retried request succeeds they throw `ERR_STREAM_UNABLE_TO_PIPE` outside any promise, which ends the\n // process whatever the caller wrote around the call. A download is sent once instead, and the driver retries it\n // itself (`download-retry.ts`). Everything else keeps the default retries: a resumable upload's session, a\n // listing, a metadata read, none of which the store retries on its own. Two clients mean two `GoogleAuth`\n // instances: lazy, so the first download makes a second token fetch, and a second hourly refresh follows.\n readClient = new GcsClient({ ...settings, retryOptions: { autoRetry: false } });\n }\n const shared = {\n storage: this.client,\n readStorage: readClient,\n bucket: options.bucket,\n ...(options.prefix === undefined ? {} : { prefix: options.prefix }),\n ...(options.readTimeoutMs === undefined ? {} : { readTimeoutMs: options.readTimeoutMs }),\n };\n this.storage = new GcsStorageDriver({\n ...shared,\n ...(options.maxObjectBytes === undefined ? {} : { maxObjectBytes: options.maxObjectBytes }),\n ...(options.simpleUploadThresholdBytes === undefined\n ? {}\n : { simpleUploadThresholdBytes: options.simpleUploadThresholdBytes }),\n });\n this.registry = new GcsRegistryDriver({\n ...shared,\n ...(options.now === undefined ? {} : { now: options.now }),\n ...(options.conditionalDelete === undefined\n ? {}\n : { conditionalDelete: options.conditionalDelete }),\n });\n brandAsBackend(this);\n }\n}\n", "/**\n * `GcsStorageDriver` \u2014 an {@link IStorageDriver} over Google Cloud Storage.\n *\n * Uses the official `@google-cloud/storage`, a real dependency of this package \u2014 installing\n * `@cloudbitmaps/gcs` is what installs it. The `Storage` client is **injected** (dependency injection): the driver owns\n * no credential/project/endpoint logic, so it's thin, reuses the caller's client, and is testable against the\n * `fake-gcs-server` emulator (point a `Storage` at its `apiEndpoint`).\n *\n * Generations are write-once immutable objects: a resumable upload with the **`ifGenerationMatch: 0`**\n * precondition (\"create only if it does not exist\") makes the publish atomic \u2014 a second write to the same\n * object fails with 412 \u2192 {@link WriteConflictError}, never a silent overwrite (hard invariant 2: storage\n * objects are immutable and never overwritten in place), the GCS analogue of S3's `If-None-Match: *` and\n * LocalFs's atomic `link`. **Writes stream in constant memory:** the codec's bytes are\n * piped into a GCS resumable-upload `Writable` (chunked/freed by the SDK as they go), so a load's write\n * footprint stays bounded regardless of segment size, up to the advertised `maxObjectBytes`. Every object carries a\n * random write id in its custom metadata (`cbwid`), so a `412` that may be the write meeting itself can be read back:\n * a resumable upload's session is retried by the SDK, and a single-request upload is sent again by the driver after a\n * `429` or `503`, at most {@link THROTTLE_RESENDS} times with backoff, and after nothing else. Drivers may use\n * `node:crypto`; only `core/` is bound by the determinism lint.\n */\nimport {\n NotFoundError,\n TransientError,\n ValidationError,\n WriteConflictError,\n isNotFoundError,\n isValidationError,\n isWriteConflictError,\n} from '@cloudbitmaps/core/driver-kit';\nimport type {\n BlobSink,\n GenKey,\n IStorageDriver,\n SegmentRef,\n StorageCaps,\n} from '@cloudbitmaps/core/driver-kit';\nimport { createHash, randomBytes, type Hash } from 'node:crypto';\nimport type { Writable } from 'node:stream';\nimport { once } from 'node:events';\nimport type { Storage } from '@google-cloud/storage';\nimport {\n storageObjectName,\n normalizeGcsPrefix,\n parseGenerationFromName,\n segmentObjectPrefix,\n} from './keys';\nimport {\n isInvalidRange,\n isNotFound,\n isPreconditionFailed,\n isThrottle,\n isTransient,\n isTransportFault,\n} from './gcs-errors';\nimport { retryDownload } from './download-retry';\nimport { saveOnce } from './send-once';\nimport { downloadFile, readOnce, singleHeader, type ObjectRead } from './read-once';\nimport {\n ReadTimedOut,\n resolveReadTimeoutMs,\n startDeadline,\n withDeadline,\n type Deadline,\n} from './read-timeout';\n\n/** Default object ceiling: GCS's 5 TiB per-object hard max. Set lower to fail fast on a runaway write. */\nconst DEFAULT_MAX_OBJECT_BYTES = 5 * 1024 * 1024 * 1024 * 1024;\n\n/** The custom-metadata name an object's write id is stored under. Short: it travels with every write. */\nconst WRITE_ID_KEY = 'cbwid';\n/** How many times a throttled single-request upload is sent again: four sends in all, as the SDK's own retry makes. */\nconst THROTTLE_RESENDS = 3;\n/** The backoff ceiling before the first re-send, doubling for each one after it. */\nconst THROTTLE_BASE_DELAY_MS = 500;\n\n/** What the throttle backoff waits on. */\ninterface Sleeper {\n sleep(ms: number): Promise<void>;\n}\n\nconst REAL_TIME: Sleeper = {\n sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),\n};\n\n/**\n * Objects at/under this size are uploaded in a **single simple (non-resumable) request**; larger ones switch to\n * a **resumable stream** (constant memory). Mirrors S3's single-PUT-vs-multipart split \u2014 the simple path buffers\n * only \u2264 this many bytes, the resumable path streams. Default 8 MiB (\u2248 S3's part size), so peak write memory is\n * ~one threshold's worth regardless of object size.\n */\nconst DEFAULT_UPLOAD_THRESHOLD_BYTES = 8 * 1024 * 1024;\n\nexport interface GcsStorageDriverOptions {\n /** A constructed `@google-cloud/storage` `Storage` client (point `apiEndpoint` at fake-gcs-server locally). */\n readonly storage: Storage;\n /**\n * The client the downloads go through; `storage` when absent. `GcsStorage` passes one built with the SDK's request\n * retries off (the read client built in its constructor), because a download the SDK retries can crash the process;\n * the driver retries a download itself instead.\n */\n readonly readStorage?: Storage;\n /** Target bucket (must already exist). */\n readonly bucket: string;\n /** Optional object-name prefix under which all objects live (e.g. `cloudbitmaps/`). */\n readonly prefix?: string;\n /** Largest object this driver will write/advertise (default = GCS's 5 TiB max). Must be a positive safe integer. */\n readonly maxObjectBytes?: number;\n /** Bytes at/under which a single non-resumable upload is used instead of a resumable stream (default 8 MiB).\n * Must be a positive safe integer. */\n readonly simpleUploadThresholdBytes?: number;\n /**\n * Cut off a read that has run this long, in milliseconds: a tail read (with the metadata read it falls back on for\n * an empty object) or a range read, every attempt and the backoff between them included. `0` (the default) sets no\n * timeout. The clock starts at the call into the driver, so a credential fetch counts, and runs until the body has\n * ended; when it passes the read throws `TransientError` and no further attempt starts. Uploads, deletes and\n * listings are not timed. A non-negative safe integer no larger than 2,147,483,647.\n */\n readonly readTimeoutMs?: number;\n /** What the backoff before re-sending a throttled upload waits on; real time when absent. */\n readonly clock?: Sleeper;\n}\n\nexport class GcsStorageDriver implements IStorageDriver {\n private readonly storage: Storage;\n private readonly readStorage: Storage;\n private readonly bucket: string;\n private readonly prefix: string | undefined;\n private readonly maxObjectBytes: number;\n private readonly threshold: number;\n private readonly readTimeoutMs: number;\n private readonly clock: Sleeper;\n\n constructor(options: GcsStorageDriverOptions) {\n this.storage = options.storage;\n this.clock = options.clock ?? REAL_TIME;\n this.readStorage = options.readStorage ?? options.storage;\n this.bucket = options.bucket;\n this.prefix = normalizeGcsPrefix(options.prefix);\n // Fail fast at the boundary: `??` only guards `undefined`, so NaN, 0, a negative or a fraction would otherwise\n // reject every write (cap) or corrupt the upload-path choice (threshold).\n for (const [name, value] of [\n ['maxObjectBytes', options.maxObjectBytes],\n ['simpleUploadThresholdBytes', options.simpleUploadThresholdBytes],\n ] as const) {\n if (value !== undefined && (!Number.isSafeInteger(value) || value < 1)) {\n throw new ValidationError(`${name} must be a positive safe integer; got ${value}`);\n }\n }\n this.maxObjectBytes = options.maxObjectBytes ?? DEFAULT_MAX_OBJECT_BYTES;\n this.threshold = options.simpleUploadThresholdBytes ?? DEFAULT_UPLOAD_THRESHOLD_BYTES;\n this.readTimeoutMs = resolveReadTimeoutMs(options.readTimeoutMs);\n }\n\n capabilities(): StorageCaps {\n return { rangeRead: true, maxObjectBytes: this.maxObjectBytes, conditionalPut: true };\n }\n\n private file(name: string) {\n return this.storage.bucket(this.bucket).file(name);\n }\n\n /** The same object on the download client, off the SDK's shared agent ({@link downloadFile}). */\n private downloadable(name: string) {\n return downloadFile(this.readStorage, this.bucket, name);\n }\n\n /** The deadline a read of `key` made now runs under, every attempt included, named for its error; none when off. */\n private deadline(read: string, key: GenKey): Deadline | undefined {\n return startDeadline(this.readTimeoutMs, `${read} of ${key.segment}.${key.generation}`);\n }\n\n async putImmutable(\n key: GenKey,\n write: (sink: BlobSink) => Promise<void>,\n ): Promise<{ size: number; sha256: string }> {\n const objectName = storageObjectName(this.prefix, key); // validates ref + generation\n const sink = new GcsUploadSink(\n this.file(objectName),\n this.maxObjectBytes,\n this.threshold,\n this.clock,\n );\n try {\n await write(sink);\n return await sink.finish();\n } catch (err) {\n await sink.abort(); // best-effort teardown of the in-flight upload\n // A lost write-once race \u2014 the object already existed, so `ifGenerationMatch: 0` failed with 412.\n if (isPreconditionFailed(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 = storageObjectName(this.prefix, key);\n try {\n // One request, buffering at most the bytes asked for. GCS `end` is inclusive. `decompress: false`, as for the tail:\n // the offsets are into the bytes as stored.\n const deadline = this.deadline('range read', key);\n const res = await retryDownload(\n () =>\n readOnce(\n this.downloadable(objectName),\n { start: offset, end: offset + length - 1, decompress: false },\n length,\n () => this.badRange(key, `the response is longer than the ${length}B requested`),\n deadline,\n ),\n deadline,\n );\n this.checkRange(res, key, offset, length);\n return res.bytes;\n } catch (err) {\n throw this.mapReadError(err, key);\n }\n }\n\n /**\n * Refuse a range response unless it is exactly the bytes asked for. A 206 must name them in `Content-Range: bytes\n * a-b/total`, starting at `offset`, and hold that many; a 200 (a server that ignored the range) is the whole object,\n * which is the range only when the range starts at 0. A short read means the range ran past the end of the object:\n * out of bounds, never a partial result.\n */\n private checkRange(res: ObjectRead, key: GenKey, offset: number, length: number): void {\n const received = res.bytes.length;\n if (res.status === 200) {\n if (offset !== 0)\n throw this.badRange(key, `whole object returned for a range from ${offset}`);\n } else if (res.status === 206) {\n const header = singleHeader(res.headers, 'content-range');\n const m = header === undefined ? null : /^bytes (\\d+)-(\\d+)\\/(\\d+)$/.exec(header);\n if (m === null) throw this.badRange(key, 'Content-Range is missing or malformed');\n const [first, last, total] = [Number(m[1]), Number(m[2]), Number(m[3])];\n if (![first, last, total].every(Number.isSafeInteger)) {\n throw this.badRange(key, 'Content-Range holds a number that is not a safe integer');\n }\n if (first !== offset || last - first + 1 !== received || last >= total) {\n throw this.badRange(\n key,\n `Content-Range ${first}-${last}/${total} disagrees with ${received}B received from ${offset}`,\n );\n }\n } else {\n throw this.badRange(key, `unexpected HTTP ${res.status}`);\n }\n if (received !== length) {\n throw new ValidationError(\n `range [${offset}, ${offset + length}) out of bounds (got ${received}B)`,\n );\n }\n }\n\n private badRange(key: GenKey, why: string): ValidationError {\n return new ValidationError(\n `GCS range read of ${key.segment}.${key.generation} refused: ${why}`,\n );\n }\n\n async getTail(key: GenKey, maxBytes: number): Promise<{ bytes: Uint8Array; size: number }> {\n const objectName = storageObjectName(this.prefix, key);\n // One deadline for the whole call: the tail's attempts, and the metadata read it may fall back on.\n const deadline = this.deadline('tail read', key);\n try {\n // Nothing to read: only the size is wanted, which the metadata answers in one request.\n if (maxBytes <= 0)\n return { bytes: new Uint8Array(0), size: await this.sizeOf(objectName, deadline) };\n if (!Number.isSafeInteger(maxBytes)) {\n throw new ValidationError(`invalid tail length ${maxBytes}`);\n }\n // One request: a suffix range (`Range: bytes=-N`, \"the last N bytes\") answers with the tail and, in\n // `Content-Range: bytes a-b/total`, the object's size. An object shorter than N comes back whole.\n let res;\n try {\n res = await retryDownload(\n () =>\n readOnce(\n this.downloadable(objectName),\n { end: -maxBytes, decompress: false },\n maxBytes,\n () => this.badTail(key, `the response is longer than the ${maxBytes}B requested`),\n deadline,\n ),\n deadline,\n );\n } catch (err) {\n // A zero-byte object has no suffix to satisfy, and a server may refuse the range with a 416. The metadata\n // settles whether that is an empty object (a valid, empty tail) or a real range fault.\n if (!isInvalidRange(err)) throw err;\n const size = await this.sizeOf(objectName, deadline);\n if (size !== 0) throw err;\n return { bytes: new Uint8Array(0), size };\n }\n if (res.bytes.length === 0) {\n // No bytes came back for a positive request: only an empty object may do that.\n const size = await this.sizeOf(objectName, deadline);\n if (size !== 0) throw this.badTail(key, `no bytes returned for an object of ${size}B`);\n return { bytes: res.bytes, size };\n }\n return { bytes: res.bytes, size: this.tailSize(res, maxBytes, key) };\n } catch (err) {\n throw this.mapReadError(err, key);\n }\n }\n\n /**\n * The object's size from its metadata, validated. Read on the download client and retried as a download is, within\n * the calling read's `deadline` ({@link withDeadline}), so nothing but the one request in flight runs past it.\n */\n private async sizeOf(objectName: string, deadline: Deadline | undefined): Promise<number> {\n const [meta] = await retryDownload(\n () => withDeadline(() => this.downloadable(objectName).getMetadata(), deadline),\n deadline,\n );\n const size = Number(meta.size ?? 0);\n if (!Number.isSafeInteger(size) || size < 0) {\n throw new ValidationError(`GCS returned an invalid object size: ${String(meta.size)}`);\n }\n return size;\n }\n\n /**\n * The object's total size from a tail response, refused unless every header agrees with the bytes received. A 206\n * must carry `Content-Range: bytes a-b/total` that names exactly the suffix asked for and exactly the bytes held;\n * a 200 (a server that ignored the range) is the whole object, and is accepted only when it fits in the request.\n */\n private tailSize(res: ObjectRead, maxBytes: number, key: GenKey): number {\n const received = res.bytes.length;\n if (res.status === 200) {\n if (received > maxBytes)\n throw this.badTail(key, 'whole object returned past the requested length');\n return received;\n }\n if (res.status !== 206) throw this.badTail(key, `unexpected HTTP ${res.status}`);\n const header = singleHeader(res.headers, 'content-range');\n const m = header === undefined ? null : /^bytes (\\d+)-(\\d+)\\/(\\d+)$/.exec(header);\n if (m === null) throw this.badTail(key, `Content-Range is missing or malformed`);\n const [first, last, total] = [Number(m[1]), Number(m[2]), Number(m[3])];\n if (![first, last, total].every(Number.isSafeInteger)) {\n throw this.badTail(key, 'Content-Range holds a number that is not a safe integer');\n }\n if (\n last !== total - 1 ||\n first !== Math.max(0, total - maxBytes) ||\n last - first + 1 !== received\n ) {\n throw this.badTail(\n key,\n `Content-Range ${first}-${last}/${total} disagrees with ${received}B received for a ${maxBytes}B tail`,\n );\n }\n return total;\n }\n\n private badTail(key: GenKey, why: string): ValidationError {\n return new ValidationError(`GCS tail read of ${key.segment}.${key.generation} refused: ${why}`);\n }\n\n async delete(key: GenKey): Promise<void> {\n // Idempotent: `ignoreNotFound` so a racing/retried GC sweep of an absent object is a no-op.\n try {\n await this.file(storageObjectName(this.prefix, key)).delete({ ignoreNotFound: true });\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 let files;\n try {\n // autoPaginate (default) drains every page; a segment has few generations, so the set is small.\n [files] = await this.storage.bucket(this.bucket).getFiles({ prefix });\n } catch (err) {\n throw this.mapError(err);\n }\n for (const f of files) {\n const generation = parseGenerationFromName(prefix, f.name);\n if (generation !== null) {\n yield { namespace: ref.namespace, segment: ref.segment, generation };\n }\n }\n }\n\n /** Map GCS 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) \u2014 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 // Timed out on every attempt: transient, in words that name the read and the timeout.\n if (err instanceof ReadTimedOut) return new TransientError(err.message, { cause: err });\n // A connection that failed or was cut off, after the driver's retries: transient, whichever code Node gave it.\n if (isTransportFault(err)) {\n return new TransientError(\n `transient GCS fault: ${String((err as { code?: unknown }).code)}`,\n {\n cause: err,\n },\n );\n }\n return this.mapError(err);\n }\n\n /**\n * Reclassify a transient GCS fault (throttle/5xx/dropped connection) as a retryable {@link TransientError},\n * so the store's read retry can ride it out and a write's caller can tell it from a deterministic failure;\n * everything else propagates unchanged. The final fallback at every client-call site: an HTTP status and a fault\n * in transit arrive typed, and anything else (a checksum mismatch, a credentials failure) as the SDK raised it.\n */\n private mapError(err: unknown): unknown {\n if (isTransient(err)) {\n return new TransientError(\n `transient GCS fault: ${(err as { code?: unknown } | null)?.code ?? 'unknown'}`,\n { cause: err },\n );\n }\n return err;\n }\n}\n\ntype GcsFile = ReturnType<ReturnType<Storage['bucket']>['file']>;\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 * {@link BlobSink} that uploads one GCS object write-once. It **buffers up to a threshold**: an object that\n * finishes at/under it is committed in a **single simple (non-resumable) request** ({@link saveOnce}) \u2014 the write-once\n * path both real GCS and `fake-gcs-server` enforce; a larger one switches to a **resumable stream**, flushing the\n * buffer then piping the rest in constant memory (peak \u2248 one threshold). SHA-256 is hashed incrementally. Both\n * paths carry `ifGenerationMatch: 0` (create-only-if-absent) \u2192 a conflict is a 412, mapped by the driver to\n * {@link WriteConflictError}. On error the caller invokes {@link abort}.\n *\n * **The simple upload is sent with no SDK retry** ({@link saveOnce}): a replay of one that landed and lost its response\n * would find its own object and read as a lost race, so a lost response or a timeout throws {@link TransientError}. A\n * `429` or `503` is the one answer it is sent again after, by {@link sendSimple}. The resumable upload is a session of\n * requests, which the SDK retries within by asking the session how much it holds; the SDK takes that retry from the\n * client's options, not the call's, so it stays on. Each upload, of either kind, tags its object with a random id in\n * custom metadata, and a `412` that may be the upload meeting itself is read back: an object that carries this\n * upload's id is a success, and any other is the lost race. The read is paid only on that `412`.\n */\nclass GcsUploadSink implements BlobSink {\n private readonly hash: Hash = createHash('sha256');\n private total = 0;\n private readonly buffered: Uint8Array[] = [];\n private bufferedLen = 0;\n private stream: Writable | undefined; // set once we cross the threshold \u2192 resumable mode\n private done: Promise<unknown[]> | undefined;\n private failure: unknown;\n\n constructor(\n private readonly file: GcsFile,\n private readonly maxObjectBytes: number,\n private readonly threshold: number,\n private readonly clock: Sleeper,\n ) {}\n\n private static readonly WRITE_OPTS = {\n preconditionOpts: { ifGenerationMatch: 0 as const }, // write-once: create only if absent\n metadata: { contentType: 'application/octet-stream' },\n };\n\n /**\n * This write's own id, stored in the object's custom metadata (outside the `.crbm` bytes) so a conflict can be told\n * from a replay of this same write: the resumable session's, or the simple upload's re-send after a throttle.\n */\n private readonly writeId = randomBytes(16).toString('hex');\n\n /** The upload options with this write's id in the object's custom metadata. */\n private tagged(): typeof GcsUploadSink.WRITE_OPTS & {\n metadata: { metadata: Record<string, string> };\n } {\n return {\n ...GcsUploadSink.WRITE_OPTS,\n metadata: {\n ...GcsUploadSink.WRITE_OPTS.metadata,\n metadata: { [WRITE_ID_KEY]: this.writeId },\n },\n };\n }\n\n async write(bytes: Uint8Array): Promise<void> {\n if (this.failure !== undefined) throw this.failure;\n if (bytes.length === 0) return;\n this.total += bytes.length;\n if (this.total > this.maxObjectBytes) {\n throw new ValidationError(`object exceeds maxObjectBytes ${this.maxObjectBytes}`);\n }\n this.hash.update(bytes);\n if (this.stream !== undefined) {\n // Resumable mode: honor backpressure. `events.once(stream,'drain')` REJECTS if the stream emits 'error'\n // while we wait (that's its documented behavior for any awaited event except 'error'), and auto-removes\n // both listeners on settle \u2014 so there's no listener accumulation across drain cycles.\n if (!this.stream.write(bytes)) await once(this.stream, 'drain');\n return;\n }\n this.buffered.push(bytes);\n this.bufferedLen += bytes.length;\n if (this.bufferedLen > this.threshold) this.startResumable();\n }\n\n /** Cross into resumable streaming: open the stream, flush the buffered bytes, keep only ~one threshold resident. */\n private startResumable(): void {\n const stream = this.file.createWriteStream({ resumable: true, ...this.tagged() });\n this.stream = stream;\n this.done = once(stream, 'finish'); // resolves on a clean commit; rejects on 'error' (e.g. 412)\n stream.on('error', (e: unknown) => {\n this.failure ??= e; // a persistent listener so a stray 'error' is never an unhandled 'error' event\n });\n this.done.catch(() => undefined); // finish() awaits this; guard against unhandled rejection until then\n const buf = concatBytes(this.buffered, this.bufferedLen);\n this.buffered.length = 0;\n this.bufferedLen = 0;\n stream.write(buf); // \u2264 threshold+one-chunk; backpressure is absorbed by the next write()'s drain-await / end()\n }\n\n async finish(): Promise<{ size: number; sha256: string }> {\n const sha256 = this.hash.digest('hex');\n if (this.stream === undefined) {\n // Small object: one simple (non-resumable) upload \u2014 write-once enforced everywhere, the emulator too.\n await this.sendSimple(concatBytes(this.buffered, this.bufferedLen));\n return { size: this.total, sha256 };\n }\n try {\n if (this.failure !== undefined) throw this.failure;\n this.stream.end();\n await this.done; // rejects if the commit fails (e.g. 412 write-once conflict)\n } catch (err) {\n // The SDK retries a resumable session, so a commit that landed and lost its response can be answered `412` by\n // its own replay. Nothing overwrites a write-once object, so the id it holds says who wrote it. A failed\n // read-back throws, and reaches the caller as `TransientError` when the fault is transient: neither a success\n // nor a conflict.\n if (!isPreconditionFailed(err) || (await this.storedWriteId()) !== this.writeId) throw err;\n }\n return { size: this.total, sha256 };\n }\n\n /**\n * Send the simple upload once, and again after a `429` or `503`, up to {@link THROTTLE_RESENDS} times, waiting a\n * full-jitter backoff before each. Any other failure is thrown at once. Once it has been sent again, a `412` can be\n * an earlier send that landed after all, so the object's write id decides: this write's own is a success, and any\n * other object, or one with no id, is the conflict. A failed read-back throws, and is neither. Exhausted, it throws\n * {@link TransientError}; nothing is deleted.\n */\n private async sendSimple(body: Uint8Array): Promise<void> {\n for (let resent = 0; ; resent++) {\n try {\n await saveOnce(this.file, body, this.tagged());\n return;\n } catch (err) {\n if (resent > 0 && isPreconditionFailed(err)) {\n if ((await this.storedWriteId()) === this.writeId) return;\n throw err;\n }\n if (!isThrottle(err)) throw err;\n if (resent >= THROTTLE_RESENDS) {\n throw new TransientError(\n `GCS upload was throttled on each of its ${resent + 1} sends; the object may or may not exist`,\n { cause: err },\n );\n }\n await this.clock.sleep(Math.floor(Math.random() * THROTTLE_BASE_DELAY_MS * 2 ** resent));\n }\n }\n }\n\n /** The id the stored object carries in its custom metadata, or `undefined` when it carries none or is gone. */\n private async storedWriteId(): Promise<string | undefined> {\n try {\n const [meta] = await this.file.getMetadata();\n return meta.metadata?.[WRITE_ID_KEY] as string | undefined;\n } catch (err) {\n if (isNotFound(err)) return undefined;\n throw err;\n }\n }\n\n async abort(): Promise<void> {\n if (this.stream !== undefined && !this.stream.destroyed) this.stream.destroy();\n }\n}\n", "/**\n * Logical-ref \u2192 GCS object-name mapping for {@link GcsStorageDriver}.\n *\n * Pure string logic, no SDK dependency \u2014 unit-testable without GCS or an emulator. Uses the **same\n * backend-agnostic `.crbm` object-name scheme** as the S3 + LocalFs storage drivers\n * (`<prefix><ns>/segments/<segment>.<gen>.crbm`), so a segment reads identically whichever storage backend holds\n * it. The default (absent) namespace maps to `_default`, which cannot collide with a real namespace because a\n * caller's `_default` encodes to `%5Fdefault` while the sentinel is emitted literally.\n *\n * The storage-key layout is shared, not copied: `prefixPart` and the name encoders come from\n * `@cloudbitmaps/core/driver-kit`, so a segment written by one driver reads identically under another. Two\n * drivers disagreeing about how a name becomes a key would be a silent cross-driver incompatibility on the\n * same bucket, which is exactly the kind of thing a local copy drifts into.\n */\n\nimport {\n ValidationError,\n encodeNameForKey,\n namespaceKeyPart,\n prefixPart,\n validateSegmentRef,\n} from '@cloudbitmaps/core/driver-kit';\nimport type { GenKey, SegmentRef } from '@cloudbitmaps/core/driver-kit';\n\nconst SUFFIX = '.crbm';\n\n/**\n * Validate the caller-supplied key prefix (trusted config, but a real containment boundary): reject control\n * characters and `.`/`..` path segments so a prefix can't traverse out of its intended space. Returns it\n * unchanged (the key builders normalize slashes).\n */\nexport function normalizeGcsPrefix(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 GCS object-name prefix shared by all of a segment's generations: `<prefix><ns>/segments/<segment>.`.\n * Used both as the `getFiles` 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)}${namespaceKeyPart(ref.namespace)}/segments/${encodeNameForKey(ref.segment)}.`;\n}\n\n/** The full GCS object name of one `.crbm` generation: `<segmentPrefix><gen>.crbm`. */\nexport function storageObjectName(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 object name, given its segment prefix, or `null` if it doesn't\n * match. Canonical decimal only \u2014 no leading zeros (so `\u2026s.07.crbm` can't alias `\u2026s.7.crbm`) and within\n * safe-integer range. Also rejects a *different* segment whose name merely shares the prefix (its middle\n * 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 Google Cloud Storage SDK errors (the transient class mirrors S3's).\n *\n * SDK-free + side-effect-free \u2014 they only read structural shapes off the thrown value (`err.code`,\n * `err.response.status`, `err.name`), so the GCS-specific translation is unit-testable without a live GCS or\n * an emulator, and without importing `@google-cloud/storage`. GCS's `ApiError` carries the HTTP status on\n * `.code` (a number); dropped/timed-out sockets surface as a Node error with a string `.code` (`ECONNRESET`,\n * `ETIMEDOUT`, \u2026).\n */\n\n/** The HTTP status of a GCS `ApiError`, if present (`err.code` as a number, or `err.response.status`). */\nfunction httpStatus(err: unknown): number | undefined {\n const e = err as { code?: unknown; response?: { status?: unknown } } | null;\n if (typeof e?.code === 'number') return e.code;\n if (typeof e?.response?.status === 'number') return e.response.status;\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 `ifGenerationMatch: 0` write lost the write-once race \u2014 the object already existed, so GCS\n * returns **412 Precondition Failed**. Maps to `WriteConflictError` (caller OCC), never a blind retry.\n */\nexport function isPreconditionFailed(err: unknown): boolean {\n return httpStatus(err) === 412;\n}\n\n/**\n * GCS asked the client to slow down: `429` (`rateLimitExceeded`) or `503` (`backendError`), which the SDK raises as an\n * `ApiError` with the status as a numeric `code`. GCS does not document that a throttled request was not applied, so\n * an upload sent again after one must still tell a landed first send apart.\n */\nexport function isThrottle(err: unknown): boolean {\n const status = httpStatus(err);\n return status === 429 || status === 503;\n}\n\n/** The object / generation does not exist (GCS returns 404). */\nexport function isNotFound(err: unknown): boolean {\n return httpStatus(err) === 404;\n}\n\n/** A range request started past EOF (HTTP 416 Requested Range Not Satisfiable). */\nexport function isInvalidRange(err: unknown): boolean {\n return httpStatus(err) === 416;\n}\n\n/** The statuses the SDK's own retry predicate retries (`RETRYABLE_ERR_FN_DEFAULT` in `@google-cloud/storage`). */\nconst SDK_RETRIED_STATUSES = [408, 429, 500, 502, 503, 504];\n\n/** The connection faults the same predicate names, matched on a lower-cased code or reason as it does. */\nfunction isSdkConnectionProblem(reason: string): boolean {\n return (\n reason.includes('eai_again') ||\n reason === 'econnreset' ||\n reason === 'unexpected connection closure' ||\n reason === 'epipe' ||\n reason === 'socket connection timeout'\n );\n}\n\n/**\n * The codes Node gives a connection that failed or was cut off: refused, reset, aborted, timed out, a DNS failure, an\n * unreachable host or network, a broken pipe, and a chunked body cut off mid-stream (`ERR_STREAM_PREMATURE_CLOSE`).\n * Only these: a file the credentials name that is missing or unreadable (`ENOENT`, `EACCES`) and a TLS failure\n * (`EPROTO`, a certificate error) carry codes of the same shape, and would fail the same way again.\n */\nconst TRANSPORT_CODES = new Set([\n 'ECONNRESET',\n 'ECONNREFUSED',\n 'ECONNABORTED',\n 'ETIMEDOUT',\n 'ENOTFOUND',\n 'EAI_AGAIN',\n 'EHOSTUNREACH',\n 'ENETUNREACH',\n 'EPIPE',\n 'ERR_STREAM_PREMATURE_CLOSE',\n]);\n\n/** A download that failed in transit rather than with an HTTP status: one of {@link TRANSPORT_CODES}. */\nexport function isTransportFault(err: unknown): boolean {\n if (httpStatus(err) !== undefined) return false;\n const code = networkCode(err);\n return code !== undefined && TRANSPORT_CODES.has(code);\n}\n\n/**\n * Whether the driver retries a failed download, which it does in the SDK's place: the statuses the SDK's predicate\n * retries (408, 429, 500, 502, 503 and 504, as a number or a string `code`), the connection faults it names in any\n * `errors[].reason`, and every {@link isTransportFault}, which the SDK retried before any response came. A 404, 412,\n * 416 or 403, a 501 or 505, a credentials or TLS failure, and every other answer reach the caller on the first attempt.\n */\nexport function isDownloadRetryable(err: unknown): boolean {\n if (err === null || typeof err !== 'object') return false;\n if (isTransportFault(err)) return true;\n const e = err as { code?: unknown; errors?: unknown };\n if (typeof e.code === 'number' && SDK_RETRIED_STATUSES.includes(e.code)) return true;\n if (typeof e.code === 'string') {\n if (SDK_RETRIED_STATUSES.map(String).includes(e.code)) return true;\n if (isSdkConnectionProblem(e.code.toLowerCase())) return true;\n }\n if (Array.isArray(e.errors)) {\n for (const inner of e.errors as Array<{ reason?: unknown } | null>) {\n const reason = inner?.reason?.toString().toLowerCase();\n if (reason !== undefined && isSdkConnectionProblem(reason)) return true;\n }\n }\n return false;\n}\n\n/**\n * A transient GCS fault that is safe to retry: throttling (429), a request timeout (408), any 5xx, or a dropped/timed-out socket.\n * Excludes the deterministic, caller-meaningful outcomes (412/404/416) \u2014 those must never be reclassified as\n * a blind transient (a retried doomed conditional write would just fail again, and mask an OCC conflict).\n */\nexport function isTransient(err: unknown): boolean {\n if (isPreconditionFailed(err) || isNotFound(err) || isInvalidRange(err)) return false;\n const status = httpStatus(err);\n if (status === 408 || status === 429 || (status !== undefined && status >= 500 && status < 600))\n return true;\n const net = networkCode(err);\n return (\n net === 'ECONNRESET' ||\n net === 'ETIMEDOUT' ||\n net === 'ECONNREFUSED' ||\n net === 'EPIPE' ||\n net === 'EAI_AGAIN' ||\n net === 'ENOTFOUND'\n );\n}\n", "/**\n * The read timeout: `readTimeoutMs`, off (`0`) unless set. It bounds one driver read \u2014 a tail read (with the metadata\n * read it can fall back on), a range read, a registry read \u2014 as a whole: one deadline, started when the driver is\n * called, covers every attempt the driver makes at it and the backoff between them.\n *\n * `@google-cloud/storage` 8.x sets no working timeout of its own: it hands its `timeout` to an HTTP client that has no\n * such option, so a read on a connection that stops answering waits forever, and nothing above it ever gets a fault to\n * retry. When the deadline passes, the request in flight fails with {@link ReadTimedOut} (coded `ETIMEDOUT`), no\n * further attempt starts, and a backoff in progress ends with the same error; the driver reports it as\n * `TransientError`, its message naming the read and the timeout, for the store's read retry to run again.\n *\n * **What the clock counts.** Everything from the call into the driver: fetching or refreshing a credential, resolving\n * the project, waiting for a socket (Node's global agents, which the downloads use, set no socket limit unless the\n * process sets one, so by default there is no queue), each request, its headers and its whole body, and the driver's\n * backoff between attempts. It counts time the process spends busy too: Node runs a due timer before it reads a socket,\n * so a synchronous stretch longer than the timeout fails the reads in flight even when their responses have arrived.\n *\n * Uploads, deletes, listings and the conditional writes are not timed: an upload can rightly take longer than a read,\n * and a write cut off may still land.\n */\nimport { ValidationError } from '@cloudbitmaps/core/driver-kit';\n\n/** The default: no timeout. */\nconst DEFAULT_READ_TIMEOUT_MS = 0;\n\n/** The longest delay a Node timer holds. A longer one fires after 1 ms instead, so it is refused rather than passed on. */\nconst MAX_TIMER_MS = 2_147_483_647;\n\n/** Validate a caller's `readTimeoutMs`, defaulting it: an integer from 0 (no timeout) to {@link MAX_TIMER_MS}. */\nexport function resolveReadTimeoutMs(value: unknown): number {\n if (value === undefined) return DEFAULT_READ_TIMEOUT_MS;\n if (\n typeof value !== 'number' ||\n !Number.isSafeInteger(value) ||\n value < 0 ||\n value > MAX_TIMER_MS\n ) {\n throw new ValidationError(\n `readTimeoutMs must be a non-negative safe integer no larger than ${MAX_TIMER_MS}; got ${describe(value)}`,\n );\n }\n return value;\n}\n\n/** A value as an error message can show it: a string quoted, so `'200'` is not mistaken for `200`, and never a throw. */\nfunction describe(value: unknown): string {\n if (typeof value === 'string') return JSON.stringify(value);\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value);\n }\n return value === null ? 'null' : typeof value;\n}\n\n/** A read the driver gave up on when its deadline passed. Coded `ETIMEDOUT`, as a timed-out connection is. */\nexport class ReadTimedOut extends Error {\n readonly code = 'ETIMEDOUT';\n constructor(read: string, ms: number, cause?: unknown) {\n super(`GCS ${read} timed out after ${ms} ms`, cause === undefined ? undefined : { cause });\n this.name = 'ReadTimedOut';\n }\n}\n\n/** One driver read's deadline: `ms` from when it is made. `read` names the read in the error, e.g. `tail read of s.3`. */\nexport class Deadline {\n private readonly endsAt: number;\n\n constructor(\n readonly ms: number,\n readonly read: string,\n ) {\n this.endsAt = performance.now() + ms;\n }\n\n /** What is left of it, in milliseconds: 0 once it has passed. */\n remaining(): number {\n return Math.max(0, this.endsAt - performance.now());\n }\n\n /** The error a read fails with once this has passed; `cause` is the last attempt's own error, if one failed. */\n expired(cause?: unknown): ReadTimedOut {\n return new ReadTimedOut(this.read, this.ms, cause);\n }\n}\n\n/** The deadline a read made now runs under, or none when `ms` is 0 (no timeout). */\nexport function startDeadline(ms: number, read: string): Deadline | undefined {\n return ms === 0 ? undefined : new Deadline(ms, read);\n}\n\n/**\n * Make the request `send` starts, and settle with it, or with {@link ReadTimedOut} once `deadline` has passed; a\n * deadline already passed sends nothing. For a request the SDK cannot cancel: a metadata read is one callback-style\n * request that returns no handle to abort it, so at the deadline this stops waiting and the request runs on until it is\n * answered or its connection closes. The race keeps a handler on the request, so what it settles with then is dropped\n * rather than raised.\n */\nexport function withDeadline<T>(\n send: () => Promise<T>,\n deadline: Deadline | undefined,\n): Promise<T> {\n if (deadline === undefined) return send();\n if (deadline.remaining() === 0) return Promise.reject(deadline.expired());\n const request = send();\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(deadline.expired()), deadline.remaining());\n });\n return Promise.race([request, timedOut]).finally(() => clearTimeout(timer));\n}\n", "/**\n * `retryDownload` \u2014 run one download again after the faults the SDK itself would have retried.\n *\n * The client `GcsStorage` builds sends a download once (see `backend.ts`: a download the SDK retries can crash the\n * process), which also took away the SDK's quiet retry of a connection fault and of a 408, 429, 500, 502, 503 or 504.\n * This puts that retry back inside the driver, so every caller keeps it: the store's admin and write-path registry\n * reads, and a store built with `retry: false`, included. Which faults: `isDownloadRetryable`. How many: three\n * retries, the SDK's default, with full-jitter backoff of at most about 0.7 s a call. A chunk read that the store's\n * own retry also repeats makes at most 16 requests, as it did with the SDK's retry. A 404, 412, 403 and every other\n * answer reach the caller on the first attempt. What the last attempt raised is rethrown as it is, for the driver to\n * classify.\n *\n * **Under a read timeout** (`readTimeoutMs`), one {@link Deadline} covers every attempt: each attempt passes it to its\n * request, so the request in flight when it passes fails with {@link ReadTimedOut}; no attempt starts after it; and a\n * backoff that would outlast it ends when it passes, with the same error. Nor is a retry sent with less than\n * {@link MIN_RETRY_MS} left: no answer could arrive in time, and the request would only be one more held open. So a\n * server that never answers is sent one request per call, not four.\n */\nimport { isDownloadRetryable } from './gcs-errors';\nimport { ReadTimedOut, type Deadline } from './read-timeout';\n\nconst RETRIES = 3;\nconst BASE_DELAY_MS = 100;\nconst MAX_DELAY_MS = 1_000;\n/** Under a deadline, a retry is not sent with less than this left of it. */\nexport const MIN_RETRY_MS = 10;\n\n/**\n * Call `download`, and again after each retryable fault, up to {@link RETRIES} more times, within `deadline` when there\n * is one; rethrow the last error, or the deadline's once it has passed.\n */\nexport async function retryDownload<T>(\n download: () => Promise<T>,\n deadline?: Deadline,\n): Promise<T> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await download();\n } catch (err) {\n // The deadline passed during the attempt: nothing more is sent.\n if (err instanceof ReadTimedOut) throw err;\n if (attempt >= RETRIES || !isDownloadRetryable(err)) throw err;\n const left = deadline?.remaining() ?? Infinity;\n if (deadline !== undefined && left < MIN_RETRY_MS) throw deadline.expired(err);\n const ceiling = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * 2 ** attempt);\n const delay = Math.random() * ceiling;\n await new Promise((resolve) => setTimeout(resolve, Math.min(delay, left)));\n if (deadline !== undefined && deadline.remaining() < MIN_RETRY_MS)\n throw deadline.expired(err);\n }\n }\n}\n", "/**\n * `saveOnce` \u2014 upload one object in a single request, sent once, with no SDK retry around it.\n *\n * `file.save()` runs a single-request upload inside a retry loop, and it counts an upload that carries\n * `ifGenerationMatch` as safe to repeat, so after a lost response \u2014 a timeout, a reset connection, a 5xx \u2014 it sends\n * the upload again. When the first had landed, the second meets it: `ifGenerationMatch: 0` finds the object it\n * created, a generation fence finds the generation it replaced, and GCS answers `412`, which the driver can only report\n * as a lost race. The caller is told its write lost when it won. Only the caller can find out which happened, by\n * reading the pointer or listing the generations, so the write is sent once and a transient failure reaches it as\n * `TransientError`.\n *\n * `createWriteStream({ resumable: false })` is that same single multipart request without the loop: the SDK sends a\n * streamed upload with no retries of its own, since a stream it has read cannot be read again. The client is not\n * touched, so a caller's own client keeps its retry options for every other call.\n *\n * **`resumable: false` is load-bearing twice.** It is what makes this one request, and it is the path on which\n * fake-gcs-server enforces `ifGenerationMatch`, so the integration lane tests the precondition rather than passing over\n * a write with none.\n */\nimport { once } from 'node:events';\nimport type { CreateWriteStreamOptions, Storage } from '@google-cloud/storage';\n\ntype GcsFile = ReturnType<ReturnType<Storage['bucket']>['file']>;\n\n/** Upload `body` to `file` in one request, sent once; resolves once GCS has stored it and its checksum matched. */\nexport async function saveOnce(\n file: GcsFile,\n body: Uint8Array,\n options: CreateWriteStreamOptions,\n): Promise<void> {\n const stream = file.createWriteStream({ ...options, resumable: false });\n const stored = once(stream, 'finish'); // rejects on 'error', which is how a failed upload surfaces\n stream.end(body);\n await stored;\n}\n", "/**\n * `readOnce` \u2014 read one object, or one range of it, in a single GET, with the response headers in hand.\n *\n * `file.download()` returns only the bytes. The headers carry what a caller needs alongside them \u2014 the object's\n * `generation` (the registry's version fence) and, on a ranged read, the object's total size in `Content-Range` \u2014 so\n * this reads through `createReadStream`, which announces the raw response before any data flows. Both come from the\n * same response as the bytes, so they describe one observation of the object.\n *\n * **Everything on the wire is untrusted.** The advertised `Content-Length` is checked against `maxBytes` before the\n * first byte is buffered, and the bytes are counted as they arrive, so a response that understates its length (or\n * omits it) still cannot make the caller hold more than `maxBytes`. The headers are returned as received; each caller\n * parses the ones it relies on strictly.\n *\n * **Every download stays off the SDK's shared connection pool.** When a download fails part-way \u2014 a body cut off, a\n * refused response, a timeout \u2014 the SDK destroys the HTTP agent the request went out on, to free its socket. By default\n * that is one keep-alive agent the whole process shares (teeny-request's, for every request with `forever: true`), so\n * every other request in flight on any client is reset with it, uploads included. {@link downloadFile} sets\n * `forever: false` on its own requests, through the SDK's request interceptors: they then go out on Node's global\n * agent, which the SDK never destroys, and on `@google-cloud/storage` 8.x a destroyed download closes its own socket\n * and nothing else. The cost: the global agent closes a connection idle for 5 seconds, where the SDK's pool keeps it,\n * so a read after a longer pause opens a new one; and it is shared with the process's other `http` and `https` code.\n */\nimport type { Storage } from '@google-cloud/storage';\nimport type { Deadline } from './read-timeout';\n\ntype GcsFile = ReturnType<ReturnType<Storage['bucket']>['file']>;\ntype Interceptor = GcsFile['interceptors'][number];\n/** What the SDK hands an interceptor: its typing says less, but every request it decorates carries a `uri`. */\ntype RequestOptions = ReturnType<Interceptor['request']>;\n\n/** Sends a request on Node's global agent rather than the SDK's shared keep-alive one (see the module comment). */\nconst OFF_THE_SHARED_AGENT: Interceptor = {\n request: (reqOpts) => ({ ...(reqOpts as RequestOptions), forever: false }),\n};\n\n/** A handle on `name` for downloading: its requests stay off the SDK's shared agent. The client is not touched. */\nexport function downloadFile(storage: Storage, bucket: string, name: string): GcsFile {\n const file = storage.bucket(bucket).file(name);\n file.interceptors.push(OFF_THE_SHARED_AGENT);\n return file;\n}\n\nexport interface ObjectRead {\n /** The HTTP status of the response (200 for a whole object, 206 for a range). */\n readonly status: number;\n /** The response headers, lower-cased as Node delivers them. */\n readonly headers: Readonly<Record<string, string | string[] | undefined>>;\n /** The body, at most `maxBytes` long. */\n readonly bytes: Uint8Array;\n}\n\n/**\n * The response the SDK announces. It is the HTTP layer's own body stream with the status and headers laid on it, and\n * while that body is in flight the SDK and the HTTP layer each run a pipeline over it, which with its own listeners is\n * eleven or twelve error and close listeners: one past Node's default limit of ten, so Node prints a possible-leak\n * warning for every read that is still arriving when it is let go. Nothing grows with the number of attempts, because\n * each attempt has a body of its own. The limit is raised on that one stream, to {@link BODY_LISTENER_LIMIT}.\n */\ninterface RawResponse {\n statusCode?: unknown;\n headers?: Record<string, string | string[] | undefined>;\n setMaxListeners?: (n: number) => unknown;\n}\n\n/** Room for the listeners the SDK's and the HTTP layer's pipelines put on one response body, with a few to spare. */\nconst BODY_LISTENER_LIMIT = 16;\n\n/** The value of a header that must appear once, or `undefined` when it is absent, repeated or empty. */\nexport function singleHeader(headers: ObjectRead['headers'], name: string): string | undefined {\n const value = headers[name];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/**\n * GET `file` (with the SDK's `createReadStream` options), buffering at most `maxBytes`. `oversize` builds the error\n * thrown when the advertised or actual length passes the cap, so each caller reports it in its own vocabulary.\n * Transport and HTTP errors reject as the SDK raises them (a 404 carries `code: 404`).\n *\n * With a `deadline`, a read still unsettled when it passes rejects with its `ReadTimedOut`; one asked for after it has\n * passed is not sent. The deadline belongs to the whole driver read, so an attempt gets only what is left of it.\n */\nexport function readOnce(\n file: GcsFile,\n options: { start?: number; end?: number; validation?: false; decompress?: false },\n maxBytes: number,\n oversize: (size: number | undefined) => Error,\n deadline?: Deadline,\n): Promise<ObjectRead> {\n if (deadline !== undefined && deadline.remaining() === 0)\n return Promise.reject(deadline.expired());\n return new Promise<ObjectRead>((resolve, reject) => {\n const stream = file.createReadStream(options);\n const chunks: Buffer[] = [];\n let total = 0;\n let status = 0;\n let headers: ObjectRead['headers'] = {};\n let responded = false;\n let settled = false;\n // The SDK makes no request until the stream is read from, in a later turn, so a timer set here covers all of it.\n const timer =\n deadline === undefined\n ? undefined\n : setTimeout(() => fail(deadline.expired()), deadline.remaining());\n\n // Deferred: the SDK builds its response pipeline right after announcing the response, and destroying the stream\n // before that makes the pipeline throw out of an event handler, where nothing can catch it, and ends the process.\n // One turn later the same destroy ends the read without a throw. So a read is let go only once its response has\n // been announced, never before: one that settles first (a timeout before any answer) is destroyed when its\n // response comes, if one ever does. Until then the SDK has no way to cancel it, so its request stays open until\n // the server answers or the connection closes.\n const release = (): void => void setImmediate(() => stream.destroy());\n\n const fail = (err: unknown): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (responded) release();\n reject(err);\n };\n\n stream.on('response', (res: RawResponse) => {\n responded = true;\n res.setMaxListeners?.(BODY_LISTENER_LIMIT);\n if (settled) return release();\n status = typeof res.statusCode === 'number' ? res.statusCode : 0;\n headers = res.headers ?? {};\n if (status < 200 || status > 299) return; // the SDK raises the error itself, with its own status\n const advertised = singleHeader(headers, 'content-length');\n if (advertised !== undefined && /^\\d+$/.test(advertised) && Number(advertised) > maxBytes) {\n fail(oversize(Number(advertised)));\n }\n });\n stream.on('data', (chunk: Buffer) => {\n if (settled) return;\n total += chunk.length;\n if (total > maxBytes) return fail(oversize(undefined));\n chunks.push(chunk);\n });\n stream.on('error', fail);\n stream.on('end', () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve({ status, headers, bytes: new Uint8Array(Buffer.concat(chunks, total)) });\n });\n // A net under the SDK: it reports a cut-off body as an 'error' itself, but a stream that closes without ending\n // or erring was cut off all the same, so it is a dropped connection, and retryable.\n stream.on('close', () =>\n fail(\n Object.assign(new Error('GCS read ended before the response completed'), {\n code: 'ECONNRESET',\n }),\n ),\n );\n });\n}\n", "/**\n * `GcsRegistryDriver` \u2014 an {@link IRegistryDriver} over Google Cloud Storage.\n *\n * Lets a **GCS deployment run on one bucket alone** \u2014 storage `.crbm` generations and the registry in the same\n * place, with no second cloud involved.\n *\n * The protocol (an ABA-safe OCC token, the delete and its tombstone, the bounded retry, the key layout) lives once\n * in {@link ObjectStoreRegistry}; this file is only the I/O calls GCS makes. A delete removes a row for good, under\n * `ifGenerationMatch: <generation>`, when `conditionalDelete` is `true`. It is off by default, on the public endpoint too:\n * no run against real GCS has verified that the service applies the precondition to a delete, and fake-gcs-server\n * accepts it and ignores it, so CI cannot show it.\n *\n * **The atomic swap is offloaded to GCS's object preconditions.** `ifGenerationMatch: 0` is create-only\n * (\"only if it does not exist\") and `ifGenerationMatch: <generation>` is compare-and-swap \u2014 the same pair S3\n * spells `If-None-Match: *` / `If-Match: <etag>`. A lost race returns `412` \u2192 {@link WriteConflictError}. Each write\n * is one request sent once ({@link saveOnce}), so a `412` means another write got there first, never this one meeting\n * itself after a lost response. A transient failure reaches the caller as {@link TransientError}: the write may or may\n * not have landed, and the caller re-reads the row to learn where it stands.\n *\n * **The version fence is the object's `generation`, not its ETag**: GCS mutates the generation on every\n * overwrite and it is the value `ifGenerationMatch` compares, so it is the only correct fence here. (Note\n * the unrelated collision of vocabulary \u2014 a GCS *object generation* has nothing to do with a CloudBitmaps\n * `.crbm` *generation*.) Reads are strongly consistent, satisfying the registry's `strongRead` contract.\n *\n * **Deployment requirements** (a policy that violates these silently corrupts the registry):\n * - The principal needs `storage.objects.get`, `create`, `update`, `list` and, to remove rows, `delete` on the\n * bucket. Without `list`\n * the registry cannot enumerate, and a missing-object read may surface as `403` rather than `404`.\n * - **Do not apply an Object Lifecycle rule to the `registry/` prefix that deletes a live object** (one on\n * noncurrent versions only is safe), and do not enable a retention\n * policy that blocks overwrite or deletion; see {@link ObjectStoreRegistry}.\n * - Object versioning is neither required nor used; the driver always reads the live generation. A read is one\n * GET whose `x-goog-generation` header and body describe the same object, so a concurrent overwrite is simply\n * observed as the older or the newer row, never as a torn pair or a missing one.\n */\nimport {\n IntegrityError,\n MAX_ROW_BYTES,\n ObjectStoreRegistry,\n TransientError,\n ValidationError,\n WriteConflictError,\n normalizeObjectPrefix,\n} from '@cloudbitmaps/core/driver-kit';\nimport type { ObjectRegistryStore, ObjectRow } from '@cloudbitmaps/core/driver-kit';\nimport type { Storage } from '@google-cloud/storage';\nimport { isNotFound, isPreconditionFailed, isTransient, isTransportFault } from './gcs-errors';\nimport { retryDownload } from './download-retry';\nimport { downloadFile, readOnce, singleHeader } from './read-once';\nimport { ReadTimedOut, resolveReadTimeoutMs, startDeadline } from './read-timeout';\nimport { saveOnce } from './send-once';\n\nexport interface GcsRegistryDriverOptions {\n /** A constructed `@google-cloud/storage` `Storage` client (point `apiEndpoint` at fake-gcs-server locally). */\n readonly storage: Storage;\n /** The client the downloads go through; `storage` when absent (see `GcsStorageDriverOptions.readStorage`). */\n readonly readStorage?: Storage;\n /** Target bucket (must already exist). */\n readonly bucket: string;\n /** Optional object-name prefix under which all registry objects live (e.g. `cloudbitmaps/`). */\n readonly prefix?: string;\n /** Injected clock for `createdAt`/`updatedAt`; defaults to `Date.now`. */\n readonly now?: () => number;\n /**\n * Cut off a read of a registry row once it has run this long, in milliseconds, every attempt included; `0` (the\n * default) sets no timeout. Timed as the storage driver's reads are (see `GcsStorageDriverOptions.readTimeoutMs`);\n * the writes and listings are not timed.\n */\n readonly readTimeoutMs?: number;\n /**\n * Whether a delete removes a row for good, by an object delete sent with `ifGenerationMatch: <the generation it\n * read>`, rather than leaving a tombstone. Only a row born with an incarnation id is removed; a row a release before\n * 0.12 wrote is always tombstoned. Defaults to `false`, on the public endpoint too: whether real GCS applies\n * `ifGenerationMatch` to a delete has not been verified by a run against the service, and fake-gcs-server accepts the\n * precondition and ignores it, so CI cannot show it. Set `true` to remove rows for good.\n */\n readonly conditionalDelete?: boolean;\n}\n\n/** The calls {@link ObjectStoreRegistry} needs, in GCS's dialect. Exported for the tests that drive one directly. */\nexport class GcsRegistryStore implements ObjectRegistryStore {\n readonly label = 'GCS';\n\n constructor(\n private readonly storage: Storage,\n private readonly readStorage: Storage,\n private readonly bucket: string,\n private readonly readTimeoutMs: number,\n readonly conditionalDelete: boolean,\n ) {}\n\n /** A handle on one registry object. */\n private file(name: string) {\n return this.storage.bucket(this.bucket).file(name);\n }\n\n /** The same file handle on the download client, off the SDK's shared agent ({@link downloadFile}). */\n private downloadable(name: string) {\n return downloadFile(this.readStorage, this.bucket, name);\n }\n\n async read(key: string): Promise<ObjectRow | null> {\n // One GET: the response headers carry the `generation` fence and the length, and the body is the same\n // observation of the object, so the pair cannot straddle a concurrent overwrite.\n let res;\n try {\n // One deadline for the read, every attempt included.\n const deadline = startDeadline(this.readTimeoutMs, `registry read of ${key}`);\n res = await retryDownload(\n () =>\n readOnce(\n this.downloadable(key),\n {},\n MAX_ROW_BYTES,\n (size) =>\n new IntegrityError(\n size === undefined\n ? `registry object exceeds cap ${MAX_ROW_BYTES}B`\n : `registry object ${size}B exceeds cap ${MAX_ROW_BYTES}B`,\n ),\n deadline,\n ),\n deadline,\n );\n } catch (err) {\n if (isNotFound(err)) return null;\n throw mapReadError(err);\n }\n if (res.status !== 200) {\n throw new IntegrityError(`registry read answered HTTP ${res.status}, not 200: ${key}`);\n }\n // The fence is compared by `ifGenerationMatch` on the next write, so it is parsed here, where a header that\n // is missing or malformed can be refused before it is mistaken for a version.\n const generation = singleHeader(res.headers, 'x-goog-generation');\n if (\n generation === undefined ||\n !/^[1-9]\\d*$/.test(generation) ||\n !Number.isSafeInteger(Number(generation))\n ) {\n throw new IntegrityError(`registry read carries no usable x-goog-generation: ${key}`);\n }\n return { bytes: res.bytes, version: generation };\n }\n\n async write(\n key: string,\n body: Uint8Array,\n expect: 'absent' | { version: string },\n ): Promise<void> {\n try {\n // One request, sent once: a replay of a write that landed would fail its own precondition, and read as a lost\n // race. A registry row is a few hundred bytes, so a resumable session would also carry one round trip's worth\n // of data over two.\n await saveOnce(this.file(key), body, {\n contentType: 'application/json',\n preconditionOpts: {\n ifGenerationMatch: expect === 'absent' ? 0 : generationFence(expect.version, key),\n },\n });\n } catch (err) {\n if (isPreconditionFailed(err)) {\n throw new WriteConflictError(`registry OCC conflict for ${key}`);\n }\n throw mapError(err);\n }\n }\n\n async delete(key: string, expect: { version: string }): Promise<void> {\n try {\n // The SDK may send this again after a lost response, as it does any request with a precondition. The\n // precondition names one generation, so a second copy removes nothing the first could not; one that meets the\n // first's landed delete is a 404, a conflict, and the registry re-reads and finds the row gone.\n await this.file(key).delete({ ifGenerationMatch: generationFence(expect.version, key) });\n } catch (err) {\n // A 412 (the generation moved on) or a 404 (the object is gone): the version to delete is not there.\n if (isPreconditionFailed(err) || isNotFound(err)) {\n throw new WriteConflictError(`registry OCC conflict deleting ${key}`);\n }\n throw mapError(err);\n }\n }\n\n async *listKeys(prefix: string): AsyncIterable<string> {\n let pageToken: string | undefined;\n do {\n let files;\n let next: { pageToken?: string } | null | undefined;\n try {\n // Explicit paging rather than autoPaginate: a registry can hold far more rows than a segment holds\n // generations, and draining every page into memory first is what `list()` exists to avoid.\n [files, next] = await this.storage\n .bucket(this.bucket)\n .getFiles({ prefix, autoPaginate: false, maxResults: 1000, pageToken });\n } catch (err) {\n throw mapError(err);\n }\n for (const f of files ?? []) yield f.name;\n pageToken = next?.pageToken;\n } while (pageToken !== undefined);\n }\n}\n\n/**\n * Narrow a version fence back to the number `ifGenerationMatch` takes. The fence always originates as\n * the `x-goog-generation` header in {@link GcsRegistryStore.read}, so this cannot fire in practice \u2014 but `Number()` answers `NaN`\n * for anything non-numeric, and `ifGenerationMatch: NaN` serializes to a precondition GCS ignores. That\n * failure is invisible (writes simply stop being fenced), so it is checked rather than assumed.\n */\nfunction generationFence(version: string, key: string): number {\n const generation = Number(version);\n if (!Number.isSafeInteger(generation) || generation <= 0) {\n throw new IntegrityError(`registry version fence is not a GCS generation: ${key}`);\n }\n return generation;\n}\n\n/** A read's error: a connection that failed or was cut off is transient too, after the driver's retries. */\nfunction mapReadError(err: unknown): unknown {\n // Timed out on every attempt: transient, in words that name the read and the timeout.\n if (err instanceof ReadTimedOut) return new TransientError(err.message, { cause: err });\n if (isTransportFault(err)) {\n return new TransientError(`transient GCS fault: ${String((err as { code?: unknown }).code)}`, {\n cause: err,\n });\n }\n return mapError(err);\n}\n\n/** Reclassify a transient GCS fault as a retryable {@link TransientError}; pass everything else through. */\nfunction mapError(err: unknown): unknown {\n if (isTransient(err)) {\n return new TransientError(\n `transient GCS fault: ${(err as { code?: unknown } | null)?.code ?? (err as { name?: string } | null)?.name ?? 'unknown'}`,\n { cause: err },\n );\n }\n return err;\n}\n\n/** {@link GcsRegistryDriverOptions.conditionalDelete}, checked, or its default: off. */\nfunction resolveConditionalDelete(value: unknown): boolean {\n if (value === undefined) {\n return false;\n }\n if (typeof value !== 'boolean') {\n throw new ValidationError(`conditionalDelete must be a boolean; got ${String(value)}`);\n }\n return value;\n}\n\nexport class GcsRegistryDriver extends ObjectStoreRegistry {\n constructor(options: GcsRegistryDriverOptions) {\n super(\n new GcsRegistryStore(\n options.storage,\n options.readStorage ?? options.storage,\n options.bucket,\n resolveReadTimeoutMs(options.readTimeoutMs),\n resolveConditionalDelete(options.conditionalDelete),\n ),\n normalizeObjectPrefix(options.prefix),\n options.now ?? ((): number => Date.now()),\n );\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["ValidationError", "ValidationError", "once", "ValidationError", "ValidationError", "once", "TransientError", "ValidationError", "WriteConflictError", "WriteConflictError", "TransientError", "ValidationError", "ValidationError"]
3
+ "sources": ["../src/backend.ts", "../src/storage.ts", "../src/keys.ts", "../src/gcs-errors.ts", "../src/read-timeout.ts", "../src/download-retry.ts", "../src/scrub-error.ts", "../src/send-once.ts", "../src/read-once.ts", "../src/registry.ts"],
4
+ "sourcesContent": ["/**\n * `GcsStorage` \u2014 the Google Cloud Storage backend as one object: generations and pointer, one bucket, stated once.\n *\n * See {@link StorageBackend} for why the two halves are configured together rather than separately.\n *\n * **A note on the word.** `@google-cloud/storage` names its own client class `Storage`, so the driver option\n * that takes it is also called `storage` \u2014 which, since this library calls its durable tier storage too, reads\n * as the same word meaning two things. That is why this class builds the client itself: `new GcsStorage({ bucket })`\n * never makes you write `{ storage: storage }`. Supply `client` when you need your own.\n */\nimport { STORAGE_BACKEND, ValidationError, brandAsBackend } from '@cloudbitmaps/core/driver-kit';\nimport type {\n IRegistryDriver,\n IStorageDriver,\n StorageBackend,\n} from '@cloudbitmaps/core/driver-kit';\nimport { Storage as GcsClient } from '@google-cloud/storage';\nimport { GcsStorageDriver } from './storage';\nimport { GcsRegistryDriver } from './registry';\n\nexport interface GcsStorageOptions {\n /** Target bucket (must already exist). */\n readonly bucket: string;\n /** Optional object-name prefix under which everything lives \u2014 generations and the registry alike. */\n readonly prefix?: string;\n /**\n * A constructed `@google-cloud/storage` client. One is built from the ambient credentials when absent. Its retry\n * options apply to every request except the single-request conditional writes, which the driver sends without\n * that retry whatever they say: a registry row once, and a generation's object again only after a throttle, under\n * its own backoff. In `@google-cloud/storage` 7.x and 8.x a download the SDK retries after a 408, 429, 500, 502, 503 or 504 can crash the process\n * with `ERR_STREAM_UNABLE_TO_PIPE`, so build it with `retryOptions: { autoRetry: false }`; the driver retries\n * downloads itself. That also turns off the SDK's retries of listings, metadata reads and resumable uploads on\n * this client, which the store does not retry. The client built here needs none of this: only its downloads are sent once.\n */\n readonly client?: GcsClient;\n /** Project id for the client built when `client` is absent (refused beside `client`). Falls back to the SDK's own resolution. */\n readonly projectId?: string;\n /** Endpoint override \u2014 point it at fake-gcs-server locally. Refused beside `client`, which carries its own. */\n readonly apiEndpoint?: string;\n /** Largest object the backend will write and advertise (default = GCS's 5 TiB max). Must be a positive safe integer. */\n readonly maxObjectBytes?: number;\n /** Bytes at/under which a single non-resumable upload is used instead of a resumable stream (default 8 MiB).\n * Must be a positive safe integer. */\n readonly simpleUploadThresholdBytes?: number;\n /** Injected clock for the registry's `createdAt`/`updatedAt`; defaults to `Date.now`. */\n readonly now?: () => number;\n /**\n * Cut off a read that has run this long, in milliseconds. `0`, the default, sets no timeout. A client's own `timeout`\n * does not bound a download on `@google-cloud/storage` 8.x (the SDK hands it to an HTTP client that has no such\n * option), so this is what does.\n *\n * It bounds each read as a whole \u2014 a generation's tail (with the metadata read it falls back on for an empty\n * object), a range of it, a registry row \u2014 with one deadline across every attempt the driver makes and the backoff\n * between them. The clock starts at the call into the driver, so a credential fetch and any wait for a socket count\n * (Node's agents set no socket limit unless your process sets one), and runs until the whole body has arrived. It\n * counts time the process spends busy too: Node runs a due timer before it reads a socket, so a synchronous stretch\n * longer than the timeout fails the reads in flight even when their responses have arrived. When it passes, the read\n * throws `TransientError` naming the read and the timeout and no further attempt starts, for the store's read retry\n * to run again: about 8.35 s in all at `2_000` with the default retry policy. Uploads, deletes, listings and the\n * conditional writes are not timed. The SDK cannot cancel a request whose response has not begun, so a read timed\n * out before its server answers leaves that connection open until the server answers or closes it: one per read, up\n * to four per call through the store's retry. A non-negative safe integer no larger than 2,147,483,647.\n */\n readonly readTimeoutMs?: number;\n /**\n * Whether the registry removes a deleted row for good, by an object delete sent with `ifGenerationMatch`, rather\n * than leaving a tombstone a full listing reads forever. Defaults to `false`, on the public endpoint too: whether real\n * GCS applies `ifGenerationMatch` to a delete has not been verified by a run against the service, and fake-gcs-server\n * accepts the precondition and ignores it, so CI cannot show it. Set `true` to remove rows for good.\n */\n readonly conditionalDelete?: boolean;\n}\n\n/**\n * The keys `new GcsStorage(options)` takes. Any other is refused by name rather than ignored: an ignored client key\n * \u2014 `storage`, as the lower-level storage driver calls it \u2014 falls back to ambient credentials and the **public** endpoint, and\n * for a client pointed at an emulator that is production traffic from a wiring typo.\n */\nexport const GCS_STORAGE_OPTION_KEYS = [\n 'bucket',\n 'prefix',\n 'client',\n 'projectId',\n 'apiEndpoint',\n 'maxObjectBytes',\n 'simpleUploadThresholdBytes',\n 'now',\n 'readTimeoutMs',\n 'conditionalDelete',\n] as const;\n\n/** The settings that build a client, which a supplied `client` already carries and so cannot be given beside. */\nconst CLIENT_SETTINGS = ['projectId', 'apiEndpoint'] as const;\n\n/** Refuse an options bag that is not an object, or that holds a key not in `keys`, naming each such key. */\nfunction refuseUnknown(\n name: string,\n options: unknown,\n keys: readonly string[],\n hint: string,\n): void {\n if (options === null || typeof options !== 'object') {\n throw new ValidationError(\n `${name} needs an options object \u2014 got ${options === null ? 'null' : typeof options}`,\n );\n }\n const unknown = Object.keys(options).filter((k) => !keys.includes(k));\n if (unknown.length > 0) {\n const list = (ks: readonly string[]): string => ks.map((k) => `\\`${k}\\``).join(', ');\n throw new ValidationError(\n `${name} does not take ${list(unknown)}. It takes ${list(keys)}; ${hint}.`,\n );\n }\n}\n\nexport class GcsStorage implements StorageBackend {\n /** Cross-bundle brand, stamped non-enumerably in the constructor so a spread cannot carry it. */\n declare readonly [STORAGE_BACKEND]: true;\n readonly storage: IStorageDriver;\n readonly registry: IRegistryDriver;\n /** The client both halves share \u2014 built here unless one was supplied. */\n readonly client: GcsClient;\n\n constructor(options: GcsStorageOptions) {\n let readClient: GcsClient;\n refuseUnknown(\n 'GcsStorage',\n options,\n GCS_STORAGE_OPTION_KEYS,\n 'a @google-cloud/storage client goes in `client`',\n );\n if (options.client !== undefined && options.client !== null) {\n // A supplied client already carries its project and endpoint, so a setting beside it is ignored, and\n // ignoring it leaves the store talking to somewhere the caller did not mean: an `apiEndpoint` meant for an\n // emulator, silently dropped, is production traffic from a client that was built for the public endpoint.\n // Refuse instead of picking one.\n const ignored = CLIENT_SETTINGS.filter((k) => options[k] !== undefined);\n if (ignored.length > 0) {\n throw new ValidationError(\n `GcsStorage takes \\`client\\` OR ${CLIENT_SETTINGS.map((k) => `\\`${k}\\``).join(' / ')}, not both \u2014 ` +\n `got \\`client\\` with ${ignored.map((k) => `\\`${k}\\``).join(', ')}; ` +\n 'the `client` already carries them, so configure them on the client, or drop `client`',\n );\n }\n this.client = options.client;\n readClient = options.client;\n } else {\n const settings = {\n ...(options.projectId === undefined ? {} : { projectId: options.projectId }),\n ...(options.apiEndpoint === undefined ? {} : { apiEndpoint: options.apiEndpoint }),\n };\n this.client = new GcsClient(settings);\n // A second client, for downloads only. `@google-cloud/storage` 7.x and 8.x retry a failed download by default, and\n // when the retried request succeeds they throw `ERR_STREAM_UNABLE_TO_PIPE` outside any promise, which ends the\n // process whatever the caller wrote around the call. A download is sent once instead, and the driver retries it\n // itself (`download-retry.ts`). Everything else keeps the default retries: a resumable upload's session, a\n // listing, a metadata read, none of which the store retries on its own. Two clients mean two `GoogleAuth`\n // instances: lazy, so the first download makes a second token fetch, and a second hourly refresh follows.\n readClient = new GcsClient({ ...settings, retryOptions: { autoRetry: false } });\n }\n const shared = {\n storage: this.client,\n readStorage: readClient,\n bucket: options.bucket,\n ...(options.prefix === undefined ? {} : { prefix: options.prefix }),\n ...(options.readTimeoutMs === undefined ? {} : { readTimeoutMs: options.readTimeoutMs }),\n };\n this.storage = new GcsStorageDriver({\n ...shared,\n ...(options.maxObjectBytes === undefined ? {} : { maxObjectBytes: options.maxObjectBytes }),\n ...(options.simpleUploadThresholdBytes === undefined\n ? {}\n : { simpleUploadThresholdBytes: options.simpleUploadThresholdBytes }),\n });\n this.registry = new GcsRegistryDriver({\n ...shared,\n ...(options.now === undefined ? {} : { now: options.now }),\n ...(options.conditionalDelete === undefined\n ? {}\n : { conditionalDelete: options.conditionalDelete }),\n });\n brandAsBackend(this);\n }\n}\n", "/**\n * `GcsStorageDriver` \u2014 an {@link IStorageDriver} over Google Cloud Storage.\n *\n * Uses the official `@google-cloud/storage`, a real dependency of this package \u2014 installing\n * `@cloudbitmaps/gcs` is what installs it. The `Storage` client is **injected** (dependency injection): the driver owns\n * no credential/project/endpoint logic, so it's thin, reuses the caller's client, and is testable against the\n * `fake-gcs-server` emulator (point a `Storage` at its `apiEndpoint`).\n *\n * Generations are write-once immutable objects: a resumable upload with the **`ifGenerationMatch: 0`**\n * precondition (\"create only if it does not exist\") makes the publish atomic \u2014 a second write to the same\n * object fails with 412 \u2192 {@link WriteConflictError}, never a silent overwrite (hard invariant 2: storage\n * objects are immutable and never overwritten in place), the GCS analogue of S3's `If-None-Match: *` and\n * LocalFs's atomic `link`. **Writes stream in constant memory:** the codec's bytes are\n * piped into a GCS resumable-upload `Writable` (chunked/freed by the SDK as they go), so a load's write\n * footprint stays bounded regardless of segment size, up to the advertised `maxObjectBytes`. Every object carries a\n * random write id in its custom metadata (`cbwid`), so a `412` that may be the write meeting itself can be read back:\n * a resumable upload's session is retried by the SDK, and a single-request upload is sent again by the driver after a\n * `429` or `503`, at most {@link THROTTLE_RESENDS} times with backoff, and after nothing else. Drivers may use\n * `node:crypto`; only `core/` is bound by the determinism lint.\n */\nimport {\n NotFoundError,\n TransientError,\n ValidationError,\n WriteConflictError,\n isNotFoundError,\n isValidationError,\n isWriteConflictError,\n} from '@cloudbitmaps/core/driver-kit';\nimport type {\n BlobSink,\n GenKey,\n IStorageDriver,\n SegmentRef,\n StorageCaps,\n} from '@cloudbitmaps/core/driver-kit';\nimport { createHash, randomBytes, type Hash } from 'node:crypto';\nimport type { Writable } from 'node:stream';\nimport { once } from 'node:events';\nimport type { Storage } from '@google-cloud/storage';\nimport {\n storageObjectName,\n normalizeGcsPrefix,\n parseGenerationFromName,\n segmentObjectPrefix,\n} from './keys';\nimport {\n isInvalidRange,\n isNotFound,\n isPreconditionFailed,\n isThrottle,\n isTransient,\n isTransportFault,\n} from './gcs-errors';\nimport { retryDownload } from './download-retry';\nimport { scrubCredentials } from './scrub-error';\nimport { saveOnce } from './send-once';\nimport { downloadFile, readOnce, singleHeader, type ObjectRead } from './read-once';\nimport {\n ReadTimedOut,\n resolveReadTimeoutMs,\n startDeadline,\n withDeadline,\n type Deadline,\n} from './read-timeout';\n\n/** Default object ceiling: GCS's 5 TiB per-object hard max. Set lower to fail fast on a runaway write. */\nconst DEFAULT_MAX_OBJECT_BYTES = 5 * 1024 * 1024 * 1024 * 1024;\n\n/** The custom-metadata name an object's write id is stored under. Short: it travels with every write. */\nconst WRITE_ID_KEY = 'cbwid';\n/** How many times a throttled single-request upload is sent again: four sends in all, as the SDK's own retry makes. */\nconst THROTTLE_RESENDS = 3;\n/** The backoff ceiling before the first re-send, doubling for each one after it. */\nconst THROTTLE_BASE_DELAY_MS = 500;\n\n/** What the throttle backoff waits on. */\ninterface Sleeper {\n sleep(ms: number): Promise<void>;\n}\n\nconst REAL_TIME: Sleeper = {\n sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),\n};\n\n/**\n * Objects at/under this size are uploaded in a **single simple (non-resumable) request**; larger ones switch to\n * a **resumable stream** (constant memory). Mirrors S3's single-PUT-vs-multipart split \u2014 the simple path buffers\n * only \u2264 this many bytes, the resumable path streams. Default 8 MiB (\u2248 S3's part size), so peak write memory is\n * ~one threshold's worth regardless of object size.\n */\nconst DEFAULT_UPLOAD_THRESHOLD_BYTES = 8 * 1024 * 1024;\n\nexport interface GcsStorageDriverOptions {\n /** A constructed `@google-cloud/storage` `Storage` client (point `apiEndpoint` at fake-gcs-server locally). */\n readonly storage: Storage;\n /**\n * The client the downloads go through; `storage` when absent. `GcsStorage` passes one built with the SDK's request\n * retries off (the read client built in its constructor), because a download the SDK retries can crash the process;\n * the driver retries a download itself instead.\n */\n readonly readStorage?: Storage;\n /** Target bucket (must already exist). */\n readonly bucket: string;\n /** Optional object-name prefix under which all objects live (e.g. `cloudbitmaps/`). */\n readonly prefix?: string;\n /** Largest object this driver will write/advertise (default = GCS's 5 TiB max). Must be a positive safe integer. */\n readonly maxObjectBytes?: number;\n /** Bytes at/under which a single non-resumable upload is used instead of a resumable stream (default 8 MiB).\n * Must be a positive safe integer. */\n readonly simpleUploadThresholdBytes?: number;\n /**\n * Cut off a read that has run this long, in milliseconds: a tail read (with the metadata read it falls back on for\n * an empty object) or a range read, every attempt and the backoff between them included. `0` (the default) sets no\n * timeout. The clock starts at the call into the driver, so a credential fetch counts, and runs until the body has\n * ended; when it passes the read throws `TransientError` and no further attempt starts. Uploads, deletes and\n * listings are not timed. A non-negative safe integer no larger than 2,147,483,647.\n */\n readonly readTimeoutMs?: number;\n /** What the backoff before re-sending a throttled upload waits on; real time when absent. */\n readonly clock?: Sleeper;\n}\n\nexport class GcsStorageDriver implements IStorageDriver {\n private readonly storage: Storage;\n private readonly readStorage: Storage;\n private readonly bucket: string;\n private readonly prefix: string | undefined;\n private readonly maxObjectBytes: number;\n private readonly threshold: number;\n private readonly readTimeoutMs: number;\n private readonly clock: Sleeper;\n\n constructor(options: GcsStorageDriverOptions) {\n this.storage = options.storage;\n this.clock = options.clock ?? REAL_TIME;\n this.readStorage = options.readStorage ?? options.storage;\n this.bucket = options.bucket;\n this.prefix = normalizeGcsPrefix(options.prefix);\n // Fail fast at the boundary: `??` only guards `undefined`, so NaN, 0, a negative or a fraction would otherwise\n // reject every write (cap) or corrupt the upload-path choice (threshold).\n for (const [name, value] of [\n ['maxObjectBytes', options.maxObjectBytes],\n ['simpleUploadThresholdBytes', options.simpleUploadThresholdBytes],\n ] as const) {\n if (value !== undefined && (!Number.isSafeInteger(value) || value < 1)) {\n throw new ValidationError(`${name} must be a positive safe integer; got ${value}`);\n }\n }\n this.maxObjectBytes = options.maxObjectBytes ?? DEFAULT_MAX_OBJECT_BYTES;\n this.threshold = options.simpleUploadThresholdBytes ?? DEFAULT_UPLOAD_THRESHOLD_BYTES;\n this.readTimeoutMs = resolveReadTimeoutMs(options.readTimeoutMs);\n }\n\n capabilities(): StorageCaps {\n return { rangeRead: true, maxObjectBytes: this.maxObjectBytes, conditionalPut: true };\n }\n\n private file(name: string) {\n return this.storage.bucket(this.bucket).file(name);\n }\n\n /** The same object on the download client, off the SDK's shared agent ({@link downloadFile}). */\n private downloadable(name: string) {\n return downloadFile(this.readStorage, this.bucket, name);\n }\n\n /** The deadline a read of `key` made now runs under, every attempt included, named for its error; none when off. */\n private deadline(read: string, key: GenKey): Deadline | undefined {\n return startDeadline(this.readTimeoutMs, `${read} of ${key.segment}.${key.generation}`);\n }\n\n async putImmutable(\n key: GenKey,\n write: (sink: BlobSink) => Promise<void>,\n ): Promise<{ size: number; sha256: string }> {\n const objectName = storageObjectName(this.prefix, key); // validates ref + generation\n const sink = new GcsUploadSink(\n this.file(objectName),\n this.maxObjectBytes,\n this.threshold,\n this.clock,\n );\n try {\n await write(sink);\n return await sink.finish();\n } catch (err) {\n await sink.abort(); // best-effort teardown of the in-flight upload\n // A lost write-once race \u2014 the object already existed, so `ifGenerationMatch: 0` failed with 412.\n if (isPreconditionFailed(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 = storageObjectName(this.prefix, key);\n try {\n // One request, buffering at most the bytes asked for. GCS `end` is inclusive. `decompress: false`, as for the tail:\n // the offsets are into the bytes as stored.\n const deadline = this.deadline('range read', key);\n const res = await retryDownload(\n () =>\n readOnce(\n this.downloadable(objectName),\n { start: offset, end: offset + length - 1, decompress: false },\n length,\n () => this.badRange(key, `the response is longer than the ${length}B requested`),\n deadline,\n ),\n deadline,\n );\n this.checkRange(res, key, offset, length);\n return res.bytes;\n } catch (err) {\n throw this.mapReadError(err, key);\n }\n }\n\n /**\n * Refuse a range response unless it is exactly the bytes asked for. A 206 must name them in `Content-Range: bytes\n * a-b/total`, starting at `offset`, and hold that many; a 200 (a server that ignored the range) is the whole object,\n * which is the range only when the range starts at 0. A short read means the range ran past the end of the object:\n * out of bounds, never a partial result.\n */\n private checkRange(res: ObjectRead, key: GenKey, offset: number, length: number): void {\n const received = res.bytes.length;\n if (res.status === 200) {\n if (offset !== 0)\n throw this.badRange(key, `whole object returned for a range from ${offset}`);\n } else if (res.status === 206) {\n const header = singleHeader(res.headers, 'content-range');\n const m = header === undefined ? null : /^bytes (\\d+)-(\\d+)\\/(\\d+)$/.exec(header);\n if (m === null) throw this.badRange(key, 'Content-Range is missing or malformed');\n const [first, last, total] = [Number(m[1]), Number(m[2]), Number(m[3])];\n if (![first, last, total].every(Number.isSafeInteger)) {\n throw this.badRange(key, 'Content-Range holds a number that is not a safe integer');\n }\n if (first !== offset || last - first + 1 !== received || last >= total) {\n throw this.badRange(\n key,\n `Content-Range ${first}-${last}/${total} disagrees with ${received}B received from ${offset}`,\n );\n }\n } else {\n throw this.badRange(key, `unexpected HTTP ${res.status}`);\n }\n if (received !== length) {\n throw new ValidationError(\n `range [${offset}, ${offset + length}) out of bounds (got ${received}B)`,\n );\n }\n }\n\n private badRange(key: GenKey, why: string): ValidationError {\n return new ValidationError(\n `GCS range read of ${key.segment}.${key.generation} refused: ${why}`,\n );\n }\n\n async getTail(key: GenKey, maxBytes: number): Promise<{ bytes: Uint8Array; size: number }> {\n const objectName = storageObjectName(this.prefix, key);\n // One deadline for the whole call: the tail's attempts, and the metadata read it may fall back on.\n const deadline = this.deadline('tail read', key);\n try {\n // Nothing to read: only the size is wanted, which the metadata answers in one request.\n if (maxBytes <= 0)\n return { bytes: new Uint8Array(0), size: await this.sizeOf(objectName, deadline) };\n if (!Number.isSafeInteger(maxBytes)) {\n throw new ValidationError(`invalid tail length ${maxBytes}`);\n }\n // One request: a suffix range (`Range: bytes=-N`, \"the last N bytes\") answers with the tail and, in\n // `Content-Range: bytes a-b/total`, the object's size. An object shorter than N comes back whole.\n let res;\n try {\n res = await retryDownload(\n () =>\n readOnce(\n this.downloadable(objectName),\n { end: -maxBytes, decompress: false },\n maxBytes,\n () => this.badTail(key, `the response is longer than the ${maxBytes}B requested`),\n deadline,\n ),\n deadline,\n );\n } catch (err) {\n // A zero-byte object has no suffix to satisfy, and a server may refuse the range with a 416. The metadata\n // settles whether that is an empty object (a valid, empty tail) or a real range fault.\n if (!isInvalidRange(err)) throw err;\n const size = await this.sizeOf(objectName, deadline);\n if (size !== 0) throw err;\n return { bytes: new Uint8Array(0), size };\n }\n if (res.bytes.length === 0) {\n // No bytes came back for a positive request: only an empty object may do that.\n const size = await this.sizeOf(objectName, deadline);\n if (size !== 0) throw this.badTail(key, `no bytes returned for an object of ${size}B`);\n return { bytes: res.bytes, size };\n }\n return { bytes: res.bytes, size: this.tailSize(res, maxBytes, key) };\n } catch (err) {\n throw this.mapReadError(err, key);\n }\n }\n\n /**\n * The object's size from its metadata, validated. Read on the download client and retried as a download is, within\n * the calling read's `deadline` ({@link withDeadline}), so nothing but the one request in flight runs past it.\n */\n private async sizeOf(objectName: string, deadline: Deadline | undefined): Promise<number> {\n const [meta] = await retryDownload(\n () => withDeadline(() => this.downloadable(objectName).getMetadata(), deadline),\n deadline,\n );\n const size = Number(meta.size ?? 0);\n if (!Number.isSafeInteger(size) || size < 0) {\n throw new ValidationError(`GCS returned an invalid object size: ${String(meta.size)}`);\n }\n return size;\n }\n\n /**\n * The object's total size from a tail response, refused unless every header agrees with the bytes received. A 206\n * must carry `Content-Range: bytes a-b/total` that names exactly the suffix asked for and exactly the bytes held;\n * a 200 (a server that ignored the range) is the whole object, and is accepted only when it fits in the request.\n */\n private tailSize(res: ObjectRead, maxBytes: number, key: GenKey): number {\n const received = res.bytes.length;\n if (res.status === 200) {\n if (received > maxBytes)\n throw this.badTail(key, 'whole object returned past the requested length');\n return received;\n }\n if (res.status !== 206) throw this.badTail(key, `unexpected HTTP ${res.status}`);\n const header = singleHeader(res.headers, 'content-range');\n const m = header === undefined ? null : /^bytes (\\d+)-(\\d+)\\/(\\d+)$/.exec(header);\n if (m === null) throw this.badTail(key, `Content-Range is missing or malformed`);\n const [first, last, total] = [Number(m[1]), Number(m[2]), Number(m[3])];\n if (![first, last, total].every(Number.isSafeInteger)) {\n throw this.badTail(key, 'Content-Range holds a number that is not a safe integer');\n }\n if (\n last !== total - 1 ||\n first !== Math.max(0, total - maxBytes) ||\n last - first + 1 !== received\n ) {\n throw this.badTail(\n key,\n `Content-Range ${first}-${last}/${total} disagrees with ${received}B received for a ${maxBytes}B tail`,\n );\n }\n return total;\n }\n\n private badTail(key: GenKey, why: string): ValidationError {\n return new ValidationError(`GCS tail read of ${key.segment}.${key.generation} refused: ${why}`);\n }\n\n async delete(key: GenKey): Promise<void> {\n // Idempotent: `ignoreNotFound` so a racing/retried GC sweep of an absent object is a no-op.\n try {\n await this.file(storageObjectName(this.prefix, key)).delete({ ignoreNotFound: true });\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 let files;\n try {\n // autoPaginate (default) drains every page; a segment has few generations, so the set is small.\n [files] = await this.storage.bucket(this.bucket).getFiles({ prefix });\n } catch (err) {\n throw this.mapError(err);\n }\n for (const f of files) {\n const generation = parseGenerationFromName(prefix, f.name);\n if (generation !== null) {\n yield { namespace: ref.namespace, segment: ref.segment, generation };\n }\n }\n }\n\n /** Map GCS read errors to the driver vocabulary; pass everything else through {@link mapError}. */\n private mapReadError(err: unknown, key: GenKey): unknown {\n err = scrubCredentials(err);\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) \u2014 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 // Timed out on every attempt: transient, in words that name the read and the timeout.\n if (err instanceof ReadTimedOut) return new TransientError(err.message, { cause: err });\n // A connection that failed or was cut off, after the driver's retries: transient, whichever code Node gave it.\n if (isTransportFault(err)) {\n return new TransientError(\n `transient GCS fault: ${String((err as { code?: unknown }).code)}`,\n {\n cause: err,\n },\n );\n }\n return this.mapError(err);\n }\n\n /**\n * Reclassify a transient GCS fault (throttle/5xx/dropped connection) as a retryable {@link TransientError},\n * so the store's read retry can ride it out and a write's caller can tell it from a deterministic failure;\n * everything else propagates unchanged. The final fallback at every client-call site: an HTTP status and a fault\n * in transit arrive typed, and anything else (a checksum mismatch, a credentials failure) as the SDK raised it.\n */\n private mapError(err: unknown): unknown {\n err = scrubCredentials(err);\n if (isTransient(err)) {\n return new TransientError(\n `transient GCS fault: ${(err as { code?: unknown } | null)?.code ?? 'unknown'}`,\n { cause: err },\n );\n }\n return err;\n }\n}\n\ntype GcsFile = ReturnType<ReturnType<Storage['bucket']>['file']>;\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 * {@link BlobSink} that uploads one GCS object write-once. It **buffers up to a threshold**: an object that\n * finishes at/under it is committed in a **single simple (non-resumable) request** ({@link saveOnce}) \u2014 the write-once\n * path both real GCS and `fake-gcs-server` enforce; a larger one switches to a **resumable stream**, flushing the\n * buffer then piping the rest in constant memory (peak \u2248 one threshold). SHA-256 is hashed incrementally. Both\n * paths carry `ifGenerationMatch: 0` (create-only-if-absent) \u2192 a conflict is a 412, mapped by the driver to\n * {@link WriteConflictError}. On error the caller invokes {@link abort}.\n *\n * **The simple upload is sent with no SDK retry** ({@link saveOnce}): a replay of one that landed and lost its response\n * would find its own object and read as a lost race, so a lost response or a timeout throws {@link TransientError}. A\n * `429` or `503` is the one answer it is sent again after, by {@link sendSimple}. The resumable upload is a session of\n * requests, which the SDK retries within by asking the session how much it holds; the SDK takes that retry from the\n * client's options, not the call's, so it stays on. Each upload, of either kind, tags its object with a random id in\n * custom metadata, and a `412` that may be the upload meeting itself is read back: an object that carries this\n * upload's id is a success, and any other is the lost race. The read is paid only on that `412`.\n */\nclass GcsUploadSink implements BlobSink {\n private readonly hash: Hash = createHash('sha256');\n private total = 0;\n private readonly buffered: Uint8Array[] = [];\n private bufferedLen = 0;\n private stream: Writable | undefined; // set once we cross the threshold \u2192 resumable mode\n private done: Promise<unknown[]> | undefined;\n private failure: unknown;\n\n constructor(\n private readonly file: GcsFile,\n private readonly maxObjectBytes: number,\n private readonly threshold: number,\n private readonly clock: Sleeper,\n ) {}\n\n private static readonly WRITE_OPTS = {\n preconditionOpts: { ifGenerationMatch: 0 as const }, // write-once: create only if absent\n metadata: { contentType: 'application/octet-stream' },\n };\n\n /**\n * This write's own id, stored in the object's custom metadata (outside the `.crbm` bytes) so a conflict can be told\n * from a replay of this same write: the resumable session's, or the simple upload's re-send after a throttle.\n */\n private readonly writeId = randomBytes(16).toString('hex');\n\n /** The upload options with this write's id in the object's custom metadata. */\n private tagged(): typeof GcsUploadSink.WRITE_OPTS & {\n metadata: { metadata: Record<string, string> };\n } {\n return {\n ...GcsUploadSink.WRITE_OPTS,\n metadata: {\n ...GcsUploadSink.WRITE_OPTS.metadata,\n metadata: { [WRITE_ID_KEY]: this.writeId },\n },\n };\n }\n\n async write(bytes: Uint8Array): Promise<void> {\n if (this.failure !== undefined) throw this.failure;\n if (bytes.length === 0) return;\n this.total += bytes.length;\n if (this.total > this.maxObjectBytes) {\n throw new ValidationError(`object exceeds maxObjectBytes ${this.maxObjectBytes}`);\n }\n this.hash.update(bytes);\n if (this.stream !== undefined) {\n // Resumable mode: honor backpressure. `events.once(stream,'drain')` REJECTS if the stream emits 'error'\n // while we wait (that's its documented behavior for any awaited event except 'error'), and auto-removes\n // both listeners on settle \u2014 so there's no listener accumulation across drain cycles.\n if (!this.stream.write(bytes)) await once(this.stream, 'drain');\n return;\n }\n this.buffered.push(bytes);\n this.bufferedLen += bytes.length;\n if (this.bufferedLen > this.threshold) this.startResumable();\n }\n\n /** Cross into resumable streaming: open the stream, flush the buffered bytes, keep only ~one threshold resident. */\n private startResumable(): void {\n const stream = this.file.createWriteStream({ resumable: true, ...this.tagged() });\n this.stream = stream;\n this.done = once(stream, 'finish'); // resolves on a clean commit; rejects on 'error' (e.g. 412)\n stream.on('error', (e: unknown) => {\n this.failure ??= e; // a persistent listener so a stray 'error' is never an unhandled 'error' event\n });\n this.done.catch(() => undefined); // finish() awaits this; guard against unhandled rejection until then\n const buf = concatBytes(this.buffered, this.bufferedLen);\n this.buffered.length = 0;\n this.bufferedLen = 0;\n stream.write(buf); // \u2264 threshold+one-chunk; backpressure is absorbed by the next write()'s drain-await / end()\n }\n\n async finish(): Promise<{ size: number; sha256: string }> {\n const sha256 = this.hash.digest('hex');\n if (this.stream === undefined) {\n // Small object: one simple (non-resumable) upload \u2014 write-once enforced everywhere, the emulator too.\n await this.sendSimple(concatBytes(this.buffered, this.bufferedLen));\n return { size: this.total, sha256 };\n }\n try {\n if (this.failure !== undefined) throw this.failure;\n this.stream.end();\n await this.done; // rejects if the commit fails (e.g. 412 write-once conflict)\n } catch (err) {\n // The SDK retries a resumable session, so a commit that landed and lost its response can be answered `412` by\n // its own replay. Nothing overwrites a write-once object, so the id it holds says who wrote it. A failed\n // read-back throws, and reaches the caller as `TransientError` when the fault is transient: neither a success\n // nor a conflict.\n if (!isPreconditionFailed(err) || (await this.storedWriteId()) !== this.writeId) throw err;\n }\n return { size: this.total, sha256 };\n }\n\n /**\n * Send the simple upload once, and again after a `429` or `503`, up to {@link THROTTLE_RESENDS} times, waiting a\n * full-jitter backoff before each. Any other failure is thrown at once. Once it has been sent again, a `412` can be\n * an earlier send that landed after all, so the object's write id decides: this write's own is a success, and any\n * other object, or one with no id, is the conflict. A failed read-back throws, and is neither. Exhausted, it throws\n * {@link TransientError}; nothing is deleted.\n */\n private async sendSimple(body: Uint8Array): Promise<void> {\n for (let resent = 0; ; resent++) {\n try {\n await saveOnce(this.file, body, this.tagged());\n return;\n } catch (err) {\n if (resent > 0 && isPreconditionFailed(err)) {\n if ((await this.storedWriteId()) === this.writeId) return;\n throw err;\n }\n if (!isThrottle(err)) throw err;\n if (resent >= THROTTLE_RESENDS) {\n throw new TransientError(\n `GCS upload was throttled on each of its ${resent + 1} sends; the object may or may not exist`,\n { cause: err },\n );\n }\n await this.clock.sleep(Math.floor(Math.random() * THROTTLE_BASE_DELAY_MS * 2 ** resent));\n }\n }\n }\n\n /** The id the stored object carries in its custom metadata, or `undefined` when it carries none or is gone. */\n private async storedWriteId(): Promise<string | undefined> {\n try {\n const [meta] = await this.file.getMetadata();\n return meta.metadata?.[WRITE_ID_KEY] as string | undefined;\n } catch (err) {\n if (isNotFound(err)) return undefined;\n throw err;\n }\n }\n\n async abort(): Promise<void> {\n if (this.stream !== undefined && !this.stream.destroyed) this.stream.destroy();\n }\n}\n", "/**\n * Logical-ref \u2192 GCS object-name mapping for {@link GcsStorageDriver}.\n *\n * Pure string logic, no SDK dependency \u2014 unit-testable without GCS or an emulator. Uses the **same\n * backend-agnostic `.crbm` object-name scheme** as the S3 + LocalFs storage drivers\n * (`<prefix><ns>/segments/<segment>.<gen>.crbm`), so a segment reads identically whichever storage backend holds\n * it. The default (absent) namespace maps to `_default`, which cannot collide with a real namespace because a\n * caller's `_default` encodes to `%5Fdefault` while the sentinel is emitted literally.\n *\n * The storage-key layout is shared, not copied: `prefixPart` and the name encoders come from\n * `@cloudbitmaps/core/driver-kit`, so a segment written by one driver reads identically under another. Two\n * drivers disagreeing about how a name becomes a key would be a silent cross-driver incompatibility on the\n * same bucket, which is exactly the kind of thing a local copy drifts into.\n */\n\nimport {\n ValidationError,\n encodeNameForKey,\n namespaceKeyPart,\n prefixPart,\n validateSegmentRef,\n} from '@cloudbitmaps/core/driver-kit';\nimport type { GenKey, SegmentRef } from '@cloudbitmaps/core/driver-kit';\n\nconst SUFFIX = '.crbm';\n\n/** Validate a caller-supplied key prefix. The rule is shared with every other object store. */\nexport { normalizeObjectPrefix as normalizeGcsPrefix } from '@cloudbitmaps/core/driver-kit';\n\n/**\n * The GCS object-name prefix shared by all of a segment's generations: `<prefix><ns>/segments/<segment>.`.\n * Used both as the `getFiles` 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)}${namespaceKeyPart(ref.namespace)}/segments/${encodeNameForKey(ref.segment)}.`;\n}\n\n/** The full GCS object name of one `.crbm` generation: `<segmentPrefix><gen>.crbm`. */\nexport function storageObjectName(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 object name, given its segment prefix, or `null` if it doesn't\n * match. Canonical decimal only \u2014 no leading zeros (so `\u2026s.07.crbm` can't alias `\u2026s.7.crbm`) and within\n * safe-integer range. Also rejects a *different* segment whose name merely shares the prefix (its middle\n * 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 Google Cloud Storage SDK errors (the transient class mirrors S3's).\n *\n * SDK-free + side-effect-free \u2014 they only read structural shapes off the thrown value (`err.code`,\n * `err.response.status`, `err.name`), so the GCS-specific translation is unit-testable without a live GCS or\n * an emulator, and without importing `@google-cloud/storage`. GCS's `ApiError` carries the HTTP status on\n * `.code` (a number); dropped/timed-out sockets surface as a Node error with a string `.code` (`ECONNRESET`,\n * `ETIMEDOUT`, \u2026).\n */\n\n/** The HTTP status of a GCS `ApiError`, if present (`err.code` as a number, or `err.response.status`). */\nfunction httpStatus(err: unknown): number | undefined {\n const e = err as { code?: unknown; response?: { status?: unknown } } | null;\n if (typeof e?.code === 'number') return e.code;\n if (typeof e?.response?.status === 'number') return e.response.status;\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 `ifGenerationMatch: 0` write lost the write-once race \u2014 the object already existed, so GCS\n * returns **412 Precondition Failed**. Maps to `WriteConflictError` (caller OCC), never a blind retry.\n */\nexport function isPreconditionFailed(err: unknown): boolean {\n return httpStatus(err) === 412;\n}\n\n/**\n * GCS asked the client to slow down: `429` (`rateLimitExceeded`) or `503` (`backendError`), which the SDK raises as an\n * `ApiError` with the status as a numeric `code`. GCS does not document that a throttled request was not applied, so\n * an upload sent again after one must still tell a landed first send apart.\n */\nexport function isThrottle(err: unknown): boolean {\n const status = httpStatus(err);\n return status === 429 || status === 503;\n}\n\n/** The object / generation does not exist (GCS returns 404). */\nexport function isNotFound(err: unknown): boolean {\n return httpStatus(err) === 404;\n}\n\n/** A range request started past EOF (HTTP 416 Requested Range Not Satisfiable). */\nexport function isInvalidRange(err: unknown): boolean {\n return httpStatus(err) === 416;\n}\n\n/** The statuses the SDK's own retry predicate retries (`RETRYABLE_ERR_FN_DEFAULT` in `@google-cloud/storage`). */\nconst SDK_RETRIED_STATUSES = [408, 429, 500, 502, 503, 504];\n\n/** The connection faults the same predicate names, matched on a lower-cased code or reason as it does. */\nfunction isSdkConnectionProblem(reason: string): boolean {\n return (\n reason.includes('eai_again') ||\n reason === 'econnreset' ||\n reason === 'unexpected connection closure' ||\n reason === 'epipe' ||\n reason === 'socket connection timeout'\n );\n}\n\n/**\n * The codes Node gives a connection that failed or was cut off: refused, reset, aborted, timed out, a DNS failure, an\n * unreachable host or network, a broken pipe, and a chunked body cut off mid-stream (`ERR_STREAM_PREMATURE_CLOSE`).\n * Only these: a file the credentials name that is missing or unreadable (`ENOENT`, `EACCES`) and a TLS failure\n * (`EPROTO`, a certificate error) carry codes of the same shape, and would fail the same way again.\n */\nconst TRANSPORT_CODES = new Set([\n 'ECONNRESET',\n 'ECONNREFUSED',\n 'ECONNABORTED',\n 'ETIMEDOUT',\n 'ENOTFOUND',\n 'EAI_AGAIN',\n 'EHOSTUNREACH',\n 'ENETUNREACH',\n 'EPIPE',\n 'ERR_STREAM_PREMATURE_CLOSE',\n]);\n\n/** A download that failed in transit rather than with an HTTP status: one of {@link TRANSPORT_CODES}. */\nexport function isTransportFault(err: unknown): boolean {\n if (httpStatus(err) !== undefined) return false;\n const code = networkCode(err);\n return code !== undefined && TRANSPORT_CODES.has(code);\n}\n\n/**\n * Whether the driver retries a failed download, which it does in the SDK's place: the statuses the SDK's predicate\n * retries (408, 429, 500, 502, 503 and 504, as a number or a string `code`), the connection faults it names in any\n * `errors[].reason`, and every {@link isTransportFault}, which the SDK retried before any response came. A 404, 412,\n * 416 or 403, a 501 or 505, a credentials or TLS failure, and every other answer reach the caller on the first attempt.\n */\nexport function isDownloadRetryable(err: unknown): boolean {\n if (err === null || typeof err !== 'object') return false;\n if (isTransportFault(err)) return true;\n const e = err as { code?: unknown; errors?: unknown };\n if (typeof e.code === 'number' && SDK_RETRIED_STATUSES.includes(e.code)) return true;\n if (typeof e.code === 'string') {\n if (SDK_RETRIED_STATUSES.map(String).includes(e.code)) return true;\n if (isSdkConnectionProblem(e.code.toLowerCase())) return true;\n }\n if (Array.isArray(e.errors)) {\n for (const inner of e.errors as Array<{ reason?: unknown } | null>) {\n const reason = inner?.reason?.toString().toLowerCase();\n if (reason !== undefined && isSdkConnectionProblem(reason)) return true;\n }\n }\n return false;\n}\n\n/**\n * A transient GCS fault that is safe to retry: throttling (429), a request timeout (408), any 5xx, or a dropped/timed-out socket.\n * Excludes the deterministic, caller-meaningful outcomes (412/404/416) \u2014 those must never be reclassified as\n * a blind transient (a retried doomed conditional write would just fail again, and mask an OCC conflict).\n */\nexport function isTransient(err: unknown): boolean {\n if (isPreconditionFailed(err) || isNotFound(err) || isInvalidRange(err)) return false;\n const status = httpStatus(err);\n if (status === 408 || status === 429 || (status !== undefined && status >= 500 && status < 600))\n return true;\n const net = networkCode(err);\n return (\n net === 'ECONNRESET' ||\n net === 'ETIMEDOUT' ||\n net === 'ECONNREFUSED' ||\n net === 'EPIPE' ||\n net === 'EAI_AGAIN' ||\n net === 'ENOTFOUND'\n );\n}\n", "/**\n * The read timeout: `readTimeoutMs`, off (`0`) unless set. It bounds one driver read \u2014 a tail read (with the metadata\n * read it can fall back on), a range read, a registry read \u2014 as a whole: one deadline, started when the driver is\n * called, covers every attempt the driver makes at it and the backoff between them.\n *\n * `@google-cloud/storage` 8.x sets no working timeout of its own: it hands its `timeout` to an HTTP client that has no\n * such option, so a read on a connection that stops answering waits forever, and nothing above it ever gets a fault to\n * retry. When the deadline passes, the request in flight fails with {@link ReadTimedOut} (coded `ETIMEDOUT`), no\n * further attempt starts, and a backoff in progress ends with the same error; the driver reports it as\n * `TransientError`, its message naming the read and the timeout, for the store's read retry to run again.\n *\n * **What the clock counts.** Everything from the call into the driver: fetching or refreshing a credential, resolving\n * the project, waiting for a socket (Node's global agents, which the downloads use, set no socket limit unless the\n * process sets one, so by default there is no queue), each request, its headers and its whole body, and the driver's\n * backoff between attempts. It counts time the process spends busy too: Node runs a due timer before it reads a socket,\n * so a synchronous stretch longer than the timeout fails the reads in flight even when their responses have arrived.\n *\n * Uploads, deletes, listings and the conditional writes are not timed: an upload can rightly take longer than a read,\n * and a write cut off may still land.\n */\nimport { ValidationError } from '@cloudbitmaps/core/driver-kit';\n\n/** The default: no timeout. */\nconst DEFAULT_READ_TIMEOUT_MS = 0;\n\n/** The longest delay a Node timer holds. A longer one fires after 1 ms instead, so it is refused rather than passed on. */\nconst MAX_TIMER_MS = 2_147_483_647;\n\n/** Validate a caller's `readTimeoutMs`, defaulting it: an integer from 0 (no timeout) to {@link MAX_TIMER_MS}. */\nexport function resolveReadTimeoutMs(value: unknown): number {\n if (value === undefined) return DEFAULT_READ_TIMEOUT_MS;\n if (\n typeof value !== 'number' ||\n !Number.isSafeInteger(value) ||\n value < 0 ||\n value > MAX_TIMER_MS\n ) {\n throw new ValidationError(\n `readTimeoutMs must be a non-negative safe integer no larger than ${MAX_TIMER_MS}; got ${describe(value)}`,\n );\n }\n return value;\n}\n\n/** A value as an error message can show it: a string quoted, so `'200'` is not mistaken for `200`, and never a throw. */\nfunction describe(value: unknown): string {\n if (typeof value === 'string') return JSON.stringify(value);\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value);\n }\n return value === null ? 'null' : typeof value;\n}\n\n/** A read the driver gave up on when its deadline passed. Coded `ETIMEDOUT`, as a timed-out connection is. */\nexport class ReadTimedOut extends Error {\n readonly code = 'ETIMEDOUT';\n constructor(read: string, ms: number, cause?: unknown) {\n super(`GCS ${read} timed out after ${ms} ms`, cause === undefined ? undefined : { cause });\n this.name = 'ReadTimedOut';\n }\n}\n\n/** One driver read's deadline: `ms` from when it is made. `read` names the read in the error, e.g. `tail read of s.3`. */\nexport class Deadline {\n private readonly endsAt: number;\n\n constructor(\n readonly ms: number,\n readonly read: string,\n ) {\n this.endsAt = performance.now() + ms;\n }\n\n /** What is left of it, in milliseconds: 0 once it has passed. */\n remaining(): number {\n return Math.max(0, this.endsAt - performance.now());\n }\n\n /** The error a read fails with once this has passed; `cause` is the last attempt's own error, if one failed. */\n expired(cause?: unknown): ReadTimedOut {\n return new ReadTimedOut(this.read, this.ms, cause);\n }\n}\n\n/** The deadline a read made now runs under, or none when `ms` is 0 (no timeout). */\nexport function startDeadline(ms: number, read: string): Deadline | undefined {\n return ms === 0 ? undefined : new Deadline(ms, read);\n}\n\n/**\n * Make the request `send` starts, and settle with it, or with {@link ReadTimedOut} once `deadline` has passed; a\n * deadline already passed sends nothing. For a request the SDK cannot cancel: a metadata read is one callback-style\n * request that returns no handle to abort it, so at the deadline this stops waiting and the request runs on until it is\n * answered or its connection closes. The race keeps a handler on the request, so what it settles with then is dropped\n * rather than raised.\n */\nexport function withDeadline<T>(\n send: () => Promise<T>,\n deadline: Deadline | undefined,\n): Promise<T> {\n if (deadline === undefined) return send();\n if (deadline.remaining() === 0) return Promise.reject(deadline.expired());\n const request = send();\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(deadline.expired()), deadline.remaining());\n });\n return Promise.race([request, timedOut]).finally(() => clearTimeout(timer));\n}\n", "/**\n * `retryDownload` \u2014 run one download again after the faults the SDK itself would have retried.\n *\n * The client `GcsStorage` builds sends a download once (see `backend.ts`: a download the SDK retries can crash the\n * process), which also took away the SDK's quiet retry of a connection fault and of a 408, 429, 500, 502, 503 or 504.\n * This puts that retry back inside the driver, so every caller keeps it: the store's admin and write-path registry\n * reads, and a store built with `retry: false`, included. Which faults: `isDownloadRetryable`. How many: three\n * retries, the SDK's default, with full-jitter backoff of at most about 0.7 s a call. A chunk read that the store's\n * own retry also repeats makes at most 16 requests, as it did with the SDK's retry. A 404, 412, 403 and every other\n * answer reach the caller on the first attempt. What the last attempt raised is rethrown as it is, for the driver to\n * classify.\n *\n * **Under a read timeout** (`readTimeoutMs`), one {@link Deadline} covers every attempt: each attempt passes it to its\n * request, so the request in flight when it passes fails with {@link ReadTimedOut}; no attempt starts after it; and a\n * backoff that would outlast it ends when it passes, with the same error. Nor is a retry sent with less than\n * {@link MIN_RETRY_MS} left: no answer could arrive in time, and the request would only be one more held open. So a\n * server that never answers is sent one request per call, not four.\n */\nimport { isDownloadRetryable } from './gcs-errors';\nimport { ReadTimedOut, type Deadline } from './read-timeout';\n\nconst RETRIES = 3;\nconst BASE_DELAY_MS = 100;\nconst MAX_DELAY_MS = 1_000;\n/** Under a deadline, a retry is not sent with less than this left of it. */\nexport const MIN_RETRY_MS = 10;\n\n/**\n * Call `download`, and again after each retryable fault, up to {@link RETRIES} more times, within `deadline` when there\n * is one; rethrow the last error, or the deadline's once it has passed.\n */\nexport async function retryDownload<T>(\n download: () => Promise<T>,\n deadline?: Deadline,\n): Promise<T> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await download();\n } catch (err) {\n // The deadline passed during the attempt: nothing more is sent.\n if (err instanceof ReadTimedOut) throw err;\n if (attempt >= RETRIES || !isDownloadRetryable(err)) throw err;\n const left = deadline?.remaining() ?? Infinity;\n if (deadline !== undefined && left < MIN_RETRY_MS) throw deadline.expired(err);\n const ceiling = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * 2 ** attempt);\n const delay = Math.random() * ceiling;\n await new Promise((resolve) => setTimeout(resolve, Math.min(delay, left)));\n if (deadline !== undefined && deadline.remaining() < MIN_RETRY_MS)\n throw deadline.expired(err);\n }\n }\n}\n", "/**\n * Make an error the SDK raised safe to throw, or to keep as a `cause`: a copy of it with no credential and none of the\n * live transport.\n *\n * The SDK keeps the request it sent on the error (`response.request`, `config.headers`, the raw request line), and\n * through that request the HTTP agent it shares with every other call, whose sockets hold the raw text, bearer token\n * included, of requests still in flight. An application that logs the error or its cause chain would write a live\n * access token. The error is therefore replaced by a copy built from what it is: its prototype, name, message, stack,\n * code, status and other plain data; `response` reduced to its status; the request, agent, socket and config dropped.\n * Nothing live is ever changed, only read, and the copy is made from plain data alone, so no depth or size limit can\n * stop short of a secret. A credential header or header line in the plain data that is kept is redacted.\n */\n\n/** Properties that hold the raw transport or the request's configuration. They are not copied. */\nconst DROPPED = new Set([\n 'request',\n 'agent',\n 'socket',\n '_httpMessage',\n 'connection',\n 'req',\n 'res',\n 'config',\n]);\n/** Header and field names whose value is a credential. */\nconst CREDENTIAL_NAME = /^(?:proxy-)?authorization$|^cookie$|^set-cookie$|^x-api-key$/i;\n/** A credential header inside a raw request or response text. */\nconst CREDENTIAL_LINES = /^((?:proxy-)?authorization|cookie|set-cookie|x-api-key):[^\\r\\n]*/gim;\nconst REDACTED = '[redacted]';\n\nconst isPlain = (value: object): boolean => {\n const proto: unknown = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n};\n\n/** The part of an HTTP response worth keeping: what the server said, as numbers and text. */\nfunction responseSummary(response: object): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const key of ['status', 'statusText']) {\n const value: unknown = (response as Record<string, unknown>)[key];\n if (typeof value === 'number' || typeof value === 'string') out[key] = value;\n }\n return out;\n}\n\n/** A copy of `value` from plain data only: other objects (streams, headers, sockets) are left out. */\nfunction clean(value: unknown, copies: WeakMap<object, unknown>): unknown {\n if (typeof value === 'string') return value.replace(CREDENTIAL_LINES, `$1: ${REDACTED}`);\n if (typeof value !== 'object' || value === null) {\n return typeof value === 'function' || typeof value === 'symbol' ? undefined : value;\n }\n if (copies.has(value)) return copies.get(value);\n if (Array.isArray(value)) {\n const list: unknown[] = [];\n copies.set(value, list);\n for (const item of value) list.push(clean(item, copies));\n return list;\n }\n const isError = value instanceof Error;\n if (!isError && !isPlain(value)) return undefined;\n const copy = Object.create(Object.getPrototypeOf(value) as object | null) as Record<\n string | symbol,\n unknown\n >;\n copies.set(value, copy);\n for (const key of Reflect.ownKeys(value)) {\n if (typeof key === 'string' && DROPPED.has(key)) continue;\n let held: unknown;\n try {\n held = (value as Record<string | symbol, unknown>)[key];\n } catch {\n continue; // a getter that throws\n }\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n let kept: unknown;\n if (typeof key === 'string' && CREDENTIAL_NAME.test(key)) kept = held == null ? held : REDACTED;\n else if (key === 'response' && typeof held === 'object' && held !== null) {\n kept = responseSummary(held);\n } else kept = clean(held, copies);\n if (kept === undefined && held !== undefined) continue; // an object this copy leaves out\n Object.defineProperty(copy, key, {\n value: kept,\n writable: true,\n configurable: true,\n enumerable: descriptor?.enumerable ?? true,\n });\n }\n return copy;\n}\n\n/** Whether `err`, or an error in its `cause` chain, holds a response, a request or the rest of the transport. */\nfunction holdsTransport(err: object, seen: Set<object>): boolean {\n if (seen.has(err)) return false;\n seen.add(err);\n if (Object.getOwnPropertyNames(err).some((key) => DROPPED.has(key) || key === 'response')) {\n return true;\n }\n const cause: unknown = (err as { cause?: unknown }).cause;\n return typeof cause === 'object' && cause !== null && holdsTransport(cause, seen);\n}\n\n/**\n * `err` as it is safe to throw: a credential-free copy of an error that holds the SDK's request or response, and any\n * other value as it is (a timeout, a connection fault or an error of the driver's own holds no credential, and keeps\n * its identity).\n */\nexport function scrubCredentials<T>(err: T): T {\n if (typeof err !== 'object' || err === null) return err;\n return holdsTransport(err, new Set()) ? (clean(err, new WeakMap()) as T) : err;\n}\n", "/**\n * `saveOnce` \u2014 upload one object in a single request, sent once, with no SDK retry around it.\n *\n * `file.save()` runs a single-request upload inside a retry loop, and it counts an upload that carries\n * `ifGenerationMatch` as safe to repeat, so after a lost response \u2014 a timeout, a reset connection, a 5xx \u2014 it sends\n * the upload again. When the first had landed, the second meets it: `ifGenerationMatch: 0` finds the object it\n * created, a generation fence finds the generation it replaced, and GCS answers `412`, which the driver can only report\n * as a lost race. The caller is told its write lost when it won. Only the caller can find out which happened, by\n * reading the pointer or listing the generations, so the write is sent once and a transient failure reaches it as\n * `TransientError`.\n *\n * `createWriteStream({ resumable: false })` is that same single multipart request without the loop: the SDK sends a\n * streamed upload with no retries of its own, since a stream it has read cannot be read again. The client is not\n * touched, so a caller's own client keeps its retry options for every other call.\n *\n * **`resumable: false` is load-bearing twice.** It is what makes this one request, and it is the path on which\n * fake-gcs-server enforces `ifGenerationMatch`, so the integration lane tests the precondition rather than passing over\n * a write with none.\n */\nimport { once } from 'node:events';\nimport type { CreateWriteStreamOptions, Storage } from '@google-cloud/storage';\n\ntype GcsFile = ReturnType<ReturnType<Storage['bucket']>['file']>;\n\n/** Upload `body` to `file` in one request, sent once; resolves once GCS has stored it and its checksum matched. */\nexport async function saveOnce(\n file: GcsFile,\n body: Uint8Array,\n options: CreateWriteStreamOptions,\n): Promise<void> {\n const stream = file.createWriteStream({ ...options, resumable: false });\n const stored = once(stream, 'finish'); // rejects on 'error', which is how a failed upload surfaces\n stream.end(body);\n await stored;\n}\n", "/**\n * `readOnce` \u2014 read one object, or one range of it, in a single GET, with the response headers in hand.\n *\n * `file.download()` returns only the bytes. The headers carry what a caller needs alongside them \u2014 the object's\n * `generation` (the registry's version fence) and, on a ranged read, the object's total size in `Content-Range` \u2014 so\n * this reads through `createReadStream`, which announces the raw response before any data flows. Both come from the\n * same response as the bytes, so they describe one observation of the object.\n *\n * **Everything on the wire is untrusted.** The advertised `Content-Length` is checked against `maxBytes` before the\n * first byte is buffered, and the bytes are counted as they arrive, so a response that understates its length (or\n * omits it) still cannot make the caller hold more than `maxBytes`. The headers are returned as received; each caller\n * parses the ones it relies on strictly.\n *\n * **Every download stays off the SDK's shared connection pool.** When a download fails part-way \u2014 a body cut off, a\n * refused response, a timeout \u2014 the SDK destroys the HTTP agent the request went out on, to free its socket. By default\n * that is one keep-alive agent the whole process shares (teeny-request's, for every request with `forever: true`), so\n * every other request in flight on any client is reset with it, uploads included. {@link downloadFile} sets\n * `forever: false` on its own requests, through the SDK's request interceptors: they then go out on Node's global\n * agent, which the SDK never destroys, and on `@google-cloud/storage` 8.x a destroyed download closes its own socket\n * and nothing else. The cost: the global agent closes a connection idle for 5 seconds, where the SDK's pool keeps it,\n * so a read after a longer pause opens a new one; and it is shared with the process's other `http` and `https` code.\n */\nimport type { Storage } from '@google-cloud/storage';\nimport type { Deadline } from './read-timeout';\n\ntype GcsFile = ReturnType<ReturnType<Storage['bucket']>['file']>;\ntype Interceptor = GcsFile['interceptors'][number];\n/** What the SDK hands an interceptor: its typing says less, but every request it decorates carries a `uri`. */\ntype RequestOptions = ReturnType<Interceptor['request']>;\n\n/** Sends a request on Node's global agent rather than the SDK's shared keep-alive one (see the module comment). */\nconst OFF_THE_SHARED_AGENT: Interceptor = {\n request: (reqOpts) => ({ ...(reqOpts as RequestOptions), forever: false }),\n};\n\n/** A handle on `name` for downloading: its requests stay off the SDK's shared agent. The client is not touched. */\nexport function downloadFile(storage: Storage, bucket: string, name: string): GcsFile {\n const file = storage.bucket(bucket).file(name);\n file.interceptors.push(OFF_THE_SHARED_AGENT);\n return file;\n}\n\nexport interface ObjectRead {\n /** The HTTP status of the response (200 for a whole object, 206 for a range). */\n readonly status: number;\n /** The response headers, lower-cased as Node delivers them. */\n readonly headers: Readonly<Record<string, string | string[] | undefined>>;\n /** The body, at most `maxBytes` long. */\n readonly bytes: Uint8Array;\n}\n\n/**\n * The response the SDK announces. It is the HTTP layer's own body stream with the status and headers laid on it, and\n * while that body is in flight the SDK and the HTTP layer each run a pipeline over it, which with its own listeners is\n * eleven or twelve error and close listeners: one past Node's default limit of ten, so Node prints a possible-leak\n * warning for every read that is still arriving when it is let go. Nothing grows with the number of attempts, because\n * each attempt has a body of its own. The limit is raised on that one stream, to {@link BODY_LISTENER_LIMIT}.\n */\ninterface RawResponse {\n statusCode?: unknown;\n headers?: Record<string, string | string[] | undefined>;\n setMaxListeners?: (n: number) => unknown;\n}\n\n/** Room for the listeners the SDK's and the HTTP layer's pipelines put on one response body, with a few to spare. */\nconst BODY_LISTENER_LIMIT = 16;\n\n/** The value of a header that must appear once, or `undefined` when it is absent, repeated or empty. */\nexport function singleHeader(headers: ObjectRead['headers'], name: string): string | undefined {\n const value = headers[name];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/**\n * GET `file` (with the SDK's `createReadStream` options), buffering at most `maxBytes`. `oversize` builds the error\n * thrown when the advertised or actual length passes the cap, so each caller reports it in its own vocabulary.\n * Transport and HTTP errors reject as the SDK raises them (a 404 carries `code: 404`).\n *\n * With a `deadline`, a read still unsettled when it passes rejects with its `ReadTimedOut`; one asked for after it has\n * passed is not sent. The deadline belongs to the whole driver read, so an attempt gets only what is left of it.\n */\nexport function readOnce(\n file: GcsFile,\n options: { start?: number; end?: number; validation?: false; decompress?: false },\n maxBytes: number,\n oversize: (size: number | undefined) => Error,\n deadline?: Deadline,\n): Promise<ObjectRead> {\n if (deadline !== undefined && deadline.remaining() === 0)\n return Promise.reject(deadline.expired());\n return new Promise<ObjectRead>((resolve, reject) => {\n const stream = file.createReadStream(options);\n const chunks: Buffer[] = [];\n let total = 0;\n let status = 0;\n let headers: ObjectRead['headers'] = {};\n let responded = false;\n let settled = false;\n // The SDK makes no request until the stream is read from, in a later turn, so a timer set here covers all of it.\n const timer =\n deadline === undefined\n ? undefined\n : setTimeout(() => fail(deadline.expired()), deadline.remaining());\n\n // Deferred: the SDK builds its response pipeline right after announcing the response, and destroying the stream\n // before that makes the pipeline throw out of an event handler, where nothing can catch it, and ends the process.\n // One turn later the same destroy ends the read without a throw. So a read is let go only once its response has\n // been announced, never before: one that settles first (a timeout before any answer) is destroyed when its\n // response comes, if one ever does. Until then the SDK has no way to cancel it, so its request stays open until\n // the server answers or the connection closes.\n const release = (): void => void setImmediate(() => stream.destroy());\n\n const fail = (err: unknown): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n if (responded) release();\n reject(err);\n };\n\n stream.on('response', (res: RawResponse) => {\n responded = true;\n res.setMaxListeners?.(BODY_LISTENER_LIMIT);\n if (settled) return release();\n status = typeof res.statusCode === 'number' ? res.statusCode : 0;\n headers = res.headers ?? {};\n if (status < 200 || status > 299) return; // the SDK raises the error itself, with its own status\n const advertised = singleHeader(headers, 'content-length');\n if (advertised !== undefined && /^\\d+$/.test(advertised) && Number(advertised) > maxBytes) {\n fail(oversize(Number(advertised)));\n }\n });\n stream.on('data', (chunk: Buffer) => {\n if (settled) return;\n total += chunk.length;\n if (total > maxBytes) return fail(oversize(undefined));\n chunks.push(chunk);\n });\n stream.on('error', fail);\n stream.on('end', () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve({ status, headers, bytes: new Uint8Array(Buffer.concat(chunks, total)) });\n });\n // A net under the SDK: it reports a cut-off body as an 'error' itself, but a stream that closes without ending\n // or erring was cut off all the same, so it is a dropped connection, and retryable.\n stream.on('close', () =>\n fail(\n Object.assign(new Error('GCS read ended before the response completed'), {\n code: 'ECONNRESET',\n }),\n ),\n );\n });\n}\n", "/**\n * `GcsRegistryDriver` \u2014 an {@link IRegistryDriver} over Google Cloud Storage.\n *\n * Lets a **GCS deployment run on one bucket alone** \u2014 storage `.crbm` generations and the registry in the same\n * place, with no second cloud involved.\n *\n * The protocol (an ABA-safe OCC token, the delete and its tombstone, the bounded retry, the key layout) lives once\n * in {@link ObjectStoreRegistry}; this file is only the I/O calls GCS makes. A delete removes a row for good, under\n * `ifGenerationMatch: <generation>`, when `conditionalDelete` is `true`. It is off by default, on the public endpoint too:\n * no run against real GCS has verified that the service applies the precondition to a delete, and fake-gcs-server\n * accepts it and ignores it, so CI cannot show it.\n *\n * **The atomic swap is offloaded to GCS's object preconditions.** `ifGenerationMatch: 0` is create-only\n * (\"only if it does not exist\") and `ifGenerationMatch: <generation>` is compare-and-swap \u2014 the same pair S3\n * spells `If-None-Match: *` / `If-Match: <etag>`. A lost race returns `412` \u2192 {@link WriteConflictError}. Each write\n * is one request sent once ({@link saveOnce}), so a `412` means another write got there first, never this one meeting\n * itself after a lost response. A transient failure reaches the caller as {@link TransientError}: the write may or may\n * not have landed, and the caller re-reads the row to learn where it stands.\n *\n * **The version fence is the object's `generation`, not its ETag**: GCS mutates the generation on every\n * overwrite and it is the value `ifGenerationMatch` compares, so it is the only correct fence here. (Note\n * the unrelated collision of vocabulary \u2014 a GCS *object generation* has nothing to do with a CloudBitmaps\n * `.crbm` *generation*.) Reads are strongly consistent, satisfying the registry's `strongRead` contract.\n *\n * **Deployment requirements** (a policy that violates these silently corrupts the registry):\n * - The principal needs `storage.objects.get`, `create`, `update`, `list` and, to remove rows, `delete` on the\n * bucket. Without `list`\n * the registry cannot enumerate, and a missing-object read may surface as `403` rather than `404`.\n * - **Do not apply an Object Lifecycle rule to the `registry/` prefix that deletes a live object** (one on\n * noncurrent versions only is safe), and do not enable a retention\n * policy that blocks overwrite or deletion; see {@link ObjectStoreRegistry}.\n * - Object versioning is neither required nor used; the driver always reads the live generation. A read is one\n * GET whose `x-goog-generation` header and body describe the same object, so a concurrent overwrite is simply\n * observed as the older or the newer row, never as a torn pair or a missing one.\n */\nimport {\n IntegrityError,\n MAX_ROW_BYTES,\n ObjectStoreRegistry,\n TransientError,\n ValidationError,\n WriteConflictError,\n normalizeObjectPrefix,\n} from '@cloudbitmaps/core/driver-kit';\nimport type { ObjectRegistryStore, ObjectRow } from '@cloudbitmaps/core/driver-kit';\nimport type { Storage } from '@google-cloud/storage';\nimport { isNotFound, isPreconditionFailed, isTransient, isTransportFault } from './gcs-errors';\nimport { retryDownload } from './download-retry';\nimport { scrubCredentials } from './scrub-error';\nimport { downloadFile, readOnce, singleHeader } from './read-once';\nimport { ReadTimedOut, resolveReadTimeoutMs, startDeadline } from './read-timeout';\nimport { saveOnce } from './send-once';\n\nexport interface GcsRegistryDriverOptions {\n /** A constructed `@google-cloud/storage` `Storage` client (point `apiEndpoint` at fake-gcs-server locally). */\n readonly storage: Storage;\n /** The client the downloads go through; `storage` when absent (see `GcsStorageDriverOptions.readStorage`). */\n readonly readStorage?: Storage;\n /** Target bucket (must already exist). */\n readonly bucket: string;\n /** Optional object-name prefix under which all registry objects live (e.g. `cloudbitmaps/`). */\n readonly prefix?: string;\n /** Injected clock for `createdAt`/`updatedAt`; defaults to `Date.now`. */\n readonly now?: () => number;\n /**\n * Cut off a read of a registry row once it has run this long, in milliseconds, every attempt included; `0` (the\n * default) sets no timeout. Timed as the storage driver's reads are (see `GcsStorageDriverOptions.readTimeoutMs`);\n * the writes and listings are not timed.\n */\n readonly readTimeoutMs?: number;\n /**\n * Whether a delete removes a row for good, by an object delete sent with `ifGenerationMatch: <the generation it\n * read>`, rather than leaving a tombstone. Only a row born with an incarnation id is removed; a row a release before\n * 0.12 wrote is always tombstoned. Defaults to `false`, on the public endpoint too: whether real GCS applies\n * `ifGenerationMatch` to a delete has not been verified by a run against the service, and fake-gcs-server accepts the\n * precondition and ignores it, so CI cannot show it. Set `true` to remove rows for good.\n */\n readonly conditionalDelete?: boolean;\n}\n\n/** The calls {@link ObjectStoreRegistry} needs, in GCS's dialect. Exported for the tests that drive one directly. */\nexport class GcsRegistryStore implements ObjectRegistryStore {\n readonly label = 'GCS';\n\n constructor(\n private readonly storage: Storage,\n private readonly readStorage: Storage,\n private readonly bucket: string,\n private readonly readTimeoutMs: number,\n readonly conditionalDelete: boolean,\n ) {}\n\n /** A handle on one registry object. */\n private file(name: string) {\n return this.storage.bucket(this.bucket).file(name);\n }\n\n /** The same file handle on the download client, off the SDK's shared agent ({@link downloadFile}). */\n private downloadable(name: string) {\n return downloadFile(this.readStorage, this.bucket, name);\n }\n\n async read(key: string): Promise<ObjectRow | null> {\n // One GET: the response headers carry the `generation` fence and the length, and the body is the same\n // observation of the object, so the pair cannot straddle a concurrent overwrite.\n let res;\n try {\n // One deadline for the read, every attempt included.\n const deadline = startDeadline(this.readTimeoutMs, `registry read of ${key}`);\n res = await retryDownload(\n () =>\n readOnce(\n this.downloadable(key),\n {},\n MAX_ROW_BYTES,\n (size) =>\n new IntegrityError(\n size === undefined\n ? `registry object exceeds cap ${MAX_ROW_BYTES}B`\n : `registry object ${size}B exceeds cap ${MAX_ROW_BYTES}B`,\n ),\n deadline,\n ),\n deadline,\n );\n } catch (err) {\n if (isNotFound(err)) return null;\n throw mapReadError(err);\n }\n if (res.status !== 200) {\n throw new IntegrityError(`registry read answered HTTP ${res.status}, not 200: ${key}`);\n }\n // The fence is compared by `ifGenerationMatch` on the next write, so it is parsed here, where a header that\n // is missing or malformed can be refused before it is mistaken for a version.\n const generation = singleHeader(res.headers, 'x-goog-generation');\n if (\n generation === undefined ||\n !/^[1-9]\\d*$/.test(generation) ||\n !Number.isSafeInteger(Number(generation))\n ) {\n throw new IntegrityError(`registry read carries no usable x-goog-generation: ${key}`);\n }\n return { bytes: res.bytes, version: generation };\n }\n\n async write(\n key: string,\n body: Uint8Array,\n expect: 'absent' | { version: string },\n ): Promise<void> {\n try {\n // One request, sent once: a replay of a write that landed would fail its own precondition, and read as a lost\n // race. A registry row is a few hundred bytes, so a resumable session would also carry one round trip's worth\n // of data over two.\n await saveOnce(this.file(key), body, {\n contentType: 'application/json',\n preconditionOpts: {\n ifGenerationMatch: expect === 'absent' ? 0 : generationFence(expect.version, key),\n },\n });\n } catch (err) {\n if (isPreconditionFailed(err)) {\n throw new WriteConflictError(`registry OCC conflict for ${key}`);\n }\n throw mapError(err);\n }\n }\n\n async delete(key: string, expect: { version: string }): Promise<void> {\n try {\n // The SDK may send this again after a lost response, as it does any request with a precondition. The\n // precondition names one generation, so a second copy removes nothing the first could not; one that meets the\n // first's landed delete is a 404, a conflict, and the registry re-reads and finds the row gone.\n await this.file(key).delete({ ifGenerationMatch: generationFence(expect.version, key) });\n } catch (err) {\n // A 412 (the generation moved on) or a 404 (the object is gone): the version to delete is not there.\n if (isPreconditionFailed(err) || isNotFound(err)) {\n throw new WriteConflictError(`registry OCC conflict deleting ${key}`);\n }\n throw mapError(err);\n }\n }\n\n async *listKeys(prefix: string): AsyncIterable<string> {\n let pageToken: string | undefined;\n do {\n let files;\n let next: { pageToken?: string } | null | undefined;\n try {\n // Explicit paging rather than autoPaginate: a registry can hold far more rows than a segment holds\n // generations, and draining every page into memory first is what `list()` exists to avoid.\n [files, next] = await this.storage\n .bucket(this.bucket)\n .getFiles({ prefix, autoPaginate: false, maxResults: 1000, pageToken });\n } catch (err) {\n throw mapError(err);\n }\n for (const f of files ?? []) yield f.name;\n pageToken = next?.pageToken;\n } while (pageToken !== undefined);\n }\n}\n\n/**\n * Narrow a version fence back to the number `ifGenerationMatch` takes. The fence always originates as\n * the `x-goog-generation` header in {@link GcsRegistryStore.read}, so this cannot fire in practice \u2014 but `Number()` answers `NaN`\n * for anything non-numeric, and `ifGenerationMatch: NaN` serializes to a precondition GCS ignores. That\n * failure is invisible (writes simply stop being fenced), so it is checked rather than assumed.\n */\nfunction generationFence(version: string, key: string): number {\n const generation = Number(version);\n if (!Number.isSafeInteger(generation) || generation <= 0) {\n throw new IntegrityError(`registry version fence is not a GCS generation: ${key}`);\n }\n return generation;\n}\n\n/** A read's error: a connection that failed or was cut off is transient too, after the driver's retries. */\nfunction mapReadError(err: unknown): unknown {\n err = scrubCredentials(err);\n // Timed out on every attempt: transient, in words that name the read and the timeout.\n if (err instanceof ReadTimedOut) return new TransientError(err.message, { cause: err });\n if (isTransportFault(err)) {\n return new TransientError(`transient GCS fault: ${String((err as { code?: unknown }).code)}`, {\n cause: err,\n });\n }\n return mapError(err);\n}\n\n/** Reclassify a transient GCS fault as a retryable {@link TransientError}; pass everything else through. */\nfunction mapError(err: unknown): unknown {\n err = scrubCredentials(err);\n if (isTransient(err)) {\n return new TransientError(\n `transient GCS fault: ${(err as { code?: unknown } | null)?.code ?? (err as { name?: string } | null)?.name ?? 'unknown'}`,\n { cause: err },\n );\n }\n return err;\n}\n\n/** {@link GcsRegistryDriverOptions.conditionalDelete}, checked, or its default: off. */\nfunction resolveConditionalDelete(value: unknown): boolean {\n if (value === undefined) {\n return false;\n }\n if (typeof value !== 'boolean') {\n throw new ValidationError(`conditionalDelete must be a boolean; got ${String(value)}`);\n }\n return value;\n}\n\nexport class GcsRegistryDriver extends ObjectStoreRegistry {\n constructor(options: GcsRegistryDriverOptions) {\n super(\n new GcsRegistryStore(\n options.storage,\n options.readStorage ?? options.storage,\n options.bucket,\n resolveReadTimeoutMs(options.readTimeoutMs),\n resolveConditionalDelete(options.conditionalDelete),\n ),\n normalizeObjectPrefix(options.prefix),\n options.now ?? ((): number => Date.now()),\n );\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["ValidationError", "ValidationError", "once", "ValidationError", "ValidationError", "once", "TransientError", "ValidationError", "WriteConflictError", "normalizeObjectPrefix", "WriteConflictError", "TransientError", "ValidationError", "normalizeObjectPrefix", "ValidationError"]
7
7
  }
package/dist/keys.d.ts CHANGED
@@ -13,12 +13,8 @@
13
13
  * same bucket, which is exactly the kind of thing a local copy drifts into.
14
14
  */
15
15
  import type { GenKey, SegmentRef } from '@cloudbitmaps/core/driver-kit';
16
- /**
17
- * Validate the caller-supplied key prefix (trusted config, but a real containment boundary): reject control
18
- * characters and `.`/`..` path segments so a prefix can't traverse out of its intended space. Returns it
19
- * unchanged (the key builders normalize slashes).
20
- */
21
- export declare function normalizeGcsPrefix(prefix: string | undefined): string | undefined;
16
+ /** Validate a caller-supplied key prefix. The rule is shared with every other object store. */
17
+ export { normalizeObjectPrefix as normalizeGcsPrefix } from '@cloudbitmaps/core/driver-kit';
22
18
  /**
23
19
  * The GCS object-name prefix shared by all of a segment's generations: `<prefix><ns>/segments/<segment>.`.
24
20
  * Used both as the `getFiles` prefix and as the string stripped by {@link parseGenerationFromName}.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Make an error the SDK raised safe to throw, or to keep as a `cause`: a copy of it with no credential and none of the
3
+ * live transport.
4
+ *
5
+ * The SDK keeps the request it sent on the error (`response.request`, `config.headers`, the raw request line), and
6
+ * through that request the HTTP agent it shares with every other call, whose sockets hold the raw text, bearer token
7
+ * included, of requests still in flight. An application that logs the error or its cause chain would write a live
8
+ * access token. The error is therefore replaced by a copy built from what it is: its prototype, name, message, stack,
9
+ * code, status and other plain data; `response` reduced to its status; the request, agent, socket and config dropped.
10
+ * Nothing live is ever changed, only read, and the copy is made from plain data alone, so no depth or size limit can
11
+ * stop short of a secret. A credential header or header line in the plain data that is kept is redacted.
12
+ */
13
+ /**
14
+ * `err` as it is safe to throw: a credential-free copy of an error that holds the SDK's request or response, and any
15
+ * other value as it is (a timeout, a connection fault or an error of the driver's own holds no credential, and keeps
16
+ * its identity).
17
+ */
18
+ export declare function scrubCredentials<T>(err: T): T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbitmaps/gcs",
3
- "version": "0.18.1",
3
+ "version": "0.18.3",
4
4
  "description": "Google Cloud Storage for CloudBitmaps",
5
5
  "keywords": [
6
6
  "cloudbitmaps",
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@google-cloud/storage": "^7 || ^8",
52
- "@cloudbitmaps/core": "^0.18.1"
52
+ "@cloudbitmaps/core": "^0.18.3"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/node": "^22.0.0",