@cloudbitmaps/gcs 0.22.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 +27 -22
- package/dist/backend.d.ts +31 -5
- package/dist/index.js +357 -163
- package/dist/index.js.map +4 -4
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -14,20 +14,14 @@ second service to run.
|
|
|
14
14
|
pnpm add @cloudbitmaps/roaring @cloudbitmaps/gcs
|
|
15
15
|
```
|
|
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
|
-
-
|
|
20
|
-
- **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`.
|
|
21
|
-
- **pnpm 10:** add `{ "pnpm": { "onlyBuiltDependencies": ["roaring"] } }` to your `package.json`. Without it the install exits 0 and the package throws at `import`.
|
|
22
|
-
- **pnpm 9:** nothing extra.
|
|
23
|
-
|
|
24
|
-
[Details](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/getting-started.md#cannot-find-module-buildreleaseroaringnode-after-a-successful-install).
|
|
25
|
-
|
|
26
|
-
`@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.
|
|
27
20
|
`@cloudbitmaps/core`, the engine underneath, arrives with it and you never name it.
|
|
28
21
|
|
|
29
22
|
## Use
|
|
30
23
|
|
|
24
|
+
<!-- typecheck -->
|
|
31
25
|
```ts
|
|
32
26
|
import { CloudRoaring } from '@cloudbitmaps/roaring';
|
|
33
27
|
import { GcsStorage } from '@cloudbitmaps/gcs';
|
|
@@ -56,12 +50,20 @@ It builds its own client from Application Default Credentials. Any other key is
|
|
|
56
50
|
|
|
57
51
|
- **This backend is outside the validated envelope.** It passes the same conformance suites as S3, but the proven
|
|
58
52
|
scale and the calibration were done on S3. You are an early user.
|
|
53
|
+
- **Run `await store.preflight()` once before the first load.** It writes, tries to write again, reads, lists and deletes one probe
|
|
54
|
+
object, and reads the registry and the bucket's lifecycle rules (`storage.buckets.get` for the last), and answers
|
|
55
|
+
`{ ok, checks }`. It does not check a read after a delete or versioning, among others: [the full list](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/production.md#what-preflight-does-not-check). A first run is eight requests here.
|
|
59
56
|
- **Grant the bucket's object permissions.** The library creates, reads, lists and deletes objects:
|
|
60
57
|
`roles/storage.objectAdmin` covers all four.
|
|
61
58
|
- **The write-once guarantee rides GCS preconditions** (`ifGenerationMatch`), so nothing else needs enabling.
|
|
62
59
|
- **Never add a lifecycle rule that expires current objects or the `registry/` prefix.**
|
|
63
|
-
- **
|
|
64
|
-
|
|
60
|
+
- **Check the bucket's lifecycle rules with `await backend.checkLifecycle()`.** It reads the bucket's metadata with one
|
|
61
|
+
request (bounded by `readTimeoutMs`, when set) and answers `problem` for a `Delete` rule that applies to live objects and can match your prefix, `ok`, or
|
|
62
|
+
`unchecked`. It needs `storage.buckets.get`, which a load never uses, `store.preflight()`'s lifecycle step does, and `roles/storage.objectAdmin` does
|
|
63
|
+
not include: without it the answer is `unchecked`, naming the permission
|
|
64
|
+
([check the lifecycle rules from code](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/production.md#check-the-lifecycle-rules-from-code)).
|
|
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
|
|
65
67
|
with `ERR_STREAM_UNABLE_TO_PIPE`, thrown outside any promise, even though the retried request succeeded. The client
|
|
66
68
|
`GcsStorage` builds therefore sends each download once, and the driver runs a download again itself, up to three more
|
|
67
69
|
times with backoff, after a connection fault (refused, reset, timed out, a DNS failure, a body cut off) or a 408, 429,
|
|
@@ -94,8 +96,9 @@ It builds its own client from Application Default Credentials. Any other key is
|
|
|
94
96
|
- **A read that times out before its server answers leaves its connection open.** The SDK cannot cancel a request
|
|
95
97
|
whose response has not begun, so that request holds its connection until the server answers or closes it, and until
|
|
96
98
|
then keeps a Node process from exiting: one per driver read, up to four per call through the store's retry, so an
|
|
97
|
-
outage at N reads a second adds about 4N held connections each second. On
|
|
98
|
-
began closes its own; on 7.x (checked on 7.22.0)
|
|
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
|
|
99
102
|
deadline is reported as a `TransientError`, not `NotFoundError`.
|
|
100
103
|
- **Downloads go out on Node's global agent, so one the driver cuts off or refuses resets nothing else.** When a
|
|
101
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
|
|
@@ -105,15 +108,17 @@ It builds its own client from Application Default Credentials. Any other key is
|
|
|
105
108
|
global agent closes a connection idle for 5 seconds (the SDK's own pool does not), so a read after a longer pause
|
|
106
109
|
opens a new connection, with its TCP and TLS handshake, and downloads share that agent with any other `http` or
|
|
107
110
|
`https` request in the process. Raising `https.globalAgent.options.timeout` keeps idle connections longer.
|
|
108
|
-
- **A transient failure of a write throws `TransientError`, and the write may or may not have landed.** Re-run the
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
store's clock
|
|
111
|
+
- **A transient failure of a write throws `TransientError`, and the write may or may not have landed.** Re-run the call,
|
|
112
|
+
or check `store.generations(ref)`. The driver [sends each registry write
|
|
113
|
+
once](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/correctness.md#sdk-retry); a load that gets no
|
|
114
|
+
answer [reads the row and sends a fresh compare-and-swap from
|
|
115
|
+
it](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/correctness.md#row-write-settled), at most three
|
|
116
|
+
times. A generation's object of up to `simpleUploadThresholdBytes` is sent again, up to three more times, only after a
|
|
117
|
+
`429` or `503`; every object carries a random id in its metadata, so a first send that landed is told from another
|
|
118
|
+
writer's object. A load whose row writes all go unanswered throws `TransientError` and keeps its object. The waits
|
|
119
|
+
before an object's re-sends use real timers: the store's injected clock does not reach them, and `GcsStorage` takes no
|
|
120
|
+
clock, so a test that throttles one object write three times waits about 3.5 s at most (the publish's waits before a
|
|
121
|
+
fresh row write do use the store's clock).
|
|
117
122
|
|
|
118
123
|
The [production checklist](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/production.md) covers each item, and the ones every backend shares: backups of the data and the registry, and a schedule for your loads.
|
|
119
124
|
- **The driver redacts the credentials it recognises from an error before it throws it.** The SDK keeps the response it
|
package/dist/backend.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { STORAGE_BACKEND } from '@cloudbitmaps/core/driver-kit';
|
|
2
|
-
import type { IRegistryDriver, IStorageDriver, StorageBackend } from '@cloudbitmaps/core/driver-kit';
|
|
2
|
+
import type { IRegistryDriver, IStorageDriver, LifecycleVerdict, StorageBackend } from '@cloudbitmaps/core/driver-kit';
|
|
3
3
|
import { Storage as GcsClient } from '@google-cloud/storage';
|
|
4
4
|
/**
|
|
5
5
|
* What {@link GcsStorage} takes: a `bucket`, and either a `client` or the two settings that build one (`projectId`,
|
|
@@ -8,13 +8,13 @@ import { Storage as GcsClient } from '@google-cloud/storage';
|
|
|
8
8
|
export interface GcsStorageOptions {
|
|
9
9
|
/** Target bucket (must already exist). */
|
|
10
10
|
readonly bucket: string;
|
|
11
|
-
/** Optional object-name prefix under which everything lives — generations and the registry alike. */
|
|
11
|
+
/** Optional object-name prefix under which everything lives — generations and the registry alike. A string, else `ValidationError`. */
|
|
12
12
|
readonly prefix?: string;
|
|
13
13
|
/**
|
|
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`
|
|
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
|
|
@@ -25,9 +25,15 @@ export interface GcsStorageOptions {
|
|
|
25
25
|
* built, other than an interceptor, is not carried to the twin.
|
|
26
26
|
*/
|
|
27
27
|
readonly client?: GcsClient;
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Project id for the client built when `client` is absent (refused beside `client`): a non-empty string, else
|
|
30
|
+
* `ValidationError`. Falls back to the SDK's own resolution.
|
|
31
|
+
*/
|
|
29
32
|
readonly projectId?: string;
|
|
30
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Endpoint override — point it at fake-gcs-server locally: a non-empty string, since an empty one would send
|
|
35
|
+
* requests to the public endpoint, else `ValidationError`. Refused beside `client`, which carries its own.
|
|
36
|
+
*/
|
|
31
37
|
readonly apiEndpoint?: string;
|
|
32
38
|
/** Largest object the backend will write and advertise (default = GCS's 5 TiB max). Must be a positive safe integer. */
|
|
33
39
|
readonly maxObjectBytes?: number;
|
|
@@ -88,5 +94,25 @@ export declare class GcsStorage implements StorageBackend {
|
|
|
88
94
|
readonly registry: IRegistryDriver;
|
|
89
95
|
/** The client both halves share — built here unless one was supplied. */
|
|
90
96
|
readonly client: GcsClient;
|
|
97
|
+
private readonly bucket;
|
|
98
|
+
/** The name prefix every object sits under: `''`, or `trimmed/`. */
|
|
99
|
+
private readonly keyPrefix;
|
|
100
|
+
private readonly readTimeoutMs;
|
|
101
|
+
/**
|
|
102
|
+
* Builds the GCS backend over one bucket, which `bucket` names. Pass `client`, or `projectId` and `apiEndpoint` to
|
|
103
|
+
* have one built, not both. `prefix` puts every object under a name prefix. Throws `ValidationError` for an unknown
|
|
104
|
+
* option, no bucket, a `now` that is not a function, a client setting beside `client`, a `maxObjectBytes` or
|
|
105
|
+
* `simpleUploadThresholdBytes` that is not a positive integer, a `conditionalDelete` that is not a boolean, an
|
|
106
|
+
* empty or non-string `apiEndpoint` or `projectId`, or an invalid `prefix` or `readTimeoutMs`.
|
|
107
|
+
*/
|
|
91
108
|
constructor(options: GcsStorageOptions);
|
|
109
|
+
/**
|
|
110
|
+
* Reads the bucket's metadata (one request, plus the SDK's own retries, bounded by `readTimeoutMs` when it is set) and
|
|
111
|
+
* reports a lifecycle rule with a `Delete` action (or one it does not know) that applies to live
|
|
112
|
+
* objects whose names can be this store's: generations and registry rows alike. Needs `storage.buckets.get`, which
|
|
113
|
+
* a load never uses (`store.preflight()`'s lifecycle step does) and `roles/storage.objectAdmin` does not include: without it the answer is
|
|
114
|
+
* `unchecked`, naming the permission. Never rejects; a `detail` carries no credential. Rule reading: see the
|
|
115
|
+
* production guide.
|
|
116
|
+
*/
|
|
117
|
+
checkLifecycle(): Promise<LifecycleVerdict>;
|
|
92
118
|
}
|