@cloudbitmaps/gcs 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,16 +16,7 @@ pnpm add @cloudbitmaps/roaring @cloudbitmaps/gcs
16
16
 
17
17
  Install the same version of every `@cloudbitmaps/*` package: they release together, and each pins the exact version of `core` it was built with.
18
18
 
19
- `roaring` needs its one install script to fetch its native binary, and newer clients run a dependency's script only if you allow it. Allow it before you install:
20
-
21
- - **npm 12:** add `{ "allowScripts": { "roaring": true } }` to your `package.json`. Without it the install exits 0 and the package throws at `import`. npm 11 runs the script but warns until you allow it the same way.
22
- - **pnpm 11 and later:** put `allowBuilds: { roaring: true }` in a `pnpm-workspace.yaml` beside your `package.json`. Those versions no longer read the `package.json` `pnpm` key. Without it the install exits 1 with `ERR_PNPM_IGNORED_BUILDS`.
23
- - **pnpm 10:** add `{ "pnpm": { "onlyBuiltDependencies": ["roaring"] } }` to your `package.json`. Without it the install exits 0 and the package throws at `import`.
24
- - **pnpm 9:** nothing extra.
25
-
26
- [Details](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/getting-started.md#cannot-find-module-buildreleaseroaringnode-after-a-successful-install).
27
-
28
- `@google-cloud/storage` (`^7 || ^8`) is a real dependency of this package, so installing it is the whole step.
19
+ `@google-cloud/storage` (`^8.1.0`) is a real dependency of this package, so installing it is the whole step. A `client` you pass must come from 8.1.0 or later: the driver's own tests pass on 8.1.0 and fail on every older release tried (8.0.1, 8.0.0, and 7.x from 7.0.0 to 7.22.0). On 8.0.1 the unit tests pass, but the integration test of a resumable upload's tail size against the emulator fails with a reset connection. On 8.0.0 and on 7.x the unit tests fail: a read the driver cuts off at `readTimeoutMs` leaves its connection open, a read whose first answer is cut off part-way is not retried to success (the call never settles), and a read the driver refuses does not release its request.
29
20
  `@cloudbitmaps/core`, the engine underneath, arrives with it and you never name it.
30
21
 
31
22
  ## Use
@@ -71,8 +62,8 @@ It builds its own client from Application Default Credentials. Any other key is
71
62
  `unchecked`. It needs `storage.buckets.get`, which a load never uses, `store.preflight()`'s lifecycle step does, and `roles/storage.objectAdmin` does
72
63
  not include: without it the answer is `unchecked`, naming the permission
73
64
  ([check the lifecycle rules from code](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/production.md#check-the-lifecycle-rules-from-code)).
74
- - **The SDK does not retry downloads; the driver does.** In `@google-cloud/storage` 7.x and 8.x (checked on 7.22.0 and
75
- 8.1.0), a download the SDK retries after any status it retries (408, 429, 500, 502, 503 or 504) can crash the process
65
+ - **The SDK does not retry downloads; the driver does.** In `@google-cloud/storage` 8.x (checked on 8.1.0) and 7.x (checked on
66
+ 7.22.0, a release outside this driver's range), a download the SDK retries after any status it retries (408, 429, 500, 502, 503 or 504) can crash the process
76
67
  with `ERR_STREAM_UNABLE_TO_PIPE`, thrown outside any promise, even though the retried request succeeded. The client
77
68
  `GcsStorage` builds therefore sends each download once, and the driver runs a download again itself, up to three more
78
69
  times with backoff, after a connection fault (refused, reset, timed out, a DNS failure, a body cut off) or a 408, 429,
@@ -105,8 +96,9 @@ It builds its own client from Application Default Credentials. Any other key is
105
96
  - **A read that times out before its server answers leaves its connection open.** The SDK cannot cancel a request
106
97
  whose response has not begun, so that request holds its connection until the server answers or closes it, and until
107
98
  then keeps a Node process from exiting: one per driver read, up to four per call through the store's retry, so an
108
- outage at N reads a second adds about 4N held connections each second. On 8.x a read cut off after its response
109
- began closes its own; on 7.x (checked on 7.22.0) it stays open too. A 404 whose error body arrives after the
99
+ outage at N reads a second adds about 4N held connections each second. On every version this driver's range admits
100
+ (8.1.0 and later) a read cut off after its response began closes its own; on 8.0.0 and 7.x (checked on 7.22.0), both
101
+ outside the range, it stays open too. A 404 whose error body arrives after the
110
102
  deadline is reported as a `TransientError`, not `NotFoundError`.
111
103
  - **Downloads go out on Node's global agent, so one the driver cuts off or refuses resets nothing else.** When a
112
104
  download fails part-way, the SDK destroys the HTTP agent it went out on, and its own agent is one keep-alive pool the
package/dist/backend.d.ts CHANGED
@@ -14,7 +14,7 @@ export interface GcsStorageOptions {
14
14
  * A constructed `@google-cloud/storage` client. One is built from the ambient credentials when absent. Its retry
15
15
  * options apply to every request except the single-request conditional writes, which the driver sends without
16
16
  * that retry whatever they say (a registry row once, and a generation's object again only after a throttle, under
17
- * its own backoff), and the downloads. In `@google-cloud/storage` 7.x and 8.x a download the SDK retries after a 408,
17
+ * its own backoff), and the downloads. In `@google-cloud/storage` 8.x, and in 7.x (which the driver's range does not admit), a download the SDK retries after a 408,
18
18
  * 429, 500, 502, 503 or 504 can crash the process with `ERR_STREAM_UNABLE_TO_PIPE`, so downloads go through a twin of
19
19
  * this client, built from its own class with the same credentials object, endpoint and settings and the SDK's retries
20
20
  * off over any its class set, and the driver retries them itself. A client whose twin would use other credentials or
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/backend.ts", "../src/defined.ts", "../src/read-timeout.ts", "../src/scrub-error.ts", "../src/lifecycle.ts", "../src/storage.ts", "../src/bucket-presence.ts", "../src/gcs-errors.ts", "../src/download-retry.ts", "../src/send-once.ts", "../src/read-once.ts", "../src/registry.ts", "../src/crc32c.ts", "../src/read-client.ts"],
4
- "sourcesContent": ["import {\n STORAGE_BACKEND,\n ValidationError,\n brandAsBackend,\n normalizeObjectPrefix,\n prefixPart,\n refuseUnknownOptions,\n requireNonEmptyString,\n} from '@cloudbitmaps/core/driver-kit';\nimport type {\n IRegistryDriver,\n IStorageDriver,\n LifecycleVerdict,\n StorageBackend,\n} from '@cloudbitmaps/core/driver-kit';\nimport { Storage as GcsClient } from '@google-cloud/storage';\nimport { defined } from './defined';\nimport { checkGcsLifecycle } from './lifecycle';\nimport { resolveReadTimeoutMs } from './read-timeout';\nimport { GcsStorageDriver } from './storage';\nimport { GcsRegistryDriver } from './registry';\nimport { downloadClient } from './read-client';\n\n/**\n * What {@link GcsStorage} takes: a `bucket`, and either a `client` or the two settings that build one (`projectId`,\n * `apiEndpoint`), never both.\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. A string, else `ValidationError`. */\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), and the downloads. In `@google-cloud/storage` 7.x and 8.x a download the SDK retries after a 408,\n * 429, 500, 502, 503 or 504 can crash the process with `ERR_STREAM_UNABLE_TO_PIPE`, so downloads go through a twin of\n * this client, built from its own class with the same credentials object, endpoint and settings and the SDK's retries\n * off over any its class set, and the driver retries them itself. A client whose twin would use other credentials or\n * another endpoint (a subclass that builds from options of its own), whose class overrides `bucket` (a test double\n * built on `Storage`), or whose `bucket` is replaced on the instance (a test stub; stub `Storage.prototype` instead)\n * is refused at construction. A test double that is not a `Storage` client is used as it is when it has\n * `retryOptions: { autoRetry: false }`, and refused without it. What is set on the client after the backend was\n * built, other than an interceptor, is not carried to the twin.\n */\n readonly client?: GcsClient;\n /**\n * Project id for the client built when `client` is absent (refused beside `client`): a non-empty string, else\n * `ValidationError`. Falls back to the SDK's own resolution.\n */\n readonly projectId?: string;\n /**\n * Endpoint override \u2014 point it at fake-gcs-server locally: a non-empty string, since an empty one would send\n * requests to the public endpoint, else `ValidationError`. Refused beside `client`, which carries its own.\n */\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 service applies `ifGenerationMatch` on an object delete, which both halves rely on when it is set: the\n * registry removes a deleted row for good rather than leaving a tombstone a full listing reads forever, and the\n * storage half removes an erasure's holder only while it is the object the erasure searched, by its generation.\n * Defaults to `false`, on the public endpoint too: whether real GCS applies `ifGenerationMatch` to a delete has not\n * been verified by a run against the service, and fake-gcs-server accepts the precondition and ignores it, so CI\n * cannot show it. Set `true` once you know it does. Until then, the window in which an erasure's stalled delete\n * takes a generation a load put under the number since stays open on GCS.\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/**\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 */\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 private readonly bucket: string;\n /** The name prefix every object sits under: `''`, or `trimmed/`. */\n private readonly keyPrefix: string;\n private readonly readTimeoutMs: number;\n\n /**\n * Builds the GCS backend over one bucket, which `bucket` names. Pass `client`, or `projectId` and `apiEndpoint` to\n * have one built, not both. `prefix` puts every object under a name prefix. Throws `ValidationError` for an unknown\n * option, no bucket, a `now` that is not a function, a client setting beside `client`, a `maxObjectBytes` or\n * `simpleUploadThresholdBytes` that is not a positive integer, a `conditionalDelete` that is not a boolean, an\n * empty or non-string `apiEndpoint` or `projectId`, or an invalid `prefix` or `readTimeoutMs`.\n */\n constructor(options: GcsStorageOptions) {\n let readClient: GcsClient;\n refuseUnknownOptions(\n 'GcsStorage',\n options,\n GCS_STORAGE_OPTION_KEYS,\n 'a @google-cloud/storage client goes in `client`',\n );\n if (typeof options.bucket !== 'string' || options.bucket.trim().length === 0) {\n throw new ValidationError('GcsStorage needs a `bucket`: the name of one, a non-empty string');\n }\n if (options.now !== undefined && typeof options.now !== 'function') {\n throw new ValidationError(\n \"GcsStorage's `now` must be a function that returns epoch milliseconds\",\n );\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 = downloadClient(options.client, true);\n } else {\n // An empty `apiEndpoint` is no endpoint: the client would send to the public cloud, not the service meant.\n const apiEndpoint = requireNonEmptyString('GcsStorage: `apiEndpoint`', options.apiEndpoint);\n // A bare scheme names no host; the client's own refusal of it talks about an environment variable instead.\n if (apiEndpoint !== undefined && /^[a-z][a-z0-9+.-]*:\\/*$/i.test(apiEndpoint.trim())) {\n throw new ValidationError(\n 'GcsStorage: `apiEndpoint` must name a host, such as \"http://localhost:4443\" or \"storage.googleapis.com\"',\n );\n }\n requireNonEmptyString('GcsStorage: `projectId`', options.projectId);\n this.client = new GcsClient(\n defined({ projectId: options.projectId, apiEndpoint: options.apiEndpoint }),\n );\n // Downloads go through a twin that sends each once (see `read-client.ts`); it shares this client's credentials\n // object, so one token fetch and one hourly refresh serve both.\n readClient = downloadClient(this.client, false);\n }\n this.bucket = options.bucket;\n this.keyPrefix = prefixPart(normalizeObjectPrefix(options.prefix));\n this.readTimeoutMs = resolveReadTimeoutMs(options.readTimeoutMs);\n const shared = {\n storage: this.client,\n readStorage: readClient,\n bucket: options.bucket,\n ...defined({ prefix: options.prefix, readTimeoutMs: options.readTimeoutMs }),\n };\n this.storage = new GcsStorageDriver({\n ...shared,\n ...defined({\n maxObjectBytes: options.maxObjectBytes,\n simpleUploadThresholdBytes: options.simpleUploadThresholdBytes,\n conditionalDelete: options.conditionalDelete,\n }),\n });\n this.registry = new GcsRegistryDriver({\n ...shared,\n ...defined({ now: options.now, conditionalDelete: options.conditionalDelete }),\n });\n brandAsBackend(this);\n }\n\n /**\n * Reads the bucket's metadata (one request, plus the SDK's own retries, bounded by `readTimeoutMs` when it is set) and\n * reports a lifecycle rule with a `Delete` action (or one it does not know) that applies to live\n * objects whose names can be this store's: generations and registry rows alike. Needs `storage.buckets.get`, which\n * a load never uses (`store.preflight()`'s lifecycle step does) and `roles/storage.objectAdmin` does not include: without it the answer is\n * `unchecked`, naming the permission. Never rejects; a `detail` carries no credential. Rule reading: see the\n * production guide.\n */\n checkLifecycle(): Promise<LifecycleVerdict> {\n return checkGcsLifecycle(this.client, this.bucket, this.keyPrefix, this.readTimeoutMs);\n }\n}\n", "/**\n * `values` without the entries that are `undefined`, typed as optional properties, so a constructor can pass on the\n * options it was given: `exactOptionalPropertyTypes` refuses `{ x: undefined }` for an optional `x`, and a sub-driver\n * must see an option the caller left out as absent.\n */\nexport function defined<T extends Record<string, unknown>>(\n values: T,\n): { [K in keyof T]?: Exclude<T[K], undefined> } {\n return Object.fromEntries(Object.entries(values).filter(([, v]) => v !== undefined)) as {\n [K in keyof T]?: Exclude<T[K], undefined>;\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 * Remove credentials from an error the GCS SDK raised, before the driver throws it or keeps it as a `cause`.\n *\n * The SDK keeps the response it got on the error (`response`). On an `ApiError` that is the HTTP layer's response\n * stream, whose `request` holds the headers it sent, the bearer token in `Authorization` included, and the HTTP agent\n * it shares with every other call, whose sockets hold the raw text of requests still in flight. A `GaxiosError` (a\n * resumable upload's, or a token request's) keeps the request's `config`, with its headers and body, and a `response`\n * that holds another copy of it. On a download, and wherever a body lists no errors, the SDK makes the body the error's\n * message as it came, so a proxy's error page or a JSON body can quote the `Authorization` value, or any other header,\n * anywhere in a line. The live objects are never changed (a socket still sending reads them): an error that holds a\n * transport, or whose data shows a credential, is replaced by a deep copy of its plain data. An error with neither is\n * returned as it is.\n *\n * The copy is a native error with the original's prototype (so `instanceof ApiError` holds), name, message, stack,\n * `code`, `status`, `errors` and every other plain field, with `response` reduced to its status and status text: what\n * classification reads. It has no `config`, request, agent or socket, no function or accessor (a `toJSON` or\n * `util.inspect` hook is a closure over the original) and no class instance other than an error. Errors are copied\n * wherever they sit: the `cause` chain, an `AggregateError`'s `errors`, any array or field. A table of copies keeps a\n * cycle a cycle, and anything nested deeper than `MAX_DEPTH` is dropped rather than kept raw.\n *\n * The value under a credential's name is replaced by `[redacted]`: a credential header's (`authorization`, the\n * encryption key's `x-goog-encryption-key`), a signed URL's signature and credential, what a service quotes of a request\n * whose signature it refused, an OAuth token or client secret, a service account's private key (its `client_email` and\n * key id are kept: they name the account and sign nothing); and so is the value after one in a flat list, under one\n * in a map, or beside one in a record (a `{ name, value }` pair, a `{ th, td }` row), except a number, a boolean and\n * the fields a Google error item describes an error with (`message`, `reason`, `location`), which classification reads.\n * In the copy's text, these are redacted: a credential header line; an `Authorization` value, an encryption key, an API\n * key, an OAuth token or client secret and a signed URL's signature or credential after its name; a cookie header's\n * value after its name; a bearer token after its scheme; a Google access token (`ya29.`) or a JWT on its own; a V2\n * signed URL's `Signature=`; a PEM private key's body; the text of an element named for a credential\n * (`<CanonicalRequest>`); and the text of the cell after one that names a credential (a table's `<th>` and `<td>`, a\n * list's `<dt>` and `<dd>`). A credential in any other form is not recognised (a header table laid out by column, a\n * colon written as an HTML entity in a body the SDK did not decode), and a text these steps cannot finish is replaced\n * whole.\n */\n\nimport { types } from 'node:util';\n\n/** Properties that hold the raw transport or the request's configuration. They are not copied. */\nconst TRANSPORT = new Set([\n 'request',\n 'agent',\n 'socket',\n '_httpMessage',\n 'connection',\n 'req',\n 'res',\n 'config',\n]);\n/** What a copy keeps of a `response`: what the server said, as numbers and text. */\nconst RESPONSE_FIELDS = ['status', 'statusText', 'statusCode', 'statusMessage'];\n/**\n * Header and field names whose value is a credential: the credential headers, a customer-supplied encryption key (as\n * a header and as the SDK's option), a signed URL's signature and credential, what a service quotes of a request whose\n * signature it refused, and the OAuth and service-account fields that hold a secret. A service account's `client_email`\n * and `private_key_id` are not among them: they name the account and its key, and sign nothing.\n */\nconst CREDENTIAL_NAMES = [\n 'authorization',\n 'proxy-authorization',\n 'cookie',\n 'set-cookie',\n 'x-api-key',\n 'x-goog-api-key',\n 'x-goog-encryption-key',\n 'x-goog-copy-source-encryption-key',\n 'encryption-key',\n 'encryption-key-base64',\n 'x-goog-signature',\n 'x-goog-credential',\n 'signature',\n 'signature-provided',\n 'canonical-request',\n 'token',\n 'access-token',\n 'refresh-token',\n 'id-token',\n 'subject-token',\n 'client-secret',\n 'private-key',\n 'assertion',\n];\n/** A name as it is compared: without the spaces and colon around it, in lower case, with `-` and `_` taken out. */\nconst fold = (name: string): string =>\n name.trim().replace(/:$/, '').replace(/[-_]/g, '').toLowerCase();\nconst CREDENTIAL_NAME = new Set(CREDENTIAL_NAMES.map(fold));\n/** The longest text that can spell one of the names. */\nconst LONGEST_NAME = 64;\n/** A credential header line inside raw request or response text. */\nconst CREDENTIAL_LINES =\n /^((?:proxy-)?authorization|cookie|set-cookie|x-api-key|x-goog-api-key|x-goog-(?:copy-source-)?encryption-key):[^\\r\\n]*/gim;\n/**\n * An `Authorization` value after its name anywhere in a text, its scheme with it: `authorization: Basic <value>`,\n * `authorization=Bearer%20<token>`, JSON's `\"authorization\":\"Bearer <token>\"` (also escaped once more).\n */\nconst AUTHORIZATION_VALUE =\n /((?:proxy-)?authorization\\\\?[\"']?\\s*(?::|=|%3D)\\s*\\\\?[\"']?)(?:[A-Za-z][\\w-]*(?:\\s+|%20|\\+))?[^\\s&\"'<>\\\\,;]+/gi;\n/**\n * A cookie header's value after its name anywhere in a text, every cookie in it: `cookie: a=1; b=2`, JSON's\n * `\"cookie\":\"a=1; b=2\"`. The name starts a word (`fortunecookie: 7` is not one), and the value runs to the end of the\n * line, a quote or a tag, since a cookie's value may itself hold `)` or `.`.\n */\nconst COOKIE_VALUE = /(?<![\\w-])((?:set-)?cookie\\\\?[\"']?\\s*:\\s*\\\\?[\"']?)[^\\r\\n\"'<>\\\\]+/gi;\n/** The headers and fields whose value follows their name in a text: an encryption key, an API key, a signed URL's parts. */\nconst NAMED =\n 'x-goog-(?:copy-source-)?encryption-key|x-goog-api-key|x-goog-signature|x-goog-credential|' +\n '(?:access|refresh|id|subject)[-_]token|client[-_]secret';\n/**\n * One of those after its name anywhere in a text: `name: value`, `name=value` (also percent-encoded) or JSON's\n * `\"name\":\"value\"` (also escaped once more).\n */\nconst NAMED_SECRET = new RegExp(\n `((?:${NAMED})\\\\\\\\?[\"']?\\\\s*(?::|=|%3D)\\\\s*\\\\\\\\?[\"']?)[^\\\\s&\"'<>\\\\\\\\,;]+`,\n 'gi',\n);\n/** A bearer token after its scheme, wherever the text shows it, also percent-encoded. */\nconst BEARER = /\\b(Bearer)(\\s+|%20|\\+)(?=[A-Za-z0-9\\-._~+/]{16})[A-Za-z0-9\\-._~+/]+=*/gi;\n/** A Google OAuth access token (`ya29.`), and a JWT: a signed assertion, an ID token, a self-signed account token. */\nconst TOKEN_TEXT =\n /ya29\\.(?=[A-Za-z0-9_.-]{16})[A-Za-z0-9_.-]+|(?<![A-Za-z0-9_-])eyJ(?=[A-Za-z0-9_-]{8})[A-Za-z0-9_-]+\\.eyJ(?=[A-Za-z0-9_-]{8})[A-Za-z0-9_-]+\\.(?=[A-Za-z0-9_-]{16})[A-Za-z0-9_-]+/g;\n/** A signed URL's `Signature=`, also percent-encoded. Its value is read by {@link redactSignatures}, not by a pattern. */\nconst SIGNATURE_NAME = /Signature(?:=|%3D)/gi;\n/** A run of the characters a base64 or hex signature is written in, `%` aside. */\nconst SIGNATURE_RUN = /[A-Za-z0-9+/=_.-]*/y;\n/** The shortest value taken for a signature. */\nconst SIGNATURE_MIN = 16;\n/** The lines that open and close a PEM private key, read where `indexOf` finds a `-----BEGIN ` or a `-----END`. */\nconst PEM_OPEN = /-----BEGIN [A-Z0-9 ]{0,40}PRIVATE KEY-----/y;\nconst PEM_CLOSE = /-----END [A-Z0-9 ]{0,40}PRIVATE KEY-----/y;\n/** An XML element; its text is redacted when its name is a credential's (`<CanonicalRequest>`, `<x-goog-encryption-key>`). */\nconst ELEMENT = /(<([A-Za-z][\\w.:-]{0,63})>)([^<]*)(<\\/\\2>)/g;\n/**\n * An element whose text may be a credential's name, and the text of the one after it, with up to four tags around\n * either: a table's `<th>` and `<td>`, a list's `<dt>` and `<dd>`, `<b>name:</b> <span>value`.\n */\nconst CELL =\n /(<([A-Za-z][\\w-]{0,15})\\b[^<>]{0,256}>\\s{0,32}([^<>\\s](?:[^<>]{0,62}[^<>\\s])?)\\s{0,32}<\\/\\2\\s{0,32}>(?:\\s{0,32}<\\/[A-Za-z][\\w-]{0,15}\\s{0,32}>){0,4}(?:\\s{0,32}<[A-Za-z][\\w-]{0,15}\\b[^<>]{0,256}>){1,4})([^<]*)/g;\nconst REDACTED = '[redacted]';\n/** How deep into errors and plain data the copy goes; anything below is dropped. */\nconst MAX_DEPTH = 10;\n\nconst isPlain = (value: object): boolean => {\n const proto: unknown = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n};\n\nconst isError = (value: object): boolean => value instanceof Error || types.isNativeError(value);\n\n/**\n * Whether `text` is a credential's name, as a key, a list item or a map key holds it. The whole name must match, in any\n * case and with `-` or `_` between its words or neither (`accessToken`, `access_token`): `pageToken` does not.\n */\nconst isCredentialName = (text: string): boolean =>\n text.length <= LONGEST_NAME && CREDENTIAL_NAME.has(fold(text));\n\n/**\n * `text` with every credential it shows replaced. Each step runs in time linear in the text, and none needs the\n * regular expression engine's backtracking stack to grow with it: a pattern that reads an unbounded run reads it with\n * one character class, and a value whose end a pattern would need alternation to find (a PEM key's body, a percent-\n * encoded signature) is read by a loop here instead. The `catch` is a fail-closed guard for what no test input reaches\n * (an engine limit, a result longer than a string can be): the text is then replaced whole, so a hostile body neither\n * leaks nor stops the error being thrown.\n */\nfunction redactText(text: string): string {\n try {\n const named = redactPrivateKeys(text)\n .replace(CREDENTIAL_LINES, `$1: ${REDACTED}`)\n .replace(AUTHORIZATION_VALUE, `$1${REDACTED}`)\n .replace(COOKIE_VALUE, `$1${REDACTED}`)\n .replace(NAMED_SECRET, `$1${REDACTED}`)\n .replace(BEARER, `$1$2${REDACTED}`)\n .replace(TOKEN_TEXT, REDACTED);\n return redactCells(\n redactSignatures(named).replace(\n ELEMENT,\n (whole, open: string, tag: string, inner: string, close: string) =>\n isCredentialName(tag) && inner.trim() !== '' ? `${open}${REDACTED}${close}` : whole,\n ),\n );\n } catch {\n return REDACTED;\n }\n}\n\n/**\n * `text` with each PEM private key's body redacted, its line breaks escaped or not, up to the next `-----END` or the\n * end of the text; the lines that open and close it are kept. The armour is found with `indexOf`.\n */\nfunction redactPrivateKeys(text: string): string {\n let out = '';\n let from = 0;\n for (\n let at = text.indexOf('-----BEGIN ');\n at !== -1;\n at = text.indexOf('-----BEGIN ', Math.max(at + 1, from))\n ) {\n PEM_OPEN.lastIndex = at;\n const open = PEM_OPEN.exec(text);\n if (open === null) continue;\n const body = at + open[0].length;\n const end = text.indexOf('-----END', body);\n let close = '';\n if (end !== -1) {\n PEM_CLOSE.lastIndex = end;\n close = PEM_CLOSE.exec(text)?.[0] ?? '';\n }\n out += `${text.slice(from, body)}${REDACTED}${close}`;\n from = end === -1 ? text.length : end + close.length;\n }\n return from === 0 ? text : out + text.slice(from);\n}\n\n/**\n * `text` with the value after each `Signature=` redacted when it is {@link SIGNATURE_MIN} characters or more: base64 or\n * hex, percent-encoded or not, up to an encoded `&` (`%26`) or a character no signature is written in.\n */\nfunction redactSignatures(text: string): string {\n let out = '';\n let from = 0;\n SIGNATURE_NAME.lastIndex = 0;\n for (let m = SIGNATURE_NAME.exec(text); m !== null; m = SIGNATURE_NAME.exec(text)) {\n const start = m.index + m[0].length;\n let end = start;\n for (;;) {\n SIGNATURE_RUN.lastIndex = end;\n SIGNATURE_RUN.exec(text);\n end = SIGNATURE_RUN.lastIndex;\n if (text[end] !== '%' || text.startsWith('%26', end)) break;\n end += 1;\n }\n if (end - start >= SIGNATURE_MIN) {\n out += `${text.slice(from, start)}${REDACTED}`;\n from = end;\n }\n SIGNATURE_NAME.lastIndex = end;\n }\n return from === 0 ? text : out + text.slice(from);\n}\n\n/**\n * `text` with the text of each element after one that names a credential redacted. A match whose first element names\n * none is not taken past that element: the element after it is tried as the next name, so a heading or a caption\n * before a table does not put every row out of step.\n */\nfunction redactCells(text: string): string {\n let out = '';\n let from = 0;\n CELL.lastIndex = 0;\n for (let m = CELL.exec(text); m !== null; m = CELL.exec(text)) {\n const [whole, head, , name, value] = m as unknown as [string, string, string, string, string];\n if (isCredentialName(name) && value.trim() !== '') {\n out += `${text.slice(from, m.index)}${head}${REDACTED}`;\n from = m.index + whole.length;\n } else {\n CELL.lastIndex = m.index + 1;\n }\n }\n return from === 0 ? text : out + text.slice(from);\n}\n\n/** The copies made so far, and whether any differs from its original. */\ninterface Copying {\n copies: WeakMap<object, unknown>;\n changed: boolean;\n}\n\n/** Marker for a value the copy leaves out. */\nconst OMIT = Symbol('omit');\n\n/** An array index as an own key spells it. */\nconst INDEX = /^(?:0|[1-9]\\d*)$/;\nconst isIndex = (key: string | symbol): key is string =>\n typeof key === 'string' && INDEX.test(key) && Number(key) < 2 ** 32 - 1;\n\n/**\n * Whether `value` has an own property its copy leaves out: any key but an index and `length` on an array, any key at\n * all on a map or a set.\n */\nfunction hasNamedKeys(value: object): boolean {\n if (!Array.isArray(value)) return Reflect.ownKeys(value).length > 0;\n return Reflect.ownKeys(value).some((key) => key !== 'length' && !isIndex(key));\n}\n\n/** Define `key` on `copy` as a plain data property. */\nfunction define(copy: object, key: string | symbol, value: unknown, enumerable: boolean): void {\n Object.defineProperty(copy, key, { value, writable: true, configurable: true, enumerable });\n}\n\n/** A credential-free copy of `value`: plain data and errors only, `OMIT` for anything else. */\nfunction copyOf(value: unknown, ctx: Copying, depth: number): unknown {\n if (typeof value === 'string') {\n const clean = redactText(value);\n if (clean !== value) ctx.changed = true;\n return clean;\n }\n if (typeof value === 'function' || typeof value === 'symbol') {\n ctx.changed = true;\n return OMIT;\n }\n if (typeof value !== 'object' || value === null) return value;\n if (ctx.copies.has(value)) return ctx.copies.get(value);\n if (depth > MAX_DEPTH) {\n ctx.changed = true;\n return OMIT;\n }\n if (Array.isArray(value)) return copyList(value as unknown[], ctx, depth);\n if (value instanceof Map) return copyMap(value as Map<unknown, unknown>, ctx, depth);\n if (value instanceof Set) return copySet(value as Set<unknown>, ctx, depth);\n return copyRecord(value, ctx, depth);\n}\n\n/**\n * A copy of an array, as sparse as the original: only its own indexes are read, so a hole costs nothing. A named\n * property is left out.\n */\nfunction copyList(value: unknown[], ctx: Copying, depth: number): unknown[] {\n const list: unknown[] = new Array<unknown>(value.length);\n ctx.copies.set(value, list);\n if (hasNamedKeys(value)) ctx.changed = true;\n let hidden = false;\n let last = -1;\n for (const key of Reflect.ownKeys(value)) {\n if (!isIndex(key)) continue;\n const index = Number(key);\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n if (descriptor === undefined || !('value' in descriptor)) {\n ctx.changed = true;\n hidden = false;\n continue;\n }\n const item: unknown = descriptor.value;\n // A flat list of header names and values: the value right after a credential name is one.\n if (hidden && index === last + 1 && item != null) {\n list[index] = REDACTED;\n if (item !== REDACTED) ctx.changed = true;\n } else {\n const kept = copyOf(item, ctx, depth + 1);\n if (kept === OMIT) delete list[index];\n else list[index] = kept;\n }\n hidden = typeof item === 'string' && isCredentialName(item);\n last = index;\n }\n return list;\n}\n\n/** A copy of a map: the value under a credential name is redacted. A named property is left out. */\nfunction copyMap(value: Map<unknown, unknown>, ctx: Copying, depth: number): Map<unknown, unknown> {\n const map = new Map<unknown, unknown>();\n ctx.copies.set(value, map);\n if (hasNamedKeys(value)) ctx.changed = true;\n for (const [k, v] of value) {\n const key = copyOf(k, ctx, depth + 1);\n if (key === OMIT) continue;\n if (typeof k === 'string' && isCredentialName(k) && v != null) {\n map.set(key, REDACTED);\n if (v !== REDACTED) ctx.changed = true;\n continue;\n }\n const kept = copyOf(v, ctx, depth + 1);\n if (kept !== OMIT) map.set(key, kept);\n }\n return map;\n}\n\n/** A copy of a set. A named property is left out. */\nfunction copySet(value: Set<unknown>, ctx: Copying, depth: number): Set<unknown> {\n const set = new Set<unknown>();\n ctx.copies.set(value, set);\n if (hasNamedKeys(value)) ctx.changed = true;\n for (const v of value) {\n const kept = copyOf(v, ctx, depth + 1);\n if (kept !== OMIT) set.add(kept);\n }\n return set;\n}\n\n/** A copy of an error or a plain object, `OMIT` for any other object (a class instance: a stream, a socket). */\nfunction copyRecord(value: object, ctx: Copying, depth: number): unknown {\n const errorLike = isError(value);\n if (!errorLike && !isPlain(value)) {\n ctx.changed = true;\n return OMIT;\n }\n const copy: object = errorLike\n ? (Object.setPrototypeOf(new Error(), Object.getPrototypeOf(value) as object | null) as object)\n : (Object.create(Object.getPrototypeOf(value) as object | null) as object);\n ctx.copies.set(value, copy);\n if (errorLike) delete (copy as { stack?: unknown }).stack;\n for (const key of Reflect.ownKeys(value)) {\n if (typeof key === 'string' && TRANSPORT.has(key)) {\n ctx.changed = true;\n } else if (errorLike && key === 'stack') {\n copyStack(value, copy, ctx, depth);\n } else {\n copyField(value, copy, key, ctx, depth);\n }\n }\n if (errorLike) keepSlotFields(value, copy, ctx, depth);\n redactPairs(value, copy, errorLike, ctx);\n return copy;\n}\n\n/**\n * The fields a Google error item describes an error with (`{ message, domain, reason, location, locationType }`). They\n * are not a name and a value: a `401` names the header it refused in `location` (`Authorization`) beside the reason and\n * message classification reads. They are never taken as a credential's name, and never redacted beside one; the text\n * rules still apply to them.\n */\nconst DESCRIBING = new Set([\n 'message',\n 'domain',\n 'reason',\n 'location',\n 'locationType',\n 'extendedHelp',\n]);\n\n/**\n * Redact the value a record holds beside a credential's name: a `{ name, value }` pair, a table row parsed to\n * `{ th, td }`. On a plain record every field but the name is redacted. On an error, whose other fields are what\n * classification reads, only the `...Value` field beside a `...Name` one is. A number or a boolean is never a\n * credential, and is kept.\n */\nfunction redactPairs(original: object, copy: object, errorLike: boolean, ctx: Copying): void {\n const names = Reflect.ownKeys(original).filter((key): key is string => {\n if (typeof key !== 'string' || DESCRIBING.has(key)) return false;\n const held = Object.getOwnPropertyDescriptor(original, key)?.value as unknown;\n return typeof held === 'string' && isCredentialName(held);\n });\n if (names.length === 0) return;\n const values = errorLike\n ? names\n .filter((key) => /name$/i.test(key))\n .map((key) => `${key.slice(0, -4)}value`.toLowerCase())\n : undefined;\n for (const key of Reflect.ownKeys(copy)) {\n if (typeof key !== 'string' || names.includes(key) || DESCRIBING.has(key)) continue;\n if (values !== undefined && !values.includes(key.toLowerCase())) continue;\n const descriptor = Object.getOwnPropertyDescriptor(copy, key);\n if (descriptor?.value == null || descriptor.value === REDACTED) continue;\n // What classification reads of a record is a number or a boolean (a response's `status`), never a credential.\n if (typeof descriptor.value !== 'string' && typeof descriptor.value !== 'object') continue;\n define(copy, key, REDACTED, descriptor.enumerable ?? false);\n ctx.changed = true;\n }\n}\n\n/** Whether `value` is a response as a copy keeps it: a plain record of the response's numbers and text alone. */\nfunction isSummary(value: object): boolean {\n return (\n isPlain(value) &&\n Reflect.ownKeys(value).every((key) => {\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n const held: unknown = descriptor?.value;\n return (\n typeof key === 'string' &&\n RESPONSE_FIELDS.includes(key) &&\n (typeof held === 'number' || typeof held === 'string')\n );\n })\n );\n}\n\n/**\n * The part of an HTTP response worth keeping: what the server said, as numbers and text. Only those are read (a getter\n * such as a fetch `Response`'s `status` included), and the live response is never kept.\n */\nfunction copyResponse(response: object, ctx: Copying, depth: number): unknown {\n // A copy's response is already this, and is copied as the plain data it is.\n if (isSummary(response)) return copyOf(response, ctx, depth);\n ctx.changed = true;\n const summary: Record<string, unknown> = {};\n for (const key of RESPONSE_FIELDS) {\n let held: unknown;\n try {\n held = (response as Record<string, unknown>)[key];\n } catch {\n continue;\n }\n if (typeof held === 'number' || typeof held === 'string') {\n summary[key] = copyOf(held, ctx, depth + 1);\n }\n }\n return summary;\n}\n\n/** Carry an error's stack over, read through the original: it may be an accessor bound to the error itself. */\nfunction copyStack(original: object, copy: object, ctx: Copying, depth: number): void {\n let stack: unknown;\n try {\n stack = (original as { stack?: unknown }).stack;\n } catch {\n return;\n }\n const kept = copyOf(stack, ctx, depth + 1);\n if (kept !== OMIT) define(copy, 'stack', kept, false);\n}\n\n/**\n * Carry one own property over, as a data property: redacted under a credential name, reduced to its status when it is\n * a `response`, left out when it is not data.\n */\nfunction copyField(\n original: object,\n copy: object,\n key: string | symbol,\n ctx: Copying,\n depth: number,\n): void {\n const descriptor = Object.getOwnPropertyDescriptor(original, key);\n if (descriptor === undefined) return;\n // A function or accessor is a closure over the original (a `toJSON`, an inspect hook): it is not carried over.\n if (!('value' in descriptor)) {\n ctx.changed = true;\n return;\n }\n // A key is text too: one that quotes a credential is carried redacted.\n const name = typeof key === 'string' ? redactText(key) : key;\n if (name !== key) ctx.changed = true;\n const held: unknown = descriptor.value;\n let kept: unknown;\n if (typeof key === 'string' && isCredentialName(key) && held != null) {\n kept = REDACTED;\n if (held !== REDACTED) ctx.changed = true;\n } else if (key === 'response' && typeof held === 'object' && held !== null) {\n kept = copyResponse(held, ctx, depth + 1);\n } else {\n kept = copyOf(held, ctx, depth + 1);\n if (kept === OMIT) return;\n }\n Object.defineProperty(copy, name, {\n value: kept,\n writable: descriptor.writable ?? true,\n configurable: true,\n enumerable: descriptor.enumerable ?? false,\n });\n}\n\n/**\n * Carry over a `name`, `message` or `code` the original reads through its prototype and the copy cannot: a\n * `DOMException`'s are getters over the instance's internal state, which throw on any other object.\n */\nfunction keepSlotFields(original: object, copy: object, ctx: Copying, depth: number): void {\n for (const key of ['name', 'message', 'code'] as const) {\n if (Object.prototype.hasOwnProperty.call(original, key)) continue;\n let held: unknown;\n try {\n held = (original as Record<string, unknown>)[key];\n } catch {\n continue;\n }\n let read: unknown = OMIT;\n try {\n read = (copy as Record<string, unknown>)[key];\n } catch {\n // the copy cannot read it\n }\n if (read === held) continue;\n const kept = copyOf(held, ctx, depth + 1);\n if (kept !== OMIT) define(copy, key, kept, false);\n }\n}\n\n/**\n * `err` as it is safe to throw: a credential-free deep copy of an error or plain object when it holds a transport or\n * shows a credential, the value itself when it holds neither, and any other value (a class instance that is not an\n * error, a primitive) as it is. The live error is never changed.\n */\nexport function scrubCredentials<T>(err: T): T {\n if (typeof err !== 'object' || err === null) return err;\n const ctx: Copying = { copies: new WeakMap(), changed: false };\n const copy = copyOf(err, ctx, 0);\n return (ctx.changed && copy !== OMIT ? copy : err) as T;\n}\n", "/**\n * The GCS lifecycle check behind `GcsStorage.checkLifecycle`: reads the bucket's metadata (one request) and says whether\n * a lifecycle rule would delete what the store keeps.\n *\n * Everything the store keeps sits under one object-name prefix, and every name ends `.crbm` (a generation) or `.reg`\n * (a registry row). A rule is a **problem** when its action is `Delete` (or one this check does not know, or none), it applies\n * to live objects (not `isLive: false` and not one of the conditions that only noncurrent versions meet:\n * `numNewerVersions` of 1 or more, which a live object never meets because none is newer than it, and\n * `daysSinceNoncurrentTime` and `noncurrentTimeBefore` at any value, because a live object has no noncurrent time), and its `matchesPrefix` and `matchesSuffix`, when it has them, can match a name of ours: a\n * prefix and ours are one a prefix of the other, and a suffix and `.crbm` or `.reg` are one a suffix of the other. Any\n * other condition (age, creation date, storage class, a name pattern) is not interpreted and is judged as if it matched,\n * so a rule it would not match can be reported; one that does is never hidden. `SetStorageClass` (even to ARCHIVE, which\n * stays readable) and `AbortIncompleteMultipartUpload` are safe.\n */\nimport type { Storage } from '@google-cloud/storage';\nimport type { LifecycleVerdict } from '@cloudbitmaps/core/driver-kit';\nimport { ReadTimedOut, startDeadline, withDeadline } from './read-timeout';\nimport { scrubCredentials } from './scrub-error';\n\n/** The longest the message of a failed request is shown. */\nconst MAX_MESSAGE = 200;\n\n/** The longest an error name, a prefix or a rule's condition is shown. */\nconst MAX_FIELD = 64;\nconst MAX_CONDITION = 120;\n\n/** How many problem rules a `detail` names before it says \"and N more\". */\nexport const MAX_NAMED_RULES = 3;\n\nconst cut = (text: string, max: number): string => (text.length > max ? text.slice(0, max) : text);\n\n/** Actions that leave every object readable. Any other action type is judged like a `Delete`. */\nconst SAFE_ACTIONS: ReadonlySet<string> = new Set([\n 'SetStorageClass',\n 'AbortIncompleteMultipartUpload',\n]);\n\n/** The suffixes of every name the store writes. */\nconst NAME_SUFFIXES = ['.crbm', '.reg'] as const;\n\n/** Two strings share a prefix relation (one starts with the other). */\nconst prefixRelated = (a: string, b: string): boolean => a.startsWith(b) || b.startsWith(a);\n/** Two strings share a suffix relation (one ends with the other). */\nconst suffixRelated = (a: string, b: string): boolean => a.endsWith(b) || b.endsWith(a);\n\nconst strings = (value: unknown): string[] =>\n Array.isArray(value) ? value.filter((v): v is string => typeof v === 'string') : [];\n\nconst record = (value: unknown): Record<string, unknown> =>\n typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : {};\n\n/** Conditions only a noncurrent version can meet, at any value: a live object has no noncurrent time. */\nconst NONCURRENT_ONLY = ['daysSinceNoncurrentTime', 'noncurrentTimeBefore'];\n\n/**\n * Whether a live object can meet the condition `numNewerVersions`: it holds when at least that many newer versions\n * exist, and a live object has none, so only a 0 matches it (a value that is not a number is judged as met).\n */\nconst metByLiveObject = (value: unknown): boolean => typeof value !== 'number' || value < 1;\n\n/** The action type of a rule, or `undefined` when it names none. */\nconst actionOf = (rule: unknown): string | undefined => {\n const type = record(record(rule).action).type;\n return typeof type === 'string' && type !== '' ? type : undefined;\n};\n\n/** Whether `rule` is an object, the only shape a rule can have; anything else is no rule and is skipped. */\nconst isRule = (rule: unknown): boolean => typeof rule === 'object' && rule !== null;\n\n/** Whether `rule` has an action that deletes, is not known to be safe or names none, and applies to live objects whose name can be one of ours, under `prefix`. */\nfunction deletesOurs(rule: unknown, prefix: string): boolean {\n if (!isRule(rule)) return false;\n const type = actionOf(rule);\n if (type !== undefined && SAFE_ACTIONS.has(type)) return false;\n const when = record(record(rule).condition);\n if (when.isLive === false) return false;\n if (NONCURRENT_ONLY.some((key) => when[key] !== undefined)) return false;\n if (!metByLiveObject(when.numNewerVersions)) return false;\n const prefixes = strings(when.matchesPrefix);\n if (prefixes.length > 0 && !prefixes.some((p) => prefixRelated(p, prefix))) return false;\n const suffixes = strings(when.matchesSuffix);\n return (\n suffixes.length === 0 ||\n suffixes.some((s) => NAME_SUFFIXES.some((ours) => suffixRelated(s, ours)))\n );\n}\n\n/**\n * Whether narrowing the rule's `matchesPrefix` cannot exclude the store's prefix: the rule names prefixes, and none is\n * a shorter prefix of the store's, so each is the store's own or sits inside it.\n */\nfunction sitsInside(rule: unknown, prefix: string): boolean {\n const prefixes = strings(record(record(rule).condition).matchesPrefix);\n return (\n prefix !== '' &&\n prefixes.length > 0 &&\n !prefixes.some((p) => p !== prefix && prefix.startsWith(p))\n );\n}\n\n/**\n * What a bucket's metadata means for a store under `prefix` (`''`, or `trimmed/`). Pure: no request.\n */\nfunction judgeMetadata(metadata: unknown, prefix: string): LifecycleVerdict {\n const rules = record(record(metadata).lifecycle).rule;\n const found = (Array.isArray(rules) ? (rules as unknown[]) : []).filter((r) =>\n deletesOurs(r, prefix),\n );\n if (found.length > 0) {\n const named = found\n .slice(0, MAX_NAMED_RULES)\n .map(\n (r) =>\n `${cut(actionOf(r) ?? 'no action type', MAX_FIELD)} when ${cut(JSON.stringify(record(r).condition ?? {}), MAX_CONDITION)}`,\n )\n .join('; ');\n const more =\n found.length > MAX_NAMED_RULES ? ` and ${found.length - MAX_NAMED_RULES} more` : '';\n const quoted = JSON.stringify(cut(prefix, MAX_FIELD));\n const narrow = `Narrow the rule with matchesPrefix to exclude ${quoted}, or restrict it to noncurrent versions.`;\n const retarget =\n `A rule that sits inside ${quoted} cannot be narrowed to exclude it: remove it or retarget it to another ` +\n 'prefix, or restrict it to noncurrent versions.';\n const where =\n prefix === ''\n ? 'The store has no prefix, so a rule on any prefix can reach it: give the store a ' +\n 'prefix, or scope the rule away from its paths.'\n : [\n found.some((r) => !sitsInside(r, prefix)) ? narrow : '',\n found.some((r) => sitsInside(r, prefix)) ? retarget : '',\n ]\n .filter((x) => x !== '')\n .join(' ');\n return {\n status: 'problem',\n detail:\n `${found.length} bucket lifecycle rule(s) with a Delete action, or one this check does not know, can match objects under ${quoted}: ` +\n `${named}${more}. The store keeps current objects it must be able to read back. ${where}`,\n };\n }\n return {\n status: 'ok',\n detail: `no bucket lifecycle rule deletes live objects under ${JSON.stringify(cut(prefix, MAX_FIELD))}`,\n };\n}\n\n/** A failed request as a sentence: the permission to grant for a refusal, the scrubbed error otherwise. */\nfunction unchecked(err: unknown): LifecycleVerdict {\n if (err instanceof ReadTimedOut) {\n return {\n status: 'unchecked',\n detail:\n 'the read of the bucket metadata got no answer in time (readTimeoutMs); run the check again',\n };\n }\n const scrubbed = scrubCredentials(err);\n const { code, message, name } = record(scrubbed);\n if (code === 403) {\n return {\n status: 'unchecked',\n detail:\n 'the identity may not read the bucket metadata: grant storage.buckets.get on the bucket to check its ' +\n 'lifecycle rules, or confirm by hand that no rule deletes current objects under this prefix',\n };\n }\n const shown = typeof message === 'string' ? `: ${message.slice(0, MAX_MESSAGE)}` : '';\n const label = typeof name === 'string' ? cut(name, MAX_FIELD) : 'Error';\n const status = typeof code === 'number' ? ` ${code}` : '';\n return {\n status: 'unchecked',\n detail: `could not read the bucket metadata (${label}${status}${shown}); check the lifecycle rules by hand`,\n };\n}\n\n/** The check: one request (the SDK may retry it), under `readTimeoutMs` (`0`: none), never a throw. */\nexport async function checkGcsLifecycle(\n client: Storage,\n bucket: string,\n prefix: string,\n readTimeoutMs = 0,\n): Promise<LifecycleVerdict> {\n try {\n const [metadata] = await withDeadline(\n () => client.bucket(bucket).getMetadata(),\n startDeadline(readTimeoutMs, 'bucket metadata read'),\n );\n return judgeMetadata(metadata, prefix);\n } catch (err) {\n try {\n return unchecked(err);\n } catch {\n return { status: 'unchecked', detail: 'could not read the bucket metadata' };\n }\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 *\n * **A delete given `ifVersion` is sent with `ifGenerationMatch: <that object generation>`** when `conditionalDelete` is\n * set, which it is not by default, as for the registry: no run against real GCS has shown the service applies the\n * precondition to a delete, and fake-gcs-server accepts it and ignores it. A tail read reports the object's generation\n * as its version, from the `x-goog-generation` header of the GET that carried the bytes, or the metadata when none were\n * asked for. A `412` is the precondition failing, and is reported as {@link WriteConflictError} unless the metadata then\n * shows no object under the name: an absent object is a no-op, whether the service answers it `404` or `412`. The\n * delete keeps the SDK's retry, which re-sends a request with a precondition: a copy that meets its own landed delete\n * finds nothing, and one that meets an object stored since is refused.\n */\nimport {\n errorKind,\n NotFoundError,\n TransientError,\n ValidationError,\n WriteConflictError,\n concatBytes,\n normalizeObjectPrefix,\n parseGenerationFromKey,\n checkDeleteOptions,\n requireBool,\n requirePositiveInt,\n segmentObjectPrefix,\n storageObjectKey,\n toTransient,\n} from '@cloudbitmaps/core/driver-kit';\nimport type {\n BlobSink,\n GenKey,\n IStorageDriver,\n SegmentRef,\n StorageCaps,\n StorageDeleteOptions,\n TailRead,\n} from '@cloudbitmaps/core/driver-kit';\nimport type { Clock, Rng } from '@cloudbitmaps/core';\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 { BucketPresence } from './bucket-presence';\nimport {\n asMissingBucket,\n missingBucketError,\n isInvalidRange,\n isNotFound,\n isPreconditionFailed,\n isThrottle,\n isTransient,\n transientFaultMessage,\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/** GCS's hard limit on one object (5 TiB), and the default ceiling. Set lower to fail fast on a runaway write. */\nconst GCS_MAX_OBJECT_BYTES = 5 * 1024 ** 4;\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. */\ntype Sleeper = Pick<Clock, 'sleep'>;\n\nconst REAL_TIME: Sleeper = {\n sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),\n};\n/** Where the backoff's jitter comes from when none is injected. */\nconst REAL_RNG: Rng = { next: () => Math.random() };\n\n/** A refusal this driver already typed: it passes on unmapped, where a raw SDK error is mapped. */\nfunction isTypedPassThrough(err: unknown): boolean {\n const kind = errorKind(err);\n return kind === 'ValidationError' || kind === 'WriteConflictError' || kind === 'NotFoundError';\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\ninterface 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 /** Where the jitter of that backoff is drawn from; `Math.random` when absent. */\n readonly rng?: Rng;\n /**\n * Whether a delete given `ifVersion` is sent with `ifGenerationMatch`, so it removes the object only while it is the\n * one that generation names. Defaults to `false`, on the public endpoint too, as the registry's option does: whether\n * real GCS applies `ifGenerationMatch` to a delete has not been verified by a run against the service, and\n * fake-gcs-server accepts the precondition and ignores it. Set `true` once you have. Until it is set, a delete\n * decided from one read of an object removes whatever is under the name by then: on GCS the window in which an\n * erasure's stalled delete takes a generation a load put under the number since stays open.\n */\n readonly conditionalDelete?: boolean;\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 private readonly rng: Rng;\n private readonly presence: BucketPresence;\n private readonly conditionalDelete: boolean;\n\n constructor(options: GcsStorageDriverOptions) {\n this.storage = options.storage;\n this.conditionalDelete = requireBool('conditionalDelete', options.conditionalDelete) ?? false;\n this.clock = options.clock ?? REAL_TIME;\n this.rng = options.rng ?? REAL_RNG;\n this.readStorage = options.readStorage ?? options.storage;\n this.bucket = options.bucket;\n this.prefix = normalizeObjectPrefix(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 requirePositiveInt('maxObjectBytes', options.maxObjectBytes);\n requirePositiveInt('simpleUploadThresholdBytes', options.simpleUploadThresholdBytes);\n // GCS's own limit on one object: a cap past it is advertised and never reachable.\n if (options.maxObjectBytes !== undefined && options.maxObjectBytes > GCS_MAX_OBJECT_BYTES) {\n throw new ValidationError(\n `maxObjectBytes must be at most GCS's 5 TiB object limit (${GCS_MAX_OBJECT_BYTES}); got ${options.maxObjectBytes}`,\n );\n }\n this.maxObjectBytes = options.maxObjectBytes ?? GCS_MAX_OBJECT_BYTES;\n this.threshold = options.simpleUploadThresholdBytes ?? DEFAULT_UPLOAD_THRESHOLD_BYTES;\n this.readTimeoutMs = resolveReadTimeoutMs(options.readTimeoutMs);\n this.presence = new BucketPresence(this.storage, this.bucket);\n }\n\n capabilities(): StorageCaps {\n return {\n rangeRead: true,\n maxObjectBytes: this.maxObjectBytes,\n conditionalPut: true,\n conditionalDelete: this.conditionalDelete,\n };\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 = storageObjectKey(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 this.rng,\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 (isTypedPassThrough(err)) throw err;\n // An upload's 404 can be the bucket's: settle it before it is passed on.\n if (isNotFound(err)) await this.presence.confirm();\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 = storageObjectKey(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 await this.readFailure(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<TailRead> {\n const objectName = storageObjectKey(this.prefix, key);\n // The rule every driver's tail read holds to: a safe integer, and zero or less an empty tail.\n if (!Number.isSafeInteger(maxBytes)) {\n throw new ValidationError(`invalid tail length ${String(maxBytes)}`);\n }\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 const meta = await this.metadataOf(objectName, deadline);\n return withVersion({ bytes: new Uint8Array(0), size: meta.size }, meta.version);\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 meta = await this.metadataOf(objectName, deadline);\n if (meta.size !== 0) throw err;\n return withVersion({ bytes: new Uint8Array(0), size: 0 }, meta.version);\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 meta = await this.metadataOf(objectName, deadline);\n if (meta.size !== 0) {\n throw this.badTail(key, `no bytes returned for an object of ${meta.size}B`);\n }\n return withVersion({ bytes: res.bytes, size: 0 }, meta.version);\n }\n // The version of the object the bytes came from: the generation the GET that carried them names.\n const size = this.tailSize(res, maxBytes, key);\n return withVersion(\n { bytes: res.bytes, size },\n objectGeneration(singleHeader(res.headers, 'x-goog-generation')),\n );\n } catch (err) {\n throw await this.readFailure(err, key);\n }\n }\n\n /**\n * The object's size from its metadata, validated, and its generation when the metadata names a usable one. Read on the\n * download client and retried as a download is, within the calling read's `deadline` ({@link withDeadline}), so\n * nothing but the one request in flight runs past it.\n */\n private async metadataOf(\n objectName: string,\n deadline: Deadline | undefined,\n ): Promise<{ size: number; version: string | undefined }> {\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 {\n size,\n version: objectGeneration(\n meta.generation === undefined ? undefined : String(meta.generation),\n ),\n };\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, options?: StorageDeleteOptions): Promise<void> {\n const objectName = storageObjectKey(this.prefix, key);\n const ifVersion = checkDeleteOptions(options);\n const conditional = ifVersion !== undefined && this.conditionalDelete;\n // Idempotent: a racing/retried GC sweep of an absent object is a no-op. Not the SDK's `ignoreNotFound`, which takes\n // every 404 and so would report a delete in a missing bucket as done.\n try {\n await this.file(objectName).delete(\n ...(conditional ? [{ ifGenerationMatch: generationOf(ifVersion, key) }] : []),\n );\n } catch (err) {\n if (isNotFound(err)) return this.presence.confirm();\n // A failed precondition is a conflict only while an object is under the name: a service may answer it for a name\n // with nothing there, a copy of this delete that landed first included.\n if (conditional && isPreconditionFailed(err)) {\n if (!(await this.exists(objectName))) return;\n throw new WriteConflictError(\n `generation ${key.segment}.${key.generation} is another object than the version given; not deleted`,\n );\n }\n throw this.mapError(err);\n }\n }\n\n /** Whether an object is under the name, from its metadata. A read that fails in any other way throws. */\n private async exists(objectName: string): Promise<boolean> {\n try {\n await this.downloadable(objectName).getMetadata();\n return true;\n } catch (err) {\n if (isNotFound(err)) {\n await this.presence.confirm();\n return false;\n }\n throw this.mapError(scrubCredentials(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 // A listing names no object, so its 404 can only be the bucket's.\n if (isNotFound(err)) throw missingBucketError(this.bucket, scrubCredentials(err));\n throw this.mapError(err);\n }\n for (const f of files) {\n const generation = parseGenerationFromKey(prefix, f.name);\n if (generation !== null) {\n yield { namespace: ref.namespace, segment: ref.segment, generation };\n }\n }\n }\n\n /** A read's error, once a 404 is known to be the object's and not the bucket's ({@link BucketPresence}). */\n private async readFailure(err: unknown, key: GenKey): Promise<unknown> {\n if (isNotFound(err)) await this.presence.confirm();\n return this.mapReadError(err, key);\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 (errorKind(err) === 'ValidationError') 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, is transient whichever code Node gave it:\n // `isTransient` takes every transport fault.\n return this.mapError(err);\n }\n\n /**\n * Reclassify a transient GCS fault (throttle/5xx/dropped connection) as a retryable {@link TransientError}, so the\n * store's read retry can ride it out and a write's caller can tell it from a deterministic failure, and a missing\n * bucket as a `ValidationError` that names it; everything else propagates unchanged. The final fallback at every\n * client-call site: an HTTP status and a fault in transit arrive typed, and anything else (a checksum mismatch, a\n * credentials failure) as the SDK raised it.\n */\n private mapError(err: unknown): unknown {\n const scrubbed = asMissingBucket(scrubCredentials(err), this.bucket);\n return toTransient(scrubbed, isTransient, transientFaultMessage);\n }\n}\n\ntype GcsFile = ReturnType<ReturnType<Storage['bucket']>['file']>;\n\n/** A tail read, with the version it was read at when there is one. */\nfunction withVersion(\n tail: { bytes: Uint8Array; size: number },\n version: string | undefined,\n): TailRead {\n return version === undefined ? tail : { ...tail, version };\n}\n\n/**\n * An object generation as GCS spells it, a positive decimal integer, or `undefined` for anything else: the version a\n * delete is conditioned on goes back to the service as a number, so one that does not parse as one is never reported.\n */\nfunction objectGeneration(value: string | undefined): string | undefined {\n return value !== undefined && /^[1-9]\\d*$/.test(value) && Number.isSafeInteger(Number(value))\n ? value\n : undefined;\n}\n\n/** The generation `ifGenerationMatch` takes, from a version this driver reported; any other is refused. */\nfunction generationOf(version: string, key: GenKey): number {\n if (objectGeneration(version) === undefined) {\n throw new ValidationError(\n `ifVersion for ${key.segment}.${key.generation} is not a GCS object generation: ${version}`,\n );\n }\n return Number(version);\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 /** The SDK's resumable upload under {@link stream}, which {@link abort} ends with an error: see there. */\n private upload: Writable | undefined;\n /** Set by {@link abort}: the held upload starts no request from then on. */\n private abandoned = false;\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 private readonly rng: Rng,\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 // A copy, here and below: the caller may reuse its buffer once this write resolves, and the stream or the\n // buffered upload still holds what it was given.\n if (!this.stream.write(Buffer.from(bytes))) await once(this.stream, 'drain');\n return;\n }\n this.buffered.push(new Uint8Array(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 this.holdUpload();\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);\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));\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(this.rng.next() * 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 /**\n * Keep the SDK's resumable upload, the writable its write stream pipes into, so {@link abort} can end its request.\n * The SDK hands it to the stream's duplex through `setWritable` in `startResumableUpload_`, a method of the `File`\n * its types declare; this wraps that method on this sink's own `File`, which no other write shares. Where the method\n * or the duplex's `setWritable` is missing, the SDK runs unchanged, nothing is kept, and an abort destroys the\n * stream alone, as it did before.\n */\n private holdUpload(): void {\n type Duplex = { setWritable?: (writable: Writable) => void };\n const file = this.file as unknown as {\n startResumableUpload_?: (dup: Duplex, options?: unknown) => void;\n };\n const start = file.startResumableUpload_;\n if (typeof start !== 'function') return;\n file.startResumableUpload_ = (dup, options) => {\n const setWritable = dup.setWritable;\n if (typeof setWritable === 'function') {\n dup.setWritable = (writable) => {\n this.hold(writable);\n setWritable.call(dup, writable);\n };\n }\n start.call(file, dup, options);\n };\n }\n\n /**\n * Keep `upload`, and let it start no request once this sink is abandoned. The SDK registers the listener that\n * aborts an upload's request only as that request starts, after the session is created, so an upload abandoned\n * before then would start its request anyway, with nothing left to end it. Each request that sends the upload's\n * bytes, its first and every retry, begins in `startUploading`, a public method of the SDK's `Upload`. On an\n * abandoned upload it resolves at once: the upload is destroyed already, and some of the SDK's callers do not\n * catch a rejection.\n */\n private hold(upload: Writable): void {\n this.upload = upload;\n const held = upload as Writable & { startUploading?: () => Promise<void> };\n const startUploading = held.startUploading;\n if (typeof startUploading !== 'function') return;\n held.startUploading = () => (this.abandoned ? Promise.resolve() : startUploading.call(held));\n }\n\n /**\n * End an upload that will not be committed. Destroying the write stream alone leaves the resumable upload's request\n * open: the SDK destroys its upload with no error, aborts the request only when the upload errors, and its request\n * body waits for bytes that never come, so the socket stays open and the process alive until the service drops the\n * connection. So the upload is destroyed with an error first, which aborts its request; the body was never ended, so\n * no object is finalized. An upload whose request has not started yet starts none: see {@link hold}. The error is\n * the sink's own and reaches no caller.\n */\n async abort(): Promise<void> {\n this.abandoned = true;\n const abandoned = new Error('the upload was abandoned before its object was committed');\n if (this.upload !== undefined && !this.upload.destroyed) this.upload.destroy(abandoned);\n if (this.stream !== undefined && !this.stream.destroyed) this.stream.destroy(abandoned);\n }\n}\n", "/**\n * Whether the bucket a driver names exists, asked when a request first answers 404.\n *\n * GCS answers a missing bucket and a missing object with the same status and reason (`404 notFound`), and a\n * GCS-compatible server may not even word them apart. Read as a missing object, a misnamed or deleted bucket would\n * answer every read as an empty segment. An object listing tells them apart: it names no object, so its 404 can only\n * be the bucket's.\n *\n * ```\n * a request answers 404 \u2500\u2500\u25B6 confirm() \u2500\u2500\u25B6 bucket seen before? \u2500\u2500 yes \u2500\u2500\u25B6 the 404 is an object's\n * \u2502 no\n * \u25BC\n * list one object in the bucket\n * \u251C\u2500 answered \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25B6 remember the bucket; the 404 is an object's\n * \u251C\u2500 404 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25B6 reject: ValidationError, the bucket does not exist\n * \u251C\u2500 transient \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25B6 reject: TransientError (asked again next time)\n * \u251C\u2500 403 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25B6 remember it as unknowable; the 404 is an object's\n * \u2514\u2500 anything else \u2500\u2500\u2500\u2500\u2500\u25B6 the 404 is an object's (asked again next time)\n * ```\n *\n * A process pays one listing at most while its bucket exists, and concurrent 404s share it. An identity without\n * `storage.objects.list` is answered `403`, which says nothing about the bucket, so its 404s read as they always did.\n * Any other failure (a credential that could not be fetched, a 401) says nothing either, but may not last, so it is\n * not remembered. Once seen, the bucket is not asked about again: one deleted later in the process's life reads as an\n * empty store, as it did before this check, while every write to it fails.\n */\n\nimport { TransientError } from '@cloudbitmaps/core/driver-kit';\nimport type { Storage } from '@google-cloud/storage';\nimport { isTransient, missingBucketError } from './gcs-errors';\nimport { scrubCredentials } from './scrub-error';\n\nexport class BucketPresence {\n private settled = false;\n private asking: Promise<void> | undefined;\n\n constructor(\n private readonly storage: Storage,\n private readonly bucket: string,\n ) {}\n\n /** Resolve when a 404 can be read as an object's; reject with `ValidationError` when the bucket does not exist, `TransientError` when the listing failed in transit. */\n async confirm(): Promise<void> {\n if (this.settled) return;\n this.asking ??= this.ask().finally(() => {\n this.asking = undefined;\n });\n return this.asking;\n }\n\n private async ask(): Promise<void> {\n let failure: { raised: unknown } | undefined;\n try {\n await this.storage.bucket(this.bucket).getFiles({ maxResults: 1, autoPaginate: false });\n } catch (raised) {\n failure = { raised };\n }\n if (failure !== undefined) {\n // The cause is the credential-free copy, never the SDK's error as raised.\n const err = scrubCredentials(failure.raised);\n const status = (err as { code?: unknown } | null)?.code;\n if (status === 404) {\n throw missingBucketError(this.bucket, err);\n }\n if (isTransient(err)) {\n throw new TransientError(\n `transient GCS fault confirming the bucket exists: ${String(status)}`,\n {\n cause: err,\n },\n );\n }\n // Refused or otherwise unanswerable: nothing is known about the bucket, so a 404 reads as an object's as it did\n // before this check. Only a refusal (no list permission) is sure to answer the same again, so only it is kept.\n if (status !== 403) return;\n }\n this.settled = true;\n }\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 */\nimport {\n ValidationError,\n isTransportFault as isNodeTransportFault,\n} from '@cloudbitmaps/core/driver-kit';\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), in a bucket that does. */\nexport function isNotFound(err: unknown): boolean {\n return httpStatus(err) === 404 && !isMissingBucket(err);\n}\n\n/**\n * The bucket itself does not exist: a wiring fault, never an absent object. Read as one, a misnamed or deleted bucket\n * would answer every read as an empty segment. GCS gives both the same status and reason (`404 notFound`), so only its\n * message tells them apart: on a download the message is the response body as it came, JSON or text, and elsewhere\n * the message and each `errors[].message` are GCS's own. A wording this does not match reads as an absent object.\n */\nexport function isMissingBucket(err: unknown): boolean {\n if (httpStatus(err) !== 404) return false;\n const e = err as { message?: unknown; errors?: unknown };\n const says = (m: unknown): boolean => typeof m === 'string' && /bucket does not exist/i.test(m);\n if (says(e.message)) return true;\n return (\n Array.isArray(e.errors) &&\n e.errors.some((inner) => says((inner as { message?: unknown } | null)?.message))\n );\n}\n\n/**\n * The {@link ValidationError} a missing bucket is: it names the bucket and what to check, and carries the service's\n * error as its `cause`. A wiring fault is not an absent object, so it is not a `NotFoundError`: a read re-resolves on\n * that, and would treat a misnamed bucket as a miss it can heal.\n */\nexport function missingBucketError(bucket: string, cause: unknown): ValidationError {\n return Object.assign(\n new ValidationError(\n `the GCS bucket does not exist: ${bucket}; check the bucket name and the project the client is built for`,\n ),\n { cause },\n );\n}\n\n/** `err`, or {@link missingBucketError} when `err` says the bucket does not exist. */\nexport function asMissingBucket(err: unknown, bucket: string): unknown {\n return isMissingBucket(err) ? missingBucketError(bucket, err) : err;\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 * A connection that failed or was cut off: one of the codes Node gives every driver's connection faults (refused,\n * reset, aborted, timed out, a DNS failure, an unreachable host or network, a broken pipe), or a chunked body cut off\n * mid-stream (`ERR_STREAM_PREMATURE_CLOSE`), which the GCS download path reports. A file the credentials name that is\n * missing or unreadable (`ENOENT`, `EACCES`) and a TLS failure (`EPROTO`, a certificate error) carry codes of the same\n * shape, and would fail the same way again, so they are not.\n */\nfunction isConnectionFault(err: unknown): boolean {\n return isNodeTransportFault(err) || networkCode(err) === 'ERR_STREAM_PREMATURE_CLOSE';\n}\n\n/** A download that failed in transit rather than with an HTTP status: a connection fault ({@link isConnectionFault}). */\nexport function isTransportFault(err: unknown): boolean {\n return httpStatus(err) === undefined && isConnectionFault(err);\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 // The same connection faults a download is retried on: one list, so a write and a read cannot disagree on one.\n return isConnectionFault(err);\n}\n\n/**\n * The message a transient GCS fault is handed on with: `transient GCS fault: <code>`, the HTTP status the SDK puts on\n * `code` or Node's connection code, else the error's name.\n */\nexport function transientFaultMessage(err: unknown): string {\n const e = err as { code?: unknown; name?: unknown } | null | undefined;\n return `transient GCS fault: ${String(e?.code ?? e?.name ?? 'unknown')}`;\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 WriteConflictError,\n normalizeObjectPrefix,\n requireBool,\n toTransient,\n} from '@cloudbitmaps/core/driver-kit';\nimport type { ObjectRegistryStore, ObjectRow } from '@cloudbitmaps/core/driver-kit';\nimport type { Storage } from '@google-cloud/storage';\nimport {\n asMissingBucket,\n missingBucketError,\n isNotFound,\n isPreconditionFailed,\n isTransient,\n transientFaultMessage,\n} from './gcs-errors';\nimport { BucketPresence } from './bucket-presence';\nimport { crc32cBase64 } from './crc32c';\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\ninterface 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. 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 private readonly presence: BucketPresence;\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 this.presence = new BucketPresence(storage, bucket);\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)) {\n await this.presence.confirm(); // a missing bucket is an error, not an absent row\n return null;\n }\n throw mapReadError(err, this.bucket);\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 // The checksum goes with the upload, for GCS to check before it stores anything. The SDK's own check runs\n // after the upload instead, and on a mismatch, or an answer that names no checksum, deletes the object by\n // name with no precondition: for a row, that is the live row, another writer's newer one included.\n validation: false,\n metadata: { crc32c: crc32cBase64(body) },\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 // A row write's 404 can be the bucket's: settle it before it is passed on.\n if (isNotFound(err)) await this.presence.confirm();\n throw mapError(err, this.bucket);\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. The registry re-reads and finds the row gone: an unfenced delete then\n // returns, and one fenced on a token reports the conflict, which its caller settles by reading the row (the\n // retention sweep does).\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 if (isNotFound(err)) await this.presence.confirm();\n throw new WriteConflictError(`registry OCC conflict deleting ${key}`);\n }\n throw mapError(err, this.bucket);\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 // A listing names no object, so its 404 can only be the bucket's.\n if (isNotFound(err)) throw missingBucketError(this.bucket, scrubCredentials(err));\n throw mapError(err, this.bucket);\n }\n for (const f of files ?? []) yield f.name;\n // A page that hands back the token it was asked with would be asked for again, forever.\n if (next?.pageToken !== undefined && next.pageToken === pageToken) {\n throw new IntegrityError('GCS listing returned the page token it was given');\n }\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/**\n * A read's error: a connection that failed or was cut off is transient too, after the driver's retries, whichever code\n * Node gave it, as `isTransient` takes every transport fault.\n */\nfunction mapReadError(err: unknown, bucket: string): 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 return mapError(err, bucket);\n}\n\n/**\n * Reclassify a transient GCS fault as a retryable {@link TransientError} and a missing bucket as a `ValidationError`;\n * any other error is passed through.\n */\nfunction mapError(err: unknown, bucket: string): unknown {\n return toTransient(\n asMissingBucket(scrubCredentials(err), bucket),\n isTransient,\n transientFaultMessage,\n );\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 requireBool('conditionalDelete', options.conditionalDelete) ?? false,\n ),\n normalizeObjectPrefix(options.prefix),\n options.now ?? ((): number => Date.now()),\n );\n }\n}\n", "/**\n * CRC32C (Castagnoli) of some bytes, in the form GCS takes in an object's `crc32c` metadata: the 4-byte big-endian\n * value, base64-encoded. Computed here rather than through the SDK so it is the same on every major the driver accepts.\n */\n\nconst TABLE = (() => {\n const table = new Uint32Array(256);\n for (let n = 0; n < 256; n++) {\n let c = n;\n for (let k = 0; k < 8; k++) c = c & 1 ? 0x82f63b78 ^ (c >>> 1) : c >>> 1;\n table[n] = c >>> 0;\n }\n return table;\n})();\n\nexport function crc32cBase64(bytes: Uint8Array): string {\n let crc = 0xffffffff;\n for (const byte of bytes) crc = TABLE[(crc ^ byte) & 0xff]! ^ (crc >>> 8);\n const value = (crc ^ 0xffffffff) >>> 0;\n const out = Buffer.alloc(4);\n out.writeUInt32BE(value);\n return out.toString('base64');\n}\n", "/**\n * The client a GCS backend sends its downloads on: one that never retries a download itself.\n *\n * `@google-cloud/storage` 7.x and 8.x retry a failed download by default, and when the retried request succeeds they\n * throw `ERR_STREAM_UNABLE_TO_PIPE` outside any promise, which ends the process whatever the caller wrote around the\n * call. So a download is sent once, and the driver retries it itself (`download-retry.ts`). The other requests keep the\n * client as it was built: a resumable upload's session, a listing, a delete (the conditional writes are sent once by\n * `send-once.ts` either way).\n *\n * ```\n * the client\n * \u2502\n * \u251C\u2500\u2500 not a Storage client (a test double) \u2500\u2500\u25B6 its retries off? yes: used as it is; no: refused\n * \u251C\u2500\u2500 its `bucket` replaced on the instance (a test stub), or overridden by its class \u2500\u2500\u25B6 refused\n * \u25BC\n * a twin, from the client's own class, with:\n * the same credentials object (one token cache), endpoint, universe, project, user agent, timeout, checksum\n * generator and retry settings but `autoRetry: false`, and one interceptor that applies the client's own\n * interceptors as they are when each request is sent (a later one too); then its own retry settings turned off,\n * over any its class set\n * \u2502\n * \u25BC\n * a second client, with a `bucket` method, the same credentials \u2500\u2500 yes \u2500\u2500\u25B6 downloads use the twin\n * object, retry settings of its own and off, the same URL and endpoint?\n * \u2502 no, or it could not be built\n * \u25BC\n * refused at construction, saying what differed\n * ```\n *\n * A `Storage` client is never used as it is, even one whose retries read off: the SDK turns a client's `autoRetry` off\n * while a delete or an upload with no precondition is in flight and back on when it ends, so that value, read once,\n * does not say how the client was built. The twin's retry settings are its own object, and only reads are sent on it,\n * which never change them; the SDK reads that object on every request.\n *\n * The twin is built from the client's own class, not this package's import: a caller on another major of the SDK hands\n * in a client whose credentials object is another copy's class, which this package's class would wrap in a second one.\n */\nimport { ValidationError } from '@cloudbitmaps/core/driver-kit';\nimport type { Storage, StorageOptions } from '@google-cloud/storage';\nimport { scrubCredentials } from './scrub-error';\n\n/** An interceptor's request hook, as the client's constructor takes one; `getRequestInterceptors` types them `Function`. */\ntype Intercept = NonNullable<StorageOptions['interceptors_']>[number]['request'];\n\n/**\n * A twin of `client` that sends each download once, or `client` itself when it is a test double whose retries are off.\n * `supplied`: the caller handed `client` in, rather than the backend building it.\n */\nexport function downloadClient(client: Storage, supplied: boolean): Storage {\n // A test double has nothing a twin could copy, and is used as it is when it says its retries are off.\n if (!isSdkClient(client)) {\n if (client.retryOptions?.autoRetry === false) return client;\n throw supplied\n ? refusal(\n true,\n 'it is not a `Storage` client (a test double, say)',\n 'Give it `retryOptions: { autoRetry: false }` to have it used as it is',\n )\n : refusal(\n false,\n \"the `Storage` it was built from is not the SDK's (a mocked module, say)\",\n 'Pass the mock as `client`, with `retryOptions: { autoRetry: false }`',\n );\n }\n const instead =\n 'Stub `Storage.prototype.bucket` instead, which reaches that client too, or pass a test double that does not ' +\n 'extend `Storage`, with `retryOptions: { autoRetry: false }`';\n if (Object.hasOwn(client, 'bucket')) {\n throw refusal(\n supplied,\n 'its `bucket` method is replaced on the instance, as by a test stub, which a client built from its class would ' +\n 'not have',\n instead,\n );\n }\n if (overridesBucket(client)) {\n throw refusal(\n supplied,\n 'its class overrides `bucket`, as a test double built on `Storage` does, so a client built from its class need ' +\n 'not hold what this one holds',\n instead,\n );\n }\n const second = supplied\n ? 'a second client built from its class with its settings'\n : 'a second client built from the same settings';\n const remedy = supplied\n ? 'Pass a client its class builds from the options it is given, as `Storage` does, or leave out `client`'\n : undefined;\n let twin: Storage;\n try {\n twin = twinOf(client);\n } catch (err) {\n throw refusal(supplied, `${second} could not be built`, remedy, err);\n }\n const why = unfaithful(client, twin);\n if (why !== undefined) throw refusal(supplied, `${second} ${why}`, remedy);\n return twin;\n}\n\n/**\n * Whether `client` is the SDK's own: its constructor sets `authClient` from the request factory it builds. A mock that\n * answers every property with a function, as an auto-mocking library's does, answers that one too, but not with the\n * same credentials object.\n */\nfunction isSdkClient(client: Storage): boolean {\n const factory: unknown = (client as { makeAuthenticatedRequest?: unknown })\n .makeAuthenticatedRequest;\n return (\n client.authClient !== undefined &&\n typeof factory === 'function' &&\n (factory as { authClient?: unknown }).authClient === client.authClient\n );\n}\n\n/** Whether a subclass of the SDK's client defines `bucket` over the SDK's own. */\nfunction overridesBucket(client: Storage): boolean {\n let defined = 0;\n for (let proto: unknown = Object.getPrototypeOf(client); proto !== null;) {\n if (Object.hasOwn(proto as object, 'bucket')) defined++;\n proto = Object.getPrototypeOf(proto);\n }\n return defined > 1;\n}\n\nfunction twinOf(client: Storage): Storage {\n const Own = client.constructor as new (options: StorageOptions) => Storage;\n const twin = new Own({\n authClient: client.authClient as StorageOptions['authClient'],\n // The SDK treats an endpoint spelled other than its default as a custom one, whose requests go without credentials,\n // and then stores it with any trailing slash taken off. A client given the default in another spelling is custom\n // with the default's URL, which that URL with a slash reproduces.\n apiEndpoint: client.customEndpoint === true ? `${client.apiEndpoint}/` : client.apiEndpoint,\n universeDomain: client.universeDomain,\n projectId: client.projectId,\n ...(client.useAuthWithCustomEndpoint === undefined\n ? {}\n : { useAuthWithCustomEndpoint: client.useAuthWithCustomEndpoint }),\n ...(client.providedUserAgent === undefined ? {} : { userAgent: client.providedUserAgent }),\n ...(client.timeout === undefined ? {} : { timeout: client.timeout }),\n crc32cGenerator: client.crc32cGenerator,\n retryOptions: { ...client.retryOptions, autoRetry: false },\n // A view, not a copy: the client's interceptors as they are when each request is sent.\n interceptors_: [\n {\n request: (options: Parameters<Intercept>[0]) =>\n (client.getRequestInterceptors() as Intercept[]).reduce(\n (sent, intercept) => intercept(sent),\n options as ReturnType<Intercept>,\n ),\n },\n ],\n });\n // A subclass may set retry settings of its own over the ones it is given. Turned off here, as the SDK turns them off\n // for a request it must not retry, on the twin's own settings only: never on an object the client holds too.\n if (\n twin !== client &&\n twin.retryOptions !== undefined &&\n twin.retryOptions !== client.retryOptions\n ) {\n twin.retryOptions.autoRetry = false;\n }\n return twin;\n}\n\n/**\n * What a twin did not take of the settings that decide where and how a download is sent, or `undefined`. A subclass may\n * build from options of its own, and would send downloads with other credentials, with the SDK's retries, or elsewhere.\n * Interceptors are carried as a view and not checked.\n */\nfunction unfaithful(client: Storage, twin: Storage): string | undefined {\n if (twin === client) return 'is the same client';\n if (typeof twin.bucket !== 'function') return 'has no `bucket` method';\n if (twin.authClient === undefined || twin.authClient !== client.authClient) {\n return 'would use other credentials';\n }\n if (twin.retryOptions === client.retryOptions) return 'would share its retry settings';\n if (twin.retryOptions?.autoRetry !== false) return \"keeps the SDK's retries\";\n // `baseUrl` prefers STORAGE_EMULATOR_HOST over `apiEndpoint` as each client is built.\n if (twin.baseUrl !== client.baseUrl) {\n return 'would send to another URL, as when `STORAGE_EMULATOR_HOST` changed after the client was built or is not a URL';\n }\n if (twin.apiEndpoint !== client.apiEndpoint) return 'would name another endpoint';\n if (twin.customEndpoint !== client.customEndpoint) {\n return 'would decide otherwise whether requests carry credentials';\n }\n return undefined;\n}\n\nfunction refusal(\n supplied: boolean,\n why: string,\n remedy: string | undefined,\n cause?: unknown,\n): ValidationError {\n const err = new ValidationError(\n (supplied\n ? `GcsStorage cannot send the supplied client's downloads without the SDK's retries: ${why}`\n : `GcsStorage cannot build a client that sends downloads without the SDK's retries: ${why}`) +\n (remedy === undefined ? '' : `. ${remedy}`),\n );\n // The credential-free copy, never the SDK's error as raised.\n if (cause !== undefined) err.cause = scrubCredentials(cause);\n return err;\n}\n"],
4
+ "sourcesContent": ["import {\n STORAGE_BACKEND,\n ValidationError,\n brandAsBackend,\n normalizeObjectPrefix,\n prefixPart,\n refuseUnknownOptions,\n requireNonEmptyString,\n} from '@cloudbitmaps/core/driver-kit';\nimport type {\n IRegistryDriver,\n IStorageDriver,\n LifecycleVerdict,\n StorageBackend,\n} from '@cloudbitmaps/core/driver-kit';\nimport { Storage as GcsClient } from '@google-cloud/storage';\nimport { defined } from './defined';\nimport { checkGcsLifecycle } from './lifecycle';\nimport { resolveReadTimeoutMs } from './read-timeout';\nimport { GcsStorageDriver } from './storage';\nimport { GcsRegistryDriver } from './registry';\nimport { downloadClient } from './read-client';\n\n/**\n * What {@link GcsStorage} takes: a `bucket`, and either a `client` or the two settings that build one (`projectId`,\n * `apiEndpoint`), never both.\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. A string, else `ValidationError`. */\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), and the downloads. In `@google-cloud/storage` 8.x, and in 7.x (which the driver's range does not admit), a download the SDK retries after a 408,\n * 429, 500, 502, 503 or 504 can crash the process with `ERR_STREAM_UNABLE_TO_PIPE`, so downloads go through a twin of\n * this client, built from its own class with the same credentials object, endpoint and settings and the SDK's retries\n * off over any its class set, and the driver retries them itself. A client whose twin would use other credentials or\n * another endpoint (a subclass that builds from options of its own), whose class overrides `bucket` (a test double\n * built on `Storage`), or whose `bucket` is replaced on the instance (a test stub; stub `Storage.prototype` instead)\n * is refused at construction. A test double that is not a `Storage` client is used as it is when it has\n * `retryOptions: { autoRetry: false }`, and refused without it. What is set on the client after the backend was\n * built, other than an interceptor, is not carried to the twin.\n */\n readonly client?: GcsClient;\n /**\n * Project id for the client built when `client` is absent (refused beside `client`): a non-empty string, else\n * `ValidationError`. Falls back to the SDK's own resolution.\n */\n readonly projectId?: string;\n /**\n * Endpoint override \u2014 point it at fake-gcs-server locally: a non-empty string, since an empty one would send\n * requests to the public endpoint, else `ValidationError`. Refused beside `client`, which carries its own.\n */\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 service applies `ifGenerationMatch` on an object delete, which both halves rely on when it is set: the\n * registry removes a deleted row for good rather than leaving a tombstone a full listing reads forever, and the\n * storage half removes an erasure's holder only while it is the object the erasure searched, by its generation.\n * Defaults to `false`, on the public endpoint too: whether real GCS applies `ifGenerationMatch` to a delete has not\n * been verified by a run against the service, and fake-gcs-server accepts the precondition and ignores it, so CI\n * cannot show it. Set `true` once you know it does. Until then, the window in which an erasure's stalled delete\n * takes a generation a load put under the number since stays open on GCS.\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/**\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 */\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 private readonly bucket: string;\n /** The name prefix every object sits under: `''`, or `trimmed/`. */\n private readonly keyPrefix: string;\n private readonly readTimeoutMs: number;\n\n /**\n * Builds the GCS backend over one bucket, which `bucket` names. Pass `client`, or `projectId` and `apiEndpoint` to\n * have one built, not both. `prefix` puts every object under a name prefix. Throws `ValidationError` for an unknown\n * option, no bucket, a `now` that is not a function, a client setting beside `client`, a `maxObjectBytes` or\n * `simpleUploadThresholdBytes` that is not a positive integer, a `conditionalDelete` that is not a boolean, an\n * empty or non-string `apiEndpoint` or `projectId`, or an invalid `prefix` or `readTimeoutMs`.\n */\n constructor(options: GcsStorageOptions) {\n let readClient: GcsClient;\n refuseUnknownOptions(\n 'GcsStorage',\n options,\n GCS_STORAGE_OPTION_KEYS,\n 'a @google-cloud/storage client goes in `client`',\n );\n if (typeof options.bucket !== 'string' || options.bucket.trim().length === 0) {\n throw new ValidationError('GcsStorage needs a `bucket`: the name of one, a non-empty string');\n }\n if (options.now !== undefined && typeof options.now !== 'function') {\n throw new ValidationError(\n \"GcsStorage's `now` must be a function that returns epoch milliseconds\",\n );\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 = downloadClient(options.client, true);\n } else {\n // An empty `apiEndpoint` is no endpoint: the client would send to the public cloud, not the service meant.\n const apiEndpoint = requireNonEmptyString('GcsStorage: `apiEndpoint`', options.apiEndpoint);\n // A bare scheme names no host; the client's own refusal of it talks about an environment variable instead.\n if (apiEndpoint !== undefined && /^[a-z][a-z0-9+.-]*:\\/*$/i.test(apiEndpoint.trim())) {\n throw new ValidationError(\n 'GcsStorage: `apiEndpoint` must name a host, such as \"http://localhost:4443\" or \"storage.googleapis.com\"',\n );\n }\n requireNonEmptyString('GcsStorage: `projectId`', options.projectId);\n this.client = new GcsClient(\n defined({ projectId: options.projectId, apiEndpoint: options.apiEndpoint }),\n );\n // Downloads go through a twin that sends each once (see `read-client.ts`); it shares this client's credentials\n // object, so one token fetch and one hourly refresh serve both.\n readClient = downloadClient(this.client, false);\n }\n this.bucket = options.bucket;\n this.keyPrefix = prefixPart(normalizeObjectPrefix(options.prefix));\n this.readTimeoutMs = resolveReadTimeoutMs(options.readTimeoutMs);\n const shared = {\n storage: this.client,\n readStorage: readClient,\n bucket: options.bucket,\n ...defined({ prefix: options.prefix, readTimeoutMs: options.readTimeoutMs }),\n };\n this.storage = new GcsStorageDriver({\n ...shared,\n ...defined({\n maxObjectBytes: options.maxObjectBytes,\n simpleUploadThresholdBytes: options.simpleUploadThresholdBytes,\n conditionalDelete: options.conditionalDelete,\n }),\n });\n this.registry = new GcsRegistryDriver({\n ...shared,\n ...defined({ now: options.now, conditionalDelete: options.conditionalDelete }),\n });\n brandAsBackend(this);\n }\n\n /**\n * Reads the bucket's metadata (one request, plus the SDK's own retries, bounded by `readTimeoutMs` when it is set) and\n * reports a lifecycle rule with a `Delete` action (or one it does not know) that applies to live\n * objects whose names can be this store's: generations and registry rows alike. Needs `storage.buckets.get`, which\n * a load never uses (`store.preflight()`'s lifecycle step does) and `roles/storage.objectAdmin` does not include: without it the answer is\n * `unchecked`, naming the permission. Never rejects; a `detail` carries no credential. Rule reading: see the\n * production guide.\n */\n checkLifecycle(): Promise<LifecycleVerdict> {\n return checkGcsLifecycle(this.client, this.bucket, this.keyPrefix, this.readTimeoutMs);\n }\n}\n", "/**\n * `values` without the entries that are `undefined`, typed as optional properties, so a constructor can pass on the\n * options it was given: `exactOptionalPropertyTypes` refuses `{ x: undefined }` for an optional `x`, and a sub-driver\n * must see an option the caller left out as absent.\n */\nexport function defined<T extends Record<string, unknown>>(\n values: T,\n): { [K in keyof T]?: Exclude<T[K], undefined> } {\n return Object.fromEntries(Object.entries(values).filter(([, v]) => v !== undefined)) as {\n [K in keyof T]?: Exclude<T[K], undefined>;\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 * Remove credentials from an error the GCS SDK raised, before the driver throws it or keeps it as a `cause`.\n *\n * The SDK keeps the response it got on the error (`response`). On an `ApiError` that is the HTTP layer's response\n * stream, whose `request` holds the headers it sent, the bearer token in `Authorization` included, and the HTTP agent\n * it shares with every other call, whose sockets hold the raw text of requests still in flight. A `GaxiosError` (a\n * resumable upload's, or a token request's) keeps the request's `config`, with its headers and body, and a `response`\n * that holds another copy of it. On a download, and wherever a body lists no errors, the SDK makes the body the error's\n * message as it came, so a proxy's error page or a JSON body can quote the `Authorization` value, or any other header,\n * anywhere in a line. The live objects are never changed (a socket still sending reads them): an error that holds a\n * transport, or whose data shows a credential, is replaced by a deep copy of its plain data. An error with neither is\n * returned as it is.\n *\n * The copy is a native error with the original's prototype (so `instanceof ApiError` holds), name, message, stack,\n * `code`, `status`, `errors` and every other plain field, with `response` reduced to its status and status text: what\n * classification reads. It has no `config`, request, agent or socket, no function or accessor (a `toJSON` or\n * `util.inspect` hook is a closure over the original) and no class instance other than an error. Errors are copied\n * wherever they sit: the `cause` chain, an `AggregateError`'s `errors`, any array or field. A table of copies keeps a\n * cycle a cycle, and anything nested deeper than `MAX_DEPTH` is dropped rather than kept raw.\n *\n * The value under a credential's name is replaced by `[redacted]`: a credential header's (`authorization`, the\n * encryption key's `x-goog-encryption-key`), a signed URL's signature and credential, what a service quotes of a request\n * whose signature it refused, an OAuth token or client secret, a service account's private key (its `client_email` and\n * key id are kept: they name the account and sign nothing); and so is the value after one in a flat list, under one\n * in a map, or beside one in a record (a `{ name, value }` pair, a `{ th, td }` row), except a number, a boolean and\n * the fields a Google error item describes an error with (`message`, `reason`, `location`), which classification reads.\n * In the copy's text, these are redacted: a credential header line; an `Authorization` value, an encryption key, an API\n * key, an OAuth token or client secret and a signed URL's signature or credential after its name; a cookie header's\n * value after its name; a bearer token after its scheme; a Google access token (`ya29.`) or a JWT on its own; a V2\n * signed URL's `Signature=`; a PEM private key's body; the text of an element named for a credential\n * (`<CanonicalRequest>`); and the text of the cell after one that names a credential (a table's `<th>` and `<td>`, a\n * list's `<dt>` and `<dd>`). A credential in any other form is not recognised (a header table laid out by column, a\n * colon written as an HTML entity in a body the SDK did not decode), and a text these steps cannot finish is replaced\n * whole.\n */\n\nimport { types } from 'node:util';\n\n/** Properties that hold the raw transport or the request's configuration. They are not copied. */\nconst TRANSPORT = new Set([\n 'request',\n 'agent',\n 'socket',\n '_httpMessage',\n 'connection',\n 'req',\n 'res',\n 'config',\n]);\n/** What a copy keeps of a `response`: what the server said, as numbers and text. */\nconst RESPONSE_FIELDS = ['status', 'statusText', 'statusCode', 'statusMessage'];\n/**\n * Header and field names whose value is a credential: the credential headers, a customer-supplied encryption key (as\n * a header and as the SDK's option), a signed URL's signature and credential, what a service quotes of a request whose\n * signature it refused, and the OAuth and service-account fields that hold a secret. A service account's `client_email`\n * and `private_key_id` are not among them: they name the account and its key, and sign nothing.\n */\nconst CREDENTIAL_NAMES = [\n 'authorization',\n 'proxy-authorization',\n 'cookie',\n 'set-cookie',\n 'x-api-key',\n 'x-goog-api-key',\n 'x-goog-encryption-key',\n 'x-goog-copy-source-encryption-key',\n 'encryption-key',\n 'encryption-key-base64',\n 'x-goog-signature',\n 'x-goog-credential',\n 'signature',\n 'signature-provided',\n 'canonical-request',\n 'token',\n 'access-token',\n 'refresh-token',\n 'id-token',\n 'subject-token',\n 'client-secret',\n 'private-key',\n 'assertion',\n];\n/** A name as it is compared: without the spaces and colon around it, in lower case, with `-` and `_` taken out. */\nconst fold = (name: string): string =>\n name.trim().replace(/:$/, '').replace(/[-_]/g, '').toLowerCase();\nconst CREDENTIAL_NAME = new Set(CREDENTIAL_NAMES.map(fold));\n/** The longest text that can spell one of the names. */\nconst LONGEST_NAME = 64;\n/** A credential header line inside raw request or response text. */\nconst CREDENTIAL_LINES =\n /^((?:proxy-)?authorization|cookie|set-cookie|x-api-key|x-goog-api-key|x-goog-(?:copy-source-)?encryption-key):[^\\r\\n]*/gim;\n/**\n * An `Authorization` value after its name anywhere in a text, its scheme with it: `authorization: Basic <value>`,\n * `authorization=Bearer%20<token>`, JSON's `\"authorization\":\"Bearer <token>\"` (also escaped once more).\n */\nconst AUTHORIZATION_VALUE =\n /((?:proxy-)?authorization\\\\?[\"']?\\s*(?::|=|%3D)\\s*\\\\?[\"']?)(?:[A-Za-z][\\w-]*(?:\\s+|%20|\\+))?[^\\s&\"'<>\\\\,;]+/gi;\n/**\n * A cookie header's value after its name anywhere in a text, every cookie in it: `cookie: a=1; b=2`, JSON's\n * `\"cookie\":\"a=1; b=2\"`. The name starts a word (`fortunecookie: 7` is not one), and the value runs to the end of the\n * line, a quote or a tag, since a cookie's value may itself hold `)` or `.`.\n */\nconst COOKIE_VALUE = /(?<![\\w-])((?:set-)?cookie\\\\?[\"']?\\s*:\\s*\\\\?[\"']?)[^\\r\\n\"'<>\\\\]+/gi;\n/** The headers and fields whose value follows their name in a text: an encryption key, an API key, a signed URL's parts. */\nconst NAMED =\n 'x-goog-(?:copy-source-)?encryption-key|x-goog-api-key|x-goog-signature|x-goog-credential|' +\n '(?:access|refresh|id|subject)[-_]token|client[-_]secret';\n/**\n * One of those after its name anywhere in a text: `name: value`, `name=value` (also percent-encoded) or JSON's\n * `\"name\":\"value\"` (also escaped once more).\n */\nconst NAMED_SECRET = new RegExp(\n `((?:${NAMED})\\\\\\\\?[\"']?\\\\s*(?::|=|%3D)\\\\s*\\\\\\\\?[\"']?)[^\\\\s&\"'<>\\\\\\\\,;]+`,\n 'gi',\n);\n/** A bearer token after its scheme, wherever the text shows it, also percent-encoded. */\nconst BEARER = /\\b(Bearer)(\\s+|%20|\\+)(?=[A-Za-z0-9\\-._~+/]{16})[A-Za-z0-9\\-._~+/]+=*/gi;\n/** A Google OAuth access token (`ya29.`), and a JWT: a signed assertion, an ID token, a self-signed account token. */\nconst TOKEN_TEXT =\n /ya29\\.(?=[A-Za-z0-9_.-]{16})[A-Za-z0-9_.-]+|(?<![A-Za-z0-9_-])eyJ(?=[A-Za-z0-9_-]{8})[A-Za-z0-9_-]+\\.eyJ(?=[A-Za-z0-9_-]{8})[A-Za-z0-9_-]+\\.(?=[A-Za-z0-9_-]{16})[A-Za-z0-9_-]+/g;\n/** A signed URL's `Signature=`, also percent-encoded. Its value is read by {@link redactSignatures}, not by a pattern. */\nconst SIGNATURE_NAME = /Signature(?:=|%3D)/gi;\n/** A run of the characters a base64 or hex signature is written in, `%` aside. */\nconst SIGNATURE_RUN = /[A-Za-z0-9+/=_.-]*/y;\n/** The shortest value taken for a signature. */\nconst SIGNATURE_MIN = 16;\n/** The lines that open and close a PEM private key, read where `indexOf` finds a `-----BEGIN ` or a `-----END`. */\nconst PEM_OPEN = /-----BEGIN [A-Z0-9 ]{0,40}PRIVATE KEY-----/y;\nconst PEM_CLOSE = /-----END [A-Z0-9 ]{0,40}PRIVATE KEY-----/y;\n/** An XML element; its text is redacted when its name is a credential's (`<CanonicalRequest>`, `<x-goog-encryption-key>`). */\nconst ELEMENT = /(<([A-Za-z][\\w.:-]{0,63})>)([^<]*)(<\\/\\2>)/g;\n/**\n * An element whose text may be a credential's name, and the text of the one after it, with up to four tags around\n * either: a table's `<th>` and `<td>`, a list's `<dt>` and `<dd>`, `<b>name:</b> <span>value`.\n */\nconst CELL =\n /(<([A-Za-z][\\w-]{0,15})\\b[^<>]{0,256}>\\s{0,32}([^<>\\s](?:[^<>]{0,62}[^<>\\s])?)\\s{0,32}<\\/\\2\\s{0,32}>(?:\\s{0,32}<\\/[A-Za-z][\\w-]{0,15}\\s{0,32}>){0,4}(?:\\s{0,32}<[A-Za-z][\\w-]{0,15}\\b[^<>]{0,256}>){1,4})([^<]*)/g;\nconst REDACTED = '[redacted]';\n/** How deep into errors and plain data the copy goes; anything below is dropped. */\nconst MAX_DEPTH = 10;\n\nconst isPlain = (value: object): boolean => {\n const proto: unknown = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n};\n\nconst isError = (value: object): boolean => value instanceof Error || types.isNativeError(value);\n\n/**\n * Whether `text` is a credential's name, as a key, a list item or a map key holds it. The whole name must match, in any\n * case and with `-` or `_` between its words or neither (`accessToken`, `access_token`): `pageToken` does not.\n */\nconst isCredentialName = (text: string): boolean =>\n text.length <= LONGEST_NAME && CREDENTIAL_NAME.has(fold(text));\n\n/**\n * `text` with every credential it shows replaced. Each step runs in time linear in the text, and none needs the\n * regular expression engine's backtracking stack to grow with it: a pattern that reads an unbounded run reads it with\n * one character class, and a value whose end a pattern would need alternation to find (a PEM key's body, a percent-\n * encoded signature) is read by a loop here instead. The `catch` is a fail-closed guard for what no test input reaches\n * (an engine limit, a result longer than a string can be): the text is then replaced whole, so a hostile body neither\n * leaks nor stops the error being thrown.\n */\nfunction redactText(text: string): string {\n try {\n const named = redactPrivateKeys(text)\n .replace(CREDENTIAL_LINES, `$1: ${REDACTED}`)\n .replace(AUTHORIZATION_VALUE, `$1${REDACTED}`)\n .replace(COOKIE_VALUE, `$1${REDACTED}`)\n .replace(NAMED_SECRET, `$1${REDACTED}`)\n .replace(BEARER, `$1$2${REDACTED}`)\n .replace(TOKEN_TEXT, REDACTED);\n return redactCells(\n redactSignatures(named).replace(\n ELEMENT,\n (whole, open: string, tag: string, inner: string, close: string) =>\n isCredentialName(tag) && inner.trim() !== '' ? `${open}${REDACTED}${close}` : whole,\n ),\n );\n } catch {\n return REDACTED;\n }\n}\n\n/**\n * `text` with each PEM private key's body redacted, its line breaks escaped or not, up to the next `-----END` or the\n * end of the text; the lines that open and close it are kept. The armour is found with `indexOf`.\n */\nfunction redactPrivateKeys(text: string): string {\n let out = '';\n let from = 0;\n for (\n let at = text.indexOf('-----BEGIN ');\n at !== -1;\n at = text.indexOf('-----BEGIN ', Math.max(at + 1, from))\n ) {\n PEM_OPEN.lastIndex = at;\n const open = PEM_OPEN.exec(text);\n if (open === null) continue;\n const body = at + open[0].length;\n const end = text.indexOf('-----END', body);\n let close = '';\n if (end !== -1) {\n PEM_CLOSE.lastIndex = end;\n close = PEM_CLOSE.exec(text)?.[0] ?? '';\n }\n out += `${text.slice(from, body)}${REDACTED}${close}`;\n from = end === -1 ? text.length : end + close.length;\n }\n return from === 0 ? text : out + text.slice(from);\n}\n\n/**\n * `text` with the value after each `Signature=` redacted when it is {@link SIGNATURE_MIN} characters or more: base64 or\n * hex, percent-encoded or not, up to an encoded `&` (`%26`) or a character no signature is written in.\n */\nfunction redactSignatures(text: string): string {\n let out = '';\n let from = 0;\n SIGNATURE_NAME.lastIndex = 0;\n for (let m = SIGNATURE_NAME.exec(text); m !== null; m = SIGNATURE_NAME.exec(text)) {\n const start = m.index + m[0].length;\n let end = start;\n for (;;) {\n SIGNATURE_RUN.lastIndex = end;\n SIGNATURE_RUN.exec(text);\n end = SIGNATURE_RUN.lastIndex;\n if (text[end] !== '%' || text.startsWith('%26', end)) break;\n end += 1;\n }\n if (end - start >= SIGNATURE_MIN) {\n out += `${text.slice(from, start)}${REDACTED}`;\n from = end;\n }\n SIGNATURE_NAME.lastIndex = end;\n }\n return from === 0 ? text : out + text.slice(from);\n}\n\n/**\n * `text` with the text of each element after one that names a credential redacted. A match whose first element names\n * none is not taken past that element: the element after it is tried as the next name, so a heading or a caption\n * before a table does not put every row out of step.\n */\nfunction redactCells(text: string): string {\n let out = '';\n let from = 0;\n CELL.lastIndex = 0;\n for (let m = CELL.exec(text); m !== null; m = CELL.exec(text)) {\n const [whole, head, , name, value] = m as unknown as [string, string, string, string, string];\n if (isCredentialName(name) && value.trim() !== '') {\n out += `${text.slice(from, m.index)}${head}${REDACTED}`;\n from = m.index + whole.length;\n } else {\n CELL.lastIndex = m.index + 1;\n }\n }\n return from === 0 ? text : out + text.slice(from);\n}\n\n/** The copies made so far, and whether any differs from its original. */\ninterface Copying {\n copies: WeakMap<object, unknown>;\n changed: boolean;\n}\n\n/** Marker for a value the copy leaves out. */\nconst OMIT = Symbol('omit');\n\n/** An array index as an own key spells it. */\nconst INDEX = /^(?:0|[1-9]\\d*)$/;\nconst isIndex = (key: string | symbol): key is string =>\n typeof key === 'string' && INDEX.test(key) && Number(key) < 2 ** 32 - 1;\n\n/**\n * Whether `value` has an own property its copy leaves out: any key but an index and `length` on an array, any key at\n * all on a map or a set.\n */\nfunction hasNamedKeys(value: object): boolean {\n if (!Array.isArray(value)) return Reflect.ownKeys(value).length > 0;\n return Reflect.ownKeys(value).some((key) => key !== 'length' && !isIndex(key));\n}\n\n/** Define `key` on `copy` as a plain data property. */\nfunction define(copy: object, key: string | symbol, value: unknown, enumerable: boolean): void {\n Object.defineProperty(copy, key, { value, writable: true, configurable: true, enumerable });\n}\n\n/** A credential-free copy of `value`: plain data and errors only, `OMIT` for anything else. */\nfunction copyOf(value: unknown, ctx: Copying, depth: number): unknown {\n if (typeof value === 'string') {\n const clean = redactText(value);\n if (clean !== value) ctx.changed = true;\n return clean;\n }\n if (typeof value === 'function' || typeof value === 'symbol') {\n ctx.changed = true;\n return OMIT;\n }\n if (typeof value !== 'object' || value === null) return value;\n if (ctx.copies.has(value)) return ctx.copies.get(value);\n if (depth > MAX_DEPTH) {\n ctx.changed = true;\n return OMIT;\n }\n if (Array.isArray(value)) return copyList(value as unknown[], ctx, depth);\n if (value instanceof Map) return copyMap(value as Map<unknown, unknown>, ctx, depth);\n if (value instanceof Set) return copySet(value as Set<unknown>, ctx, depth);\n return copyRecord(value, ctx, depth);\n}\n\n/**\n * A copy of an array, as sparse as the original: only its own indexes are read, so a hole costs nothing. A named\n * property is left out.\n */\nfunction copyList(value: unknown[], ctx: Copying, depth: number): unknown[] {\n const list: unknown[] = new Array<unknown>(value.length);\n ctx.copies.set(value, list);\n if (hasNamedKeys(value)) ctx.changed = true;\n let hidden = false;\n let last = -1;\n for (const key of Reflect.ownKeys(value)) {\n if (!isIndex(key)) continue;\n const index = Number(key);\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n if (descriptor === undefined || !('value' in descriptor)) {\n ctx.changed = true;\n hidden = false;\n continue;\n }\n const item: unknown = descriptor.value;\n // A flat list of header names and values: the value right after a credential name is one.\n if (hidden && index === last + 1 && item != null) {\n list[index] = REDACTED;\n if (item !== REDACTED) ctx.changed = true;\n } else {\n const kept = copyOf(item, ctx, depth + 1);\n if (kept === OMIT) delete list[index];\n else list[index] = kept;\n }\n hidden = typeof item === 'string' && isCredentialName(item);\n last = index;\n }\n return list;\n}\n\n/** A copy of a map: the value under a credential name is redacted. A named property is left out. */\nfunction copyMap(value: Map<unknown, unknown>, ctx: Copying, depth: number): Map<unknown, unknown> {\n const map = new Map<unknown, unknown>();\n ctx.copies.set(value, map);\n if (hasNamedKeys(value)) ctx.changed = true;\n for (const [k, v] of value) {\n const key = copyOf(k, ctx, depth + 1);\n if (key === OMIT) continue;\n if (typeof k === 'string' && isCredentialName(k) && v != null) {\n map.set(key, REDACTED);\n if (v !== REDACTED) ctx.changed = true;\n continue;\n }\n const kept = copyOf(v, ctx, depth + 1);\n if (kept !== OMIT) map.set(key, kept);\n }\n return map;\n}\n\n/** A copy of a set. A named property is left out. */\nfunction copySet(value: Set<unknown>, ctx: Copying, depth: number): Set<unknown> {\n const set = new Set<unknown>();\n ctx.copies.set(value, set);\n if (hasNamedKeys(value)) ctx.changed = true;\n for (const v of value) {\n const kept = copyOf(v, ctx, depth + 1);\n if (kept !== OMIT) set.add(kept);\n }\n return set;\n}\n\n/** A copy of an error or a plain object, `OMIT` for any other object (a class instance: a stream, a socket). */\nfunction copyRecord(value: object, ctx: Copying, depth: number): unknown {\n const errorLike = isError(value);\n if (!errorLike && !isPlain(value)) {\n ctx.changed = true;\n return OMIT;\n }\n const copy: object = errorLike\n ? (Object.setPrototypeOf(new Error(), Object.getPrototypeOf(value) as object | null) as object)\n : (Object.create(Object.getPrototypeOf(value) as object | null) as object);\n ctx.copies.set(value, copy);\n if (errorLike) delete (copy as { stack?: unknown }).stack;\n for (const key of Reflect.ownKeys(value)) {\n if (typeof key === 'string' && TRANSPORT.has(key)) {\n ctx.changed = true;\n } else if (errorLike && key === 'stack') {\n copyStack(value, copy, ctx, depth);\n } else {\n copyField(value, copy, key, ctx, depth);\n }\n }\n if (errorLike) keepSlotFields(value, copy, ctx, depth);\n redactPairs(value, copy, errorLike, ctx);\n return copy;\n}\n\n/**\n * The fields a Google error item describes an error with (`{ message, domain, reason, location, locationType }`). They\n * are not a name and a value: a `401` names the header it refused in `location` (`Authorization`) beside the reason and\n * message classification reads. They are never taken as a credential's name, and never redacted beside one; the text\n * rules still apply to them.\n */\nconst DESCRIBING = new Set([\n 'message',\n 'domain',\n 'reason',\n 'location',\n 'locationType',\n 'extendedHelp',\n]);\n\n/**\n * Redact the value a record holds beside a credential's name: a `{ name, value }` pair, a table row parsed to\n * `{ th, td }`. On a plain record every field but the name is redacted. On an error, whose other fields are what\n * classification reads, only the `...Value` field beside a `...Name` one is. A number or a boolean is never a\n * credential, and is kept.\n */\nfunction redactPairs(original: object, copy: object, errorLike: boolean, ctx: Copying): void {\n const names = Reflect.ownKeys(original).filter((key): key is string => {\n if (typeof key !== 'string' || DESCRIBING.has(key)) return false;\n const held = Object.getOwnPropertyDescriptor(original, key)?.value as unknown;\n return typeof held === 'string' && isCredentialName(held);\n });\n if (names.length === 0) return;\n const values = errorLike\n ? names\n .filter((key) => /name$/i.test(key))\n .map((key) => `${key.slice(0, -4)}value`.toLowerCase())\n : undefined;\n for (const key of Reflect.ownKeys(copy)) {\n if (typeof key !== 'string' || names.includes(key) || DESCRIBING.has(key)) continue;\n if (values !== undefined && !values.includes(key.toLowerCase())) continue;\n const descriptor = Object.getOwnPropertyDescriptor(copy, key);\n if (descriptor?.value == null || descriptor.value === REDACTED) continue;\n // What classification reads of a record is a number or a boolean (a response's `status`), never a credential.\n if (typeof descriptor.value !== 'string' && typeof descriptor.value !== 'object') continue;\n define(copy, key, REDACTED, descriptor.enumerable ?? false);\n ctx.changed = true;\n }\n}\n\n/** Whether `value` is a response as a copy keeps it: a plain record of the response's numbers and text alone. */\nfunction isSummary(value: object): boolean {\n return (\n isPlain(value) &&\n Reflect.ownKeys(value).every((key) => {\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n const held: unknown = descriptor?.value;\n return (\n typeof key === 'string' &&\n RESPONSE_FIELDS.includes(key) &&\n (typeof held === 'number' || typeof held === 'string')\n );\n })\n );\n}\n\n/**\n * The part of an HTTP response worth keeping: what the server said, as numbers and text. Only those are read (a getter\n * such as a fetch `Response`'s `status` included), and the live response is never kept.\n */\nfunction copyResponse(response: object, ctx: Copying, depth: number): unknown {\n // A copy's response is already this, and is copied as the plain data it is.\n if (isSummary(response)) return copyOf(response, ctx, depth);\n ctx.changed = true;\n const summary: Record<string, unknown> = {};\n for (const key of RESPONSE_FIELDS) {\n let held: unknown;\n try {\n held = (response as Record<string, unknown>)[key];\n } catch {\n continue;\n }\n if (typeof held === 'number' || typeof held === 'string') {\n summary[key] = copyOf(held, ctx, depth + 1);\n }\n }\n return summary;\n}\n\n/** Carry an error's stack over, read through the original: it may be an accessor bound to the error itself. */\nfunction copyStack(original: object, copy: object, ctx: Copying, depth: number): void {\n let stack: unknown;\n try {\n stack = (original as { stack?: unknown }).stack;\n } catch {\n return;\n }\n const kept = copyOf(stack, ctx, depth + 1);\n if (kept !== OMIT) define(copy, 'stack', kept, false);\n}\n\n/**\n * Carry one own property over, as a data property: redacted under a credential name, reduced to its status when it is\n * a `response`, left out when it is not data.\n */\nfunction copyField(\n original: object,\n copy: object,\n key: string | symbol,\n ctx: Copying,\n depth: number,\n): void {\n const descriptor = Object.getOwnPropertyDescriptor(original, key);\n if (descriptor === undefined) return;\n // A function or accessor is a closure over the original (a `toJSON`, an inspect hook): it is not carried over.\n if (!('value' in descriptor)) {\n ctx.changed = true;\n return;\n }\n // A key is text too: one that quotes a credential is carried redacted.\n const name = typeof key === 'string' ? redactText(key) : key;\n if (name !== key) ctx.changed = true;\n const held: unknown = descriptor.value;\n let kept: unknown;\n if (typeof key === 'string' && isCredentialName(key) && held != null) {\n kept = REDACTED;\n if (held !== REDACTED) ctx.changed = true;\n } else if (key === 'response' && typeof held === 'object' && held !== null) {\n kept = copyResponse(held, ctx, depth + 1);\n } else {\n kept = copyOf(held, ctx, depth + 1);\n if (kept === OMIT) return;\n }\n Object.defineProperty(copy, name, {\n value: kept,\n writable: descriptor.writable ?? true,\n configurable: true,\n enumerable: descriptor.enumerable ?? false,\n });\n}\n\n/**\n * Carry over a `name`, `message` or `code` the original reads through its prototype and the copy cannot: a\n * `DOMException`'s are getters over the instance's internal state, which throw on any other object.\n */\nfunction keepSlotFields(original: object, copy: object, ctx: Copying, depth: number): void {\n for (const key of ['name', 'message', 'code'] as const) {\n if (Object.prototype.hasOwnProperty.call(original, key)) continue;\n let held: unknown;\n try {\n held = (original as Record<string, unknown>)[key];\n } catch {\n continue;\n }\n let read: unknown = OMIT;\n try {\n read = (copy as Record<string, unknown>)[key];\n } catch {\n // the copy cannot read it\n }\n if (read === held) continue;\n const kept = copyOf(held, ctx, depth + 1);\n if (kept !== OMIT) define(copy, key, kept, false);\n }\n}\n\n/**\n * `err` as it is safe to throw: a credential-free deep copy of an error or plain object when it holds a transport or\n * shows a credential, the value itself when it holds neither, and any other value (a class instance that is not an\n * error, a primitive) as it is. The live error is never changed.\n */\nexport function scrubCredentials<T>(err: T): T {\n if (typeof err !== 'object' || err === null) return err;\n const ctx: Copying = { copies: new WeakMap(), changed: false };\n const copy = copyOf(err, ctx, 0);\n return (ctx.changed && copy !== OMIT ? copy : err) as T;\n}\n", "/**\n * The GCS lifecycle check behind `GcsStorage.checkLifecycle`: reads the bucket's metadata (one request) and says whether\n * a lifecycle rule would delete what the store keeps.\n *\n * Everything the store keeps sits under one object-name prefix, and every name ends `.crbm` (a generation) or `.reg`\n * (a registry row). A rule is a **problem** when its action is `Delete` (or one this check does not know, or none), it applies\n * to live objects (not `isLive: false` and not one of the conditions that only noncurrent versions meet:\n * `numNewerVersions` of 1 or more, which a live object never meets because none is newer than it, and\n * `daysSinceNoncurrentTime` and `noncurrentTimeBefore` at any value, because a live object has no noncurrent time), and its `matchesPrefix` and `matchesSuffix`, when it has them, can match a name of ours: a\n * prefix and ours are one a prefix of the other, and a suffix and `.crbm` or `.reg` are one a suffix of the other. Any\n * other condition (age, creation date, storage class, a name pattern) is not interpreted and is judged as if it matched,\n * so a rule it would not match can be reported; one that does is never hidden. `SetStorageClass` (even to ARCHIVE, which\n * stays readable) and `AbortIncompleteMultipartUpload` are safe.\n */\nimport type { Storage } from '@google-cloud/storage';\nimport type { LifecycleVerdict } from '@cloudbitmaps/core/driver-kit';\nimport { ReadTimedOut, startDeadline, withDeadline } from './read-timeout';\nimport { scrubCredentials } from './scrub-error';\n\n/** The longest the message of a failed request is shown. */\nconst MAX_MESSAGE = 200;\n\n/** The longest an error name, a prefix or a rule's condition is shown. */\nconst MAX_FIELD = 64;\nconst MAX_CONDITION = 120;\n\n/** How many problem rules a `detail` names before it says \"and N more\". */\nexport const MAX_NAMED_RULES = 3;\n\nconst cut = (text: string, max: number): string => (text.length > max ? text.slice(0, max) : text);\n\n/** Actions that leave every object readable. Any other action type is judged like a `Delete`. */\nconst SAFE_ACTIONS: ReadonlySet<string> = new Set([\n 'SetStorageClass',\n 'AbortIncompleteMultipartUpload',\n]);\n\n/** The suffixes of every name the store writes. */\nconst NAME_SUFFIXES = ['.crbm', '.reg'] as const;\n\n/** Two strings share a prefix relation (one starts with the other). */\nconst prefixRelated = (a: string, b: string): boolean => a.startsWith(b) || b.startsWith(a);\n/** Two strings share a suffix relation (one ends with the other). */\nconst suffixRelated = (a: string, b: string): boolean => a.endsWith(b) || b.endsWith(a);\n\nconst strings = (value: unknown): string[] =>\n Array.isArray(value) ? value.filter((v): v is string => typeof v === 'string') : [];\n\nconst record = (value: unknown): Record<string, unknown> =>\n typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : {};\n\n/** Conditions only a noncurrent version can meet, at any value: a live object has no noncurrent time. */\nconst NONCURRENT_ONLY = ['daysSinceNoncurrentTime', 'noncurrentTimeBefore'];\n\n/**\n * Whether a live object can meet the condition `numNewerVersions`: it holds when at least that many newer versions\n * exist, and a live object has none, so only a 0 matches it (a value that is not a number is judged as met).\n */\nconst metByLiveObject = (value: unknown): boolean => typeof value !== 'number' || value < 1;\n\n/** The action type of a rule, or `undefined` when it names none. */\nconst actionOf = (rule: unknown): string | undefined => {\n const type = record(record(rule).action).type;\n return typeof type === 'string' && type !== '' ? type : undefined;\n};\n\n/** Whether `rule` is an object, the only shape a rule can have; anything else is no rule and is skipped. */\nconst isRule = (rule: unknown): boolean => typeof rule === 'object' && rule !== null;\n\n/** Whether `rule` has an action that deletes, is not known to be safe or names none, and applies to live objects whose name can be one of ours, under `prefix`. */\nfunction deletesOurs(rule: unknown, prefix: string): boolean {\n if (!isRule(rule)) return false;\n const type = actionOf(rule);\n if (type !== undefined && SAFE_ACTIONS.has(type)) return false;\n const when = record(record(rule).condition);\n if (when.isLive === false) return false;\n if (NONCURRENT_ONLY.some((key) => when[key] !== undefined)) return false;\n if (!metByLiveObject(when.numNewerVersions)) return false;\n const prefixes = strings(when.matchesPrefix);\n if (prefixes.length > 0 && !prefixes.some((p) => prefixRelated(p, prefix))) return false;\n const suffixes = strings(when.matchesSuffix);\n return (\n suffixes.length === 0 ||\n suffixes.some((s) => NAME_SUFFIXES.some((ours) => suffixRelated(s, ours)))\n );\n}\n\n/**\n * Whether narrowing the rule's `matchesPrefix` cannot exclude the store's prefix: the rule names prefixes, and none is\n * a shorter prefix of the store's, so each is the store's own or sits inside it.\n */\nfunction sitsInside(rule: unknown, prefix: string): boolean {\n const prefixes = strings(record(record(rule).condition).matchesPrefix);\n return (\n prefix !== '' &&\n prefixes.length > 0 &&\n !prefixes.some((p) => p !== prefix && prefix.startsWith(p))\n );\n}\n\n/**\n * What a bucket's metadata means for a store under `prefix` (`''`, or `trimmed/`). Pure: no request.\n */\nfunction judgeMetadata(metadata: unknown, prefix: string): LifecycleVerdict {\n const rules = record(record(metadata).lifecycle).rule;\n const found = (Array.isArray(rules) ? (rules as unknown[]) : []).filter((r) =>\n deletesOurs(r, prefix),\n );\n if (found.length > 0) {\n const named = found\n .slice(0, MAX_NAMED_RULES)\n .map(\n (r) =>\n `${cut(actionOf(r) ?? 'no action type', MAX_FIELD)} when ${cut(JSON.stringify(record(r).condition ?? {}), MAX_CONDITION)}`,\n )\n .join('; ');\n const more =\n found.length > MAX_NAMED_RULES ? ` and ${found.length - MAX_NAMED_RULES} more` : '';\n const quoted = JSON.stringify(cut(prefix, MAX_FIELD));\n const narrow = `Narrow the rule with matchesPrefix to exclude ${quoted}, or restrict it to noncurrent versions.`;\n const retarget =\n `A rule that sits inside ${quoted} cannot be narrowed to exclude it: remove it or retarget it to another ` +\n 'prefix, or restrict it to noncurrent versions.';\n const where =\n prefix === ''\n ? 'The store has no prefix, so a rule on any prefix can reach it: give the store a ' +\n 'prefix, or scope the rule away from its paths.'\n : [\n found.some((r) => !sitsInside(r, prefix)) ? narrow : '',\n found.some((r) => sitsInside(r, prefix)) ? retarget : '',\n ]\n .filter((x) => x !== '')\n .join(' ');\n return {\n status: 'problem',\n detail:\n `${found.length} bucket lifecycle rule(s) with a Delete action, or one this check does not know, can match objects under ${quoted}: ` +\n `${named}${more}. The store keeps current objects it must be able to read back. ${where}`,\n };\n }\n return {\n status: 'ok',\n detail: `no bucket lifecycle rule deletes live objects under ${JSON.stringify(cut(prefix, MAX_FIELD))}`,\n };\n}\n\n/** A failed request as a sentence: the permission to grant for a refusal, the scrubbed error otherwise. */\nfunction unchecked(err: unknown): LifecycleVerdict {\n if (err instanceof ReadTimedOut) {\n return {\n status: 'unchecked',\n detail:\n 'the read of the bucket metadata got no answer in time (readTimeoutMs); run the check again',\n };\n }\n const scrubbed = scrubCredentials(err);\n const { code, message, name } = record(scrubbed);\n if (code === 403) {\n return {\n status: 'unchecked',\n detail:\n 'the identity may not read the bucket metadata: grant storage.buckets.get on the bucket to check its ' +\n 'lifecycle rules, or confirm by hand that no rule deletes current objects under this prefix',\n };\n }\n const shown = typeof message === 'string' ? `: ${message.slice(0, MAX_MESSAGE)}` : '';\n const label = typeof name === 'string' ? cut(name, MAX_FIELD) : 'Error';\n const status = typeof code === 'number' ? ` ${code}` : '';\n return {\n status: 'unchecked',\n detail: `could not read the bucket metadata (${label}${status}${shown}); check the lifecycle rules by hand`,\n };\n}\n\n/** The check: one request (the SDK may retry it), under `readTimeoutMs` (`0`: none), never a throw. */\nexport async function checkGcsLifecycle(\n client: Storage,\n bucket: string,\n prefix: string,\n readTimeoutMs = 0,\n): Promise<LifecycleVerdict> {\n try {\n const [metadata] = await withDeadline(\n () => client.bucket(bucket).getMetadata(),\n startDeadline(readTimeoutMs, 'bucket metadata read'),\n );\n return judgeMetadata(metadata, prefix);\n } catch (err) {\n try {\n return unchecked(err);\n } catch {\n return { status: 'unchecked', detail: 'could not read the bucket metadata' };\n }\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 *\n * **A delete given `ifVersion` is sent with `ifGenerationMatch: <that object generation>`** when `conditionalDelete` is\n * set, which it is not by default, as for the registry: no run against real GCS has shown the service applies the\n * precondition to a delete, and fake-gcs-server accepts it and ignores it. A tail read reports the object's generation\n * as its version, from the `x-goog-generation` header of the GET that carried the bytes, or the metadata when none were\n * asked for. A `412` is the precondition failing, and is reported as {@link WriteConflictError} unless the metadata then\n * shows no object under the name: an absent object is a no-op, whether the service answers it `404` or `412`. The\n * delete keeps the SDK's retry, which re-sends a request with a precondition: a copy that meets its own landed delete\n * finds nothing, and one that meets an object stored since is refused.\n */\nimport {\n errorKind,\n NotFoundError,\n TransientError,\n ValidationError,\n WriteConflictError,\n concatBytes,\n normalizeObjectPrefix,\n parseGenerationFromKey,\n checkDeleteOptions,\n requireBool,\n requirePositiveInt,\n segmentObjectPrefix,\n storageObjectKey,\n toTransient,\n} from '@cloudbitmaps/core/driver-kit';\nimport type {\n BlobSink,\n GenKey,\n IStorageDriver,\n SegmentRef,\n StorageCaps,\n StorageDeleteOptions,\n TailRead,\n} from '@cloudbitmaps/core/driver-kit';\nimport type { Clock, Rng } from '@cloudbitmaps/core';\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 { BucketPresence } from './bucket-presence';\nimport {\n asMissingBucket,\n missingBucketError,\n isInvalidRange,\n isNotFound,\n isPreconditionFailed,\n isThrottle,\n isTransient,\n transientFaultMessage,\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/** GCS's hard limit on one object (5 TiB), and the default ceiling. Set lower to fail fast on a runaway write. */\nconst GCS_MAX_OBJECT_BYTES = 5 * 1024 ** 4;\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. */\ntype Sleeper = Pick<Clock, 'sleep'>;\n\nconst REAL_TIME: Sleeper = {\n sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),\n};\n/** Where the backoff's jitter comes from when none is injected. */\nconst REAL_RNG: Rng = { next: () => Math.random() };\n\n/** A refusal this driver already typed: it passes on unmapped, where a raw SDK error is mapped. */\nfunction isTypedPassThrough(err: unknown): boolean {\n const kind = errorKind(err);\n return kind === 'ValidationError' || kind === 'WriteConflictError' || kind === 'NotFoundError';\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\ninterface 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 /** Where the jitter of that backoff is drawn from; `Math.random` when absent. */\n readonly rng?: Rng;\n /**\n * Whether a delete given `ifVersion` is sent with `ifGenerationMatch`, so it removes the object only while it is the\n * one that generation names. Defaults to `false`, on the public endpoint too, as the registry's option does: whether\n * real GCS applies `ifGenerationMatch` to a delete has not been verified by a run against the service, and\n * fake-gcs-server accepts the precondition and ignores it. Set `true` once you have. Until it is set, a delete\n * decided from one read of an object removes whatever is under the name by then: on GCS the window in which an\n * erasure's stalled delete takes a generation a load put under the number since stays open.\n */\n readonly conditionalDelete?: boolean;\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 private readonly rng: Rng;\n private readonly presence: BucketPresence;\n private readonly conditionalDelete: boolean;\n\n constructor(options: GcsStorageDriverOptions) {\n this.storage = options.storage;\n this.conditionalDelete = requireBool('conditionalDelete', options.conditionalDelete) ?? false;\n this.clock = options.clock ?? REAL_TIME;\n this.rng = options.rng ?? REAL_RNG;\n this.readStorage = options.readStorage ?? options.storage;\n this.bucket = options.bucket;\n this.prefix = normalizeObjectPrefix(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 requirePositiveInt('maxObjectBytes', options.maxObjectBytes);\n requirePositiveInt('simpleUploadThresholdBytes', options.simpleUploadThresholdBytes);\n // GCS's own limit on one object: a cap past it is advertised and never reachable.\n if (options.maxObjectBytes !== undefined && options.maxObjectBytes > GCS_MAX_OBJECT_BYTES) {\n throw new ValidationError(\n `maxObjectBytes must be at most GCS's 5 TiB object limit (${GCS_MAX_OBJECT_BYTES}); got ${options.maxObjectBytes}`,\n );\n }\n this.maxObjectBytes = options.maxObjectBytes ?? GCS_MAX_OBJECT_BYTES;\n this.threshold = options.simpleUploadThresholdBytes ?? DEFAULT_UPLOAD_THRESHOLD_BYTES;\n this.readTimeoutMs = resolveReadTimeoutMs(options.readTimeoutMs);\n this.presence = new BucketPresence(this.storage, this.bucket);\n }\n\n capabilities(): StorageCaps {\n return {\n rangeRead: true,\n maxObjectBytes: this.maxObjectBytes,\n conditionalPut: true,\n conditionalDelete: this.conditionalDelete,\n };\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 = storageObjectKey(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 this.rng,\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 (isTypedPassThrough(err)) throw err;\n // An upload's 404 can be the bucket's: settle it before it is passed on.\n if (isNotFound(err)) await this.presence.confirm();\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 = storageObjectKey(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 await this.readFailure(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<TailRead> {\n const objectName = storageObjectKey(this.prefix, key);\n // The rule every driver's tail read holds to: a safe integer, and zero or less an empty tail.\n if (!Number.isSafeInteger(maxBytes)) {\n throw new ValidationError(`invalid tail length ${String(maxBytes)}`);\n }\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 const meta = await this.metadataOf(objectName, deadline);\n return withVersion({ bytes: new Uint8Array(0), size: meta.size }, meta.version);\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 meta = await this.metadataOf(objectName, deadline);\n if (meta.size !== 0) throw err;\n return withVersion({ bytes: new Uint8Array(0), size: 0 }, meta.version);\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 meta = await this.metadataOf(objectName, deadline);\n if (meta.size !== 0) {\n throw this.badTail(key, `no bytes returned for an object of ${meta.size}B`);\n }\n return withVersion({ bytes: res.bytes, size: 0 }, meta.version);\n }\n // The version of the object the bytes came from: the generation the GET that carried them names.\n const size = this.tailSize(res, maxBytes, key);\n return withVersion(\n { bytes: res.bytes, size },\n objectGeneration(singleHeader(res.headers, 'x-goog-generation')),\n );\n } catch (err) {\n throw await this.readFailure(err, key);\n }\n }\n\n /**\n * The object's size from its metadata, validated, and its generation when the metadata names a usable one. Read on the\n * download client and retried as a download is, within the calling read's `deadline` ({@link withDeadline}), so\n * nothing but the one request in flight runs past it.\n */\n private async metadataOf(\n objectName: string,\n deadline: Deadline | undefined,\n ): Promise<{ size: number; version: string | undefined }> {\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 {\n size,\n version: objectGeneration(\n meta.generation === undefined ? undefined : String(meta.generation),\n ),\n };\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, options?: StorageDeleteOptions): Promise<void> {\n const objectName = storageObjectKey(this.prefix, key);\n const ifVersion = checkDeleteOptions(options);\n const conditional = ifVersion !== undefined && this.conditionalDelete;\n // Idempotent: a racing/retried GC sweep of an absent object is a no-op. Not the SDK's `ignoreNotFound`, which takes\n // every 404 and so would report a delete in a missing bucket as done.\n try {\n await this.file(objectName).delete(\n ...(conditional ? [{ ifGenerationMatch: generationOf(ifVersion, key) }] : []),\n );\n } catch (err) {\n if (isNotFound(err)) return this.presence.confirm();\n // A failed precondition is a conflict only while an object is under the name: a service may answer it for a name\n // with nothing there, a copy of this delete that landed first included.\n if (conditional && isPreconditionFailed(err)) {\n if (!(await this.exists(objectName))) return;\n throw new WriteConflictError(\n `generation ${key.segment}.${key.generation} is another object than the version given; not deleted`,\n );\n }\n throw this.mapError(err);\n }\n }\n\n /** Whether an object is under the name, from its metadata. A read that fails in any other way throws. */\n private async exists(objectName: string): Promise<boolean> {\n try {\n await this.downloadable(objectName).getMetadata();\n return true;\n } catch (err) {\n if (isNotFound(err)) {\n await this.presence.confirm();\n return false;\n }\n throw this.mapError(scrubCredentials(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 // A listing names no object, so its 404 can only be the bucket's.\n if (isNotFound(err)) throw missingBucketError(this.bucket, scrubCredentials(err));\n throw this.mapError(err);\n }\n for (const f of files) {\n const generation = parseGenerationFromKey(prefix, f.name);\n if (generation !== null) {\n yield { namespace: ref.namespace, segment: ref.segment, generation };\n }\n }\n }\n\n /** A read's error, once a 404 is known to be the object's and not the bucket's ({@link BucketPresence}). */\n private async readFailure(err: unknown, key: GenKey): Promise<unknown> {\n if (isNotFound(err)) await this.presence.confirm();\n return this.mapReadError(err, key);\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 (errorKind(err) === 'ValidationError') 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, is transient whichever code Node gave it:\n // `isTransient` takes every transport fault.\n return this.mapError(err);\n }\n\n /**\n * Reclassify a transient GCS fault (throttle/5xx/dropped connection) as a retryable {@link TransientError}, so the\n * store's read retry can ride it out and a write's caller can tell it from a deterministic failure, and a missing\n * bucket as a `ValidationError` that names it; everything else propagates unchanged. The final fallback at every\n * client-call site: an HTTP status and a fault in transit arrive typed, and anything else (a checksum mismatch, a\n * credentials failure) as the SDK raised it.\n */\n private mapError(err: unknown): unknown {\n const scrubbed = asMissingBucket(scrubCredentials(err), this.bucket);\n return toTransient(scrubbed, isTransient, transientFaultMessage);\n }\n}\n\ntype GcsFile = ReturnType<ReturnType<Storage['bucket']>['file']>;\n\n/** A tail read, with the version it was read at when there is one. */\nfunction withVersion(\n tail: { bytes: Uint8Array; size: number },\n version: string | undefined,\n): TailRead {\n return version === undefined ? tail : { ...tail, version };\n}\n\n/**\n * An object generation as GCS spells it, a positive decimal integer, or `undefined` for anything else: the version a\n * delete is conditioned on goes back to the service as a number, so one that does not parse as one is never reported.\n */\nfunction objectGeneration(value: string | undefined): string | undefined {\n return value !== undefined && /^[1-9]\\d*$/.test(value) && Number.isSafeInteger(Number(value))\n ? value\n : undefined;\n}\n\n/** The generation `ifGenerationMatch` takes, from a version this driver reported; any other is refused. */\nfunction generationOf(version: string, key: GenKey): number {\n if (objectGeneration(version) === undefined) {\n throw new ValidationError(\n `ifVersion for ${key.segment}.${key.generation} is not a GCS object generation: ${version}`,\n );\n }\n return Number(version);\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 /** The SDK's resumable upload under {@link stream}, which {@link abort} ends with an error: see there. */\n private upload: Writable | undefined;\n /** Set by {@link abort}: the held upload starts no request from then on. */\n private abandoned = false;\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 private readonly rng: Rng,\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 // A copy, here and below: the caller may reuse its buffer once this write resolves, and the stream or the\n // buffered upload still holds what it was given.\n if (!this.stream.write(Buffer.from(bytes))) await once(this.stream, 'drain');\n return;\n }\n this.buffered.push(new Uint8Array(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 this.holdUpload();\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);\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));\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(this.rng.next() * 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 /**\n * Keep the SDK's resumable upload, the writable its write stream pipes into, so {@link abort} can end its request.\n * The SDK hands it to the stream's duplex through `setWritable` in `startResumableUpload_`, a method of the `File`\n * its types declare; this wraps that method on this sink's own `File`, which no other write shares. Where the method\n * or the duplex's `setWritable` is missing, the SDK runs unchanged, nothing is kept, and an abort destroys the\n * stream alone, as it did before.\n */\n private holdUpload(): void {\n type Duplex = { setWritable?: (writable: Writable) => void };\n const file = this.file as unknown as {\n startResumableUpload_?: (dup: Duplex, options?: unknown) => void;\n };\n const start = file.startResumableUpload_;\n if (typeof start !== 'function') return;\n file.startResumableUpload_ = (dup, options) => {\n const setWritable = dup.setWritable;\n if (typeof setWritable === 'function') {\n dup.setWritable = (writable) => {\n this.hold(writable);\n setWritable.call(dup, writable);\n };\n }\n start.call(file, dup, options);\n };\n }\n\n /**\n * Keep `upload`, and let it start no request once this sink is abandoned. The SDK registers the listener that\n * aborts an upload's request only as that request starts, after the session is created, so an upload abandoned\n * before then would start its request anyway, with nothing left to end it. Each request that sends the upload's\n * bytes, its first and every retry, begins in `startUploading`, a public method of the SDK's `Upload`. On an\n * abandoned upload it resolves at once: the upload is destroyed already, and some of the SDK's callers do not\n * catch a rejection.\n */\n private hold(upload: Writable): void {\n this.upload = upload;\n const held = upload as Writable & { startUploading?: () => Promise<void> };\n const startUploading = held.startUploading;\n if (typeof startUploading !== 'function') return;\n held.startUploading = () => (this.abandoned ? Promise.resolve() : startUploading.call(held));\n }\n\n /**\n * End an upload that will not be committed. Destroying the write stream alone leaves the resumable upload's request\n * open: the SDK destroys its upload with no error, aborts the request only when the upload errors, and its request\n * body waits for bytes that never come, so the socket stays open and the process alive until the service drops the\n * connection. So the upload is destroyed with an error first, which aborts its request; the body was never ended, so\n * no object is finalized. An upload whose request has not started yet starts none: see {@link hold}. The error is\n * the sink's own and reaches no caller.\n */\n async abort(): Promise<void> {\n this.abandoned = true;\n const abandoned = new Error('the upload was abandoned before its object was committed');\n if (this.upload !== undefined && !this.upload.destroyed) this.upload.destroy(abandoned);\n if (this.stream !== undefined && !this.stream.destroyed) this.stream.destroy(abandoned);\n }\n}\n", "/**\n * Whether the bucket a driver names exists, asked when a request first answers 404.\n *\n * GCS answers a missing bucket and a missing object with the same status and reason (`404 notFound`), and a\n * GCS-compatible server may not even word them apart. Read as a missing object, a misnamed or deleted bucket would\n * answer every read as an empty segment. An object listing tells them apart: it names no object, so its 404 can only\n * be the bucket's.\n *\n * ```\n * a request answers 404 \u2500\u2500\u25B6 confirm() \u2500\u2500\u25B6 bucket seen before? \u2500\u2500 yes \u2500\u2500\u25B6 the 404 is an object's\n * \u2502 no\n * \u25BC\n * list one object in the bucket\n * \u251C\u2500 answered \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25B6 remember the bucket; the 404 is an object's\n * \u251C\u2500 404 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25B6 reject: ValidationError, the bucket does not exist\n * \u251C\u2500 transient \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25B6 reject: TransientError (asked again next time)\n * \u251C\u2500 403 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25B6 remember it as unknowable; the 404 is an object's\n * \u2514\u2500 anything else \u2500\u2500\u2500\u2500\u2500\u25B6 the 404 is an object's (asked again next time)\n * ```\n *\n * A process pays one listing at most while its bucket exists, and concurrent 404s share it. An identity without\n * `storage.objects.list` is answered `403`, which says nothing about the bucket, so its 404s read as they always did.\n * Any other failure (a credential that could not be fetched, a 401) says nothing either, but may not last, so it is\n * not remembered. Once seen, the bucket is not asked about again: one deleted later in the process's life reads as an\n * empty store, as it did before this check, while every write to it fails.\n */\n\nimport { TransientError } from '@cloudbitmaps/core/driver-kit';\nimport type { Storage } from '@google-cloud/storage';\nimport { isTransient, missingBucketError } from './gcs-errors';\nimport { scrubCredentials } from './scrub-error';\n\nexport class BucketPresence {\n private settled = false;\n private asking: Promise<void> | undefined;\n\n constructor(\n private readonly storage: Storage,\n private readonly bucket: string,\n ) {}\n\n /** Resolve when a 404 can be read as an object's; reject with `ValidationError` when the bucket does not exist, `TransientError` when the listing failed in transit. */\n async confirm(): Promise<void> {\n if (this.settled) return;\n this.asking ??= this.ask().finally(() => {\n this.asking = undefined;\n });\n return this.asking;\n }\n\n private async ask(): Promise<void> {\n let failure: { raised: unknown } | undefined;\n try {\n await this.storage.bucket(this.bucket).getFiles({ maxResults: 1, autoPaginate: false });\n } catch (raised) {\n failure = { raised };\n }\n if (failure !== undefined) {\n // The cause is the credential-free copy, never the SDK's error as raised.\n const err = scrubCredentials(failure.raised);\n const status = (err as { code?: unknown } | null)?.code;\n if (status === 404) {\n throw missingBucketError(this.bucket, err);\n }\n if (isTransient(err)) {\n throw new TransientError(\n `transient GCS fault confirming the bucket exists: ${String(status)}`,\n {\n cause: err,\n },\n );\n }\n // Refused or otherwise unanswerable: nothing is known about the bucket, so a 404 reads as an object's as it did\n // before this check. Only a refusal (no list permission) is sure to answer the same again, so only it is kept.\n if (status !== 403) return;\n }\n this.settled = true;\n }\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 */\nimport {\n ValidationError,\n isTransportFault as isNodeTransportFault,\n} from '@cloudbitmaps/core/driver-kit';\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), in a bucket that does. */\nexport function isNotFound(err: unknown): boolean {\n return httpStatus(err) === 404 && !isMissingBucket(err);\n}\n\n/**\n * The bucket itself does not exist: a wiring fault, never an absent object. Read as one, a misnamed or deleted bucket\n * would answer every read as an empty segment. GCS gives both the same status and reason (`404 notFound`), so only its\n * message tells them apart: on a download the message is the response body as it came, JSON or text, and elsewhere\n * the message and each `errors[].message` are GCS's own. A wording this does not match reads as an absent object.\n */\nexport function isMissingBucket(err: unknown): boolean {\n if (httpStatus(err) !== 404) return false;\n const e = err as { message?: unknown; errors?: unknown };\n const says = (m: unknown): boolean => typeof m === 'string' && /bucket does not exist/i.test(m);\n if (says(e.message)) return true;\n return (\n Array.isArray(e.errors) &&\n e.errors.some((inner) => says((inner as { message?: unknown } | null)?.message))\n );\n}\n\n/**\n * The {@link ValidationError} a missing bucket is: it names the bucket and what to check, and carries the service's\n * error as its `cause`. A wiring fault is not an absent object, so it is not a `NotFoundError`: a read re-resolves on\n * that, and would treat a misnamed bucket as a miss it can heal.\n */\nexport function missingBucketError(bucket: string, cause: unknown): ValidationError {\n return Object.assign(\n new ValidationError(\n `the GCS bucket does not exist: ${bucket}; check the bucket name and the project the client is built for`,\n ),\n { cause },\n );\n}\n\n/** `err`, or {@link missingBucketError} when `err` says the bucket does not exist. */\nexport function asMissingBucket(err: unknown, bucket: string): unknown {\n return isMissingBucket(err) ? missingBucketError(bucket, err) : err;\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 * A connection that failed or was cut off: one of the codes Node gives every driver's connection faults (refused,\n * reset, aborted, timed out, a DNS failure, an unreachable host or network, a broken pipe), or a chunked body cut off\n * mid-stream (`ERR_STREAM_PREMATURE_CLOSE`), which the GCS download path reports. A file the credentials name that is\n * missing or unreadable (`ENOENT`, `EACCES`) and a TLS failure (`EPROTO`, a certificate error) carry codes of the same\n * shape, and would fail the same way again, so they are not.\n */\nfunction isConnectionFault(err: unknown): boolean {\n return isNodeTransportFault(err) || networkCode(err) === 'ERR_STREAM_PREMATURE_CLOSE';\n}\n\n/** A download that failed in transit rather than with an HTTP status: a connection fault ({@link isConnectionFault}). */\nexport function isTransportFault(err: unknown): boolean {\n return httpStatus(err) === undefined && isConnectionFault(err);\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 // The same connection faults a download is retried on: one list, so a write and a read cannot disagree on one.\n return isConnectionFault(err);\n}\n\n/**\n * The message a transient GCS fault is handed on with: `transient GCS fault: <code>`, the HTTP status the SDK puts on\n * `code` or Node's connection code, else the error's name.\n */\nexport function transientFaultMessage(err: unknown): string {\n const e = err as { code?: unknown; name?: unknown } | null | undefined;\n return `transient GCS fault: ${String(e?.code ?? e?.name ?? 'unknown')}`;\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 WriteConflictError,\n normalizeObjectPrefix,\n requireBool,\n toTransient,\n} from '@cloudbitmaps/core/driver-kit';\nimport type { ObjectRegistryStore, ObjectRow } from '@cloudbitmaps/core/driver-kit';\nimport type { Storage } from '@google-cloud/storage';\nimport {\n asMissingBucket,\n missingBucketError,\n isNotFound,\n isPreconditionFailed,\n isTransient,\n transientFaultMessage,\n} from './gcs-errors';\nimport { BucketPresence } from './bucket-presence';\nimport { crc32cBase64 } from './crc32c';\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\ninterface 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. 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 private readonly presence: BucketPresence;\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 this.presence = new BucketPresence(storage, bucket);\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)) {\n await this.presence.confirm(); // a missing bucket is an error, not an absent row\n return null;\n }\n throw mapReadError(err, this.bucket);\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 // The checksum goes with the upload, for GCS to check before it stores anything. The SDK's own check runs\n // after the upload instead, and on a mismatch, or an answer that names no checksum, deletes the object by\n // name with no precondition: for a row, that is the live row, another writer's newer one included.\n validation: false,\n metadata: { crc32c: crc32cBase64(body) },\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 // A row write's 404 can be the bucket's: settle it before it is passed on.\n if (isNotFound(err)) await this.presence.confirm();\n throw mapError(err, this.bucket);\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. The registry re-reads and finds the row gone: an unfenced delete then\n // returns, and one fenced on a token reports the conflict, which its caller settles by reading the row (the\n // retention sweep does).\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 if (isNotFound(err)) await this.presence.confirm();\n throw new WriteConflictError(`registry OCC conflict deleting ${key}`);\n }\n throw mapError(err, this.bucket);\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 // A listing names no object, so its 404 can only be the bucket's.\n if (isNotFound(err)) throw missingBucketError(this.bucket, scrubCredentials(err));\n throw mapError(err, this.bucket);\n }\n for (const f of files ?? []) yield f.name;\n // A page that hands back the token it was asked with would be asked for again, forever.\n if (next?.pageToken !== undefined && next.pageToken === pageToken) {\n throw new IntegrityError('GCS listing returned the page token it was given');\n }\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/**\n * A read's error: a connection that failed or was cut off is transient too, after the driver's retries, whichever code\n * Node gave it, as `isTransient` takes every transport fault.\n */\nfunction mapReadError(err: unknown, bucket: string): 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 return mapError(err, bucket);\n}\n\n/**\n * Reclassify a transient GCS fault as a retryable {@link TransientError} and a missing bucket as a `ValidationError`;\n * any other error is passed through.\n */\nfunction mapError(err: unknown, bucket: string): unknown {\n return toTransient(\n asMissingBucket(scrubCredentials(err), bucket),\n isTransient,\n transientFaultMessage,\n );\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 requireBool('conditionalDelete', options.conditionalDelete) ?? false,\n ),\n normalizeObjectPrefix(options.prefix),\n options.now ?? ((): number => Date.now()),\n );\n }\n}\n", "/**\n * CRC32C (Castagnoli) of some bytes, in the form GCS takes in an object's `crc32c` metadata: the 4-byte big-endian\n * value, base64-encoded. Computed here rather than through the SDK so it is the same on every major the driver accepts.\n */\n\nconst TABLE = (() => {\n const table = new Uint32Array(256);\n for (let n = 0; n < 256; n++) {\n let c = n;\n for (let k = 0; k < 8; k++) c = c & 1 ? 0x82f63b78 ^ (c >>> 1) : c >>> 1;\n table[n] = c >>> 0;\n }\n return table;\n})();\n\nexport function crc32cBase64(bytes: Uint8Array): string {\n let crc = 0xffffffff;\n for (const byte of bytes) crc = TABLE[(crc ^ byte) & 0xff]! ^ (crc >>> 8);\n const value = (crc ^ 0xffffffff) >>> 0;\n const out = Buffer.alloc(4);\n out.writeUInt32BE(value);\n return out.toString('base64');\n}\n", "/**\n * The client a GCS backend sends its downloads on: one that never retries a download itself.\n *\n * `@google-cloud/storage` 8.x, and 7.x before it, retry a failed download by default, and when the retried request succeeds they\n * throw `ERR_STREAM_UNABLE_TO_PIPE` outside any promise, which ends the process whatever the caller wrote around the\n * call. So a download is sent once, and the driver retries it itself (`download-retry.ts`). The other requests keep the\n * client as it was built: a resumable upload's session, a listing, a delete (the conditional writes are sent once by\n * `send-once.ts` either way).\n *\n * ```\n * the client\n * \u2502\n * \u251C\u2500\u2500 not a Storage client (a test double) \u2500\u2500\u25B6 its retries off? yes: used as it is; no: refused\n * \u251C\u2500\u2500 its `bucket` replaced on the instance (a test stub), or overridden by its class \u2500\u2500\u25B6 refused\n * \u25BC\n * a twin, from the client's own class, with:\n * the same credentials object (one token cache), endpoint, universe, project, user agent, timeout, checksum\n * generator and retry settings but `autoRetry: false`, and one interceptor that applies the client's own\n * interceptors as they are when each request is sent (a later one too); then its own retry settings turned off,\n * over any its class set\n * \u2502\n * \u25BC\n * a second client, with a `bucket` method, the same credentials \u2500\u2500 yes \u2500\u2500\u25B6 downloads use the twin\n * object, retry settings of its own and off, the same URL and endpoint?\n * \u2502 no, or it could not be built\n * \u25BC\n * refused at construction, saying what differed\n * ```\n *\n * A `Storage` client is never used as it is, even one whose retries read off: the SDK turns a client's `autoRetry` off\n * while a delete or an upload with no precondition is in flight and back on when it ends, so that value, read once,\n * does not say how the client was built. The twin's retry settings are its own object, and only reads are sent on it,\n * which never change them; the SDK reads that object on every request.\n *\n * The twin is built from the client's own class, not this package's import: a caller on another major of the SDK hands\n * in a client whose credentials object is another copy's class, which this package's class would wrap in a second one.\n */\nimport { ValidationError } from '@cloudbitmaps/core/driver-kit';\nimport type { Storage, StorageOptions } from '@google-cloud/storage';\nimport { scrubCredentials } from './scrub-error';\n\n/** An interceptor's request hook, as the client's constructor takes one; `getRequestInterceptors` types them `Function`. */\ntype Intercept = NonNullable<StorageOptions['interceptors_']>[number]['request'];\n\n/**\n * A twin of `client` that sends each download once, or `client` itself when it is a test double whose retries are off.\n * `supplied`: the caller handed `client` in, rather than the backend building it.\n */\nexport function downloadClient(client: Storage, supplied: boolean): Storage {\n // A test double has nothing a twin could copy, and is used as it is when it says its retries are off.\n if (!isSdkClient(client)) {\n if (client.retryOptions?.autoRetry === false) return client;\n throw supplied\n ? refusal(\n true,\n 'it is not a `Storage` client (a test double, say)',\n 'Give it `retryOptions: { autoRetry: false }` to have it used as it is',\n )\n : refusal(\n false,\n \"the `Storage` it was built from is not the SDK's (a mocked module, say)\",\n 'Pass the mock as `client`, with `retryOptions: { autoRetry: false }`',\n );\n }\n const instead =\n 'Stub `Storage.prototype.bucket` instead, which reaches that client too, or pass a test double that does not ' +\n 'extend `Storage`, with `retryOptions: { autoRetry: false }`';\n if (Object.hasOwn(client, 'bucket')) {\n throw refusal(\n supplied,\n 'its `bucket` method is replaced on the instance, as by a test stub, which a client built from its class would ' +\n 'not have',\n instead,\n );\n }\n if (overridesBucket(client)) {\n throw refusal(\n supplied,\n 'its class overrides `bucket`, as a test double built on `Storage` does, so a client built from its class need ' +\n 'not hold what this one holds',\n instead,\n );\n }\n const second = supplied\n ? 'a second client built from its class with its settings'\n : 'a second client built from the same settings';\n const remedy = supplied\n ? 'Pass a client its class builds from the options it is given, as `Storage` does, or leave out `client`'\n : undefined;\n let twin: Storage;\n try {\n twin = twinOf(client);\n } catch (err) {\n throw refusal(supplied, `${second} could not be built`, remedy, err);\n }\n const why = unfaithful(client, twin);\n if (why !== undefined) throw refusal(supplied, `${second} ${why}`, remedy);\n return twin;\n}\n\n/**\n * Whether `client` is the SDK's own: its constructor sets `authClient` from the request factory it builds. A mock that\n * answers every property with a function, as an auto-mocking library's does, answers that one too, but not with the\n * same credentials object.\n */\nfunction isSdkClient(client: Storage): boolean {\n const factory: unknown = (client as { makeAuthenticatedRequest?: unknown })\n .makeAuthenticatedRequest;\n return (\n client.authClient !== undefined &&\n typeof factory === 'function' &&\n (factory as { authClient?: unknown }).authClient === client.authClient\n );\n}\n\n/** Whether a subclass of the SDK's client defines `bucket` over the SDK's own. */\nfunction overridesBucket(client: Storage): boolean {\n let defined = 0;\n for (let proto: unknown = Object.getPrototypeOf(client); proto !== null;) {\n if (Object.hasOwn(proto as object, 'bucket')) defined++;\n proto = Object.getPrototypeOf(proto);\n }\n return defined > 1;\n}\n\nfunction twinOf(client: Storage): Storage {\n const Own = client.constructor as new (options: StorageOptions) => Storage;\n const twin = new Own({\n authClient: client.authClient as StorageOptions['authClient'],\n // The SDK treats an endpoint spelled other than its default as a custom one, whose requests go without credentials,\n // and then stores it with any trailing slash taken off. A client given the default in another spelling is custom\n // with the default's URL, which that URL with a slash reproduces.\n apiEndpoint: client.customEndpoint === true ? `${client.apiEndpoint}/` : client.apiEndpoint,\n universeDomain: client.universeDomain,\n projectId: client.projectId,\n ...(client.useAuthWithCustomEndpoint === undefined\n ? {}\n : { useAuthWithCustomEndpoint: client.useAuthWithCustomEndpoint }),\n ...(client.providedUserAgent === undefined ? {} : { userAgent: client.providedUserAgent }),\n ...(client.timeout === undefined ? {} : { timeout: client.timeout }),\n crc32cGenerator: client.crc32cGenerator,\n retryOptions: { ...client.retryOptions, autoRetry: false },\n // A view, not a copy: the client's interceptors as they are when each request is sent.\n interceptors_: [\n {\n request: (options: Parameters<Intercept>[0]) =>\n (client.getRequestInterceptors() as Intercept[]).reduce(\n (sent, intercept) => intercept(sent),\n options as ReturnType<Intercept>,\n ),\n },\n ],\n });\n // A subclass may set retry settings of its own over the ones it is given. Turned off here, as the SDK turns them off\n // for a request it must not retry, on the twin's own settings only: never on an object the client holds too.\n if (\n twin !== client &&\n twin.retryOptions !== undefined &&\n twin.retryOptions !== client.retryOptions\n ) {\n twin.retryOptions.autoRetry = false;\n }\n return twin;\n}\n\n/**\n * What a twin did not take of the settings that decide where and how a download is sent, or `undefined`. A subclass may\n * build from options of its own, and would send downloads with other credentials, with the SDK's retries, or elsewhere.\n * Interceptors are carried as a view and not checked.\n */\nfunction unfaithful(client: Storage, twin: Storage): string | undefined {\n if (twin === client) return 'is the same client';\n if (typeof twin.bucket !== 'function') return 'has no `bucket` method';\n if (twin.authClient === undefined || twin.authClient !== client.authClient) {\n return 'would use other credentials';\n }\n if (twin.retryOptions === client.retryOptions) return 'would share its retry settings';\n if (twin.retryOptions?.autoRetry !== false) return \"keeps the SDK's retries\";\n // `baseUrl` prefers STORAGE_EMULATOR_HOST over `apiEndpoint` as each client is built.\n if (twin.baseUrl !== client.baseUrl) {\n return 'would send to another URL, as when `STORAGE_EMULATOR_HOST` changed after the client was built or is not a URL';\n }\n if (twin.apiEndpoint !== client.apiEndpoint) return 'would name another endpoint';\n if (twin.customEndpoint !== client.customEndpoint) {\n return 'would decide otherwise whether requests carry credentials';\n }\n return undefined;\n}\n\nfunction refusal(\n supplied: boolean,\n why: string,\n remedy: string | undefined,\n cause?: unknown,\n): ValidationError {\n const err = new ValidationError(\n (supplied\n ? `GcsStorage cannot send the supplied client's downloads without the SDK's retries: ${why}`\n : `GcsStorage cannot build a client that sends downloads without the SDK's retries: ${why}`) +\n (remedy === undefined ? '' : `. ${remedy}`),\n );\n // The credential-free copy, never the SDK's error as raised.\n if (cause !== undefined) err.cause = scrubCredentials(cause);\n return err;\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["ValidationError", "normalizeObjectPrefix", "TransientError", "ValidationError", "once", "ValidationError", "ValidationError", "TransientError", "once", "TransientError", "WriteConflictError", "normalizeObjectPrefix", "requireBool", "toTransient", "WriteConflictError", "TransientError", "toTransient", "requireBool", "normalizeObjectPrefix", "ValidationError", "defined", "ValidationError", "ValidationError", "normalizeObjectPrefix"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbitmaps/gcs",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "description": "Google Cloud Storage for CloudBitmaps",
5
5
  "keywords": [
6
6
  "cloudbitmaps",
@@ -49,8 +49,8 @@
49
49
  "access": "public"
50
50
  },
51
51
  "dependencies": {
52
- "@google-cloud/storage": "^7 || ^8",
53
- "@cloudbitmaps/core": "0.23.0"
52
+ "@google-cloud/storage": "^8.1.0",
53
+ "@cloudbitmaps/core": "0.24.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/node": "^22.0.0",