@cloudbitmaps/s3 0.22.0 → 0.23.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 +29 -13
- package/dist/backend.d.ts +42 -7
- package/dist/index.js +374 -153
- package/dist/index.js.map +4 -4
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ service to run.
|
|
|
14
14
|
pnpm add @cloudbitmaps/roaring @cloudbitmaps/s3
|
|
15
15
|
```
|
|
16
16
|
|
|
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
|
+
|
|
17
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:
|
|
18
20
|
|
|
19
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.
|
|
@@ -28,6 +30,7 @@ pnpm add @cloudbitmaps/roaring @cloudbitmaps/s3
|
|
|
28
30
|
|
|
29
31
|
## Use
|
|
30
32
|
|
|
33
|
+
<!-- typecheck -->
|
|
31
34
|
```ts
|
|
32
35
|
import { CloudRoaring } from '@cloudbitmaps/roaring';
|
|
33
36
|
import { S3Storage } from '@cloudbitmaps/s3';
|
|
@@ -49,12 +52,17 @@ It builds its own client from your usual AWS credentials. Any other key is refus
|
|
|
49
52
|
| `client` | your own `S3Client`; it carries its own region, endpoint and credentials |
|
|
50
53
|
| `region`, `endpoint`, `pathStyle`, `credentials` | build a client for you, such as one for MinIO; refused beside `client` |
|
|
51
54
|
| `maxSockets` | most sockets the client you do not pass opens at once, for `https` and plain `http` alike; a positive integer, default 128 (the SDK's own is 50; if a future SDK changes its handler, the built client keeps the SDK's own limit); refused beside `client` |
|
|
52
|
-
| `partBytes`, `maxObjectBytes` | multipart sizing: part size (default 8 MiB; the upload buffers one part at a time) and the largest object (about
|
|
55
|
+
| `partBytes`, `maxObjectBytes` | multipart sizing: part size (default 8 MiB; the upload buffers one part at a time and copies it into one body to send it, so it holds up to about twice the part size at a flush, 16 MiB at the default, derived from the driver's code) and the largest object (about 78 GiB by default, up to S3's 5 TiB). A cap above 5 TiB, or a part above S3's 5 GiB part limit, is a `ValidationError` |
|
|
53
56
|
| `readTimeoutMs` | how long one read (a `GetObject` or `HeadObject`, its body included) may take before it throws `TransientError` and the store retries it; `0`, the default, sets no timeout. Writes are not timed |
|
|
54
57
|
| `conditionalDelete` | whether the store applies `If-Match` on a `DeleteObject`: the registry then removes a deleted row rather than leaving a tombstone, and the storage half deletes an erasure's holder only while it is the object the erasure read, by its ETag. On by default when the host the client resolves is an AWS S3 host, however its endpoint was set (`endpoint`, `AWS_ENDPOINT_URL_S3`, `AWS_ENDPOINT_URL`, the shared config file); off for any other host: set it for an S3-compatible store only once you know it applies the header (MinIO ignores it) |
|
|
55
58
|
|
|
56
59
|
## Before production
|
|
57
60
|
|
|
61
|
+
- **Run `await store.preflight()` once before the first load.** It reads the registry, writes one probe object, writes the
|
|
62
|
+
same key again (which must be refused), reads it back, lists it, deletes it and checks the lifecycle rules, with
|
|
63
|
+
`s3:ListBucket`, `s3:GetObject`, `s3:PutObject` and `s3:DeleteObject`, and `s3:GetLifecycleConfiguration` for the last. It does
|
|
64
|
+
not check `s3:AbortMultipartUpload`, the registry's `If-Match`, or a read after a delete, among others: [the full list](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/production.md#what-preflight-does-not-check). It
|
|
65
|
+
shows an SDK of 3.640.0 or older as a `write-once` problem, and cannot see one from 3.641.0 to 3.699.0, so the floor below stays yours to check. The probe sits at `<prefix>cbm.due.preflight/`, outside your application's namespaces: a grant scoped per namespace shows as a `write` problem, and as a `registry` problem too when the registry grant is scoped per namespace.
|
|
58
66
|
- **The bucket must honor conditional writes**: `If-None-Match: *` for a write-once generation and `If-Match` for the
|
|
59
67
|
pointer. AWS S3 does, and the test suite runs against MinIO. Another S3-compatible service that ignores the headers
|
|
60
68
|
turns write-once into overwrite, so check yours before you depend on it. It must also be strongly consistent: a read
|
|
@@ -74,18 +82,26 @@ It builds its own client from your usual AWS credentials. Any other key is refus
|
|
|
74
82
|
`registry/` prefix. An expired registry row takes the segment's pointer with it, and the token every publish is fenced
|
|
75
83
|
on, so its generations look unreferenced; an expired generation makes a live segment unreadable
|
|
76
84
|
([bucket lifecycle](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/production.md#bucket-lifecycle)).
|
|
77
|
-
- **
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
- **Check the bucket's lifecycle rules with `await backend.checkLifecycle()`.** It reads the configuration with one
|
|
86
|
+
request (bounded by `readTimeoutMs`, when set) and answers `ok`, `problem` (an enabled, untagged rule over your prefix that expires current objects or moves
|
|
87
|
+
them to `GLACIER`, `DEEP_ARCHIVE` or a class it does not know) or `unchecked`. It needs `s3:GetLifecycleConfiguration`, which a load never uses and
|
|
88
|
+
`store.preflight()`'s lifecycle step does: without it the answer is `unchecked`, naming the permission. A missing abort-incomplete-upload rule is
|
|
89
|
+
advice in `detail`, never a `problem`
|
|
90
|
+
([check the lifecycle rules from code](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/production.md#check-the-lifecycle-rules-from-code)).
|
|
91
|
+
- **A transient failure of a write throws `TransientError`, and the write may or may not have landed.** Re-run the call,
|
|
92
|
+
or check `store.generations(ref)`. The SDK's own retry is off for conditional writes, so a write that landed is not
|
|
93
|
+
reported as a conflict. The driver [sends each registry write
|
|
94
|
+
once](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/correctness.md#sdk-retry); a load that gets no
|
|
95
|
+
answer [reads the row and sends a fresh compare-and-swap from
|
|
96
|
+
it](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/correctness.md#row-write-settled), at most three
|
|
97
|
+
times. A generation's object is sent again, up to three more times, only after a `503` (`SlowDown` is the usual one) or a bare `429` ([why that is
|
|
98
|
+
safe](https://github.com/cloudbitmaps/cloudbitmaps/blob/main/docs/guide/correctness.md#object-write-resent));
|
|
99
|
+
it carries a random id in its user metadata (`x-amz-meta-cbwid`), so a first send that landed is told from another
|
|
100
|
+
writer's object. A load whose row writes all go unanswered throws `TransientError` and keeps its object. The waits
|
|
101
|
+
before an object's re-sends use real timers: the store's injected clock does not reach them, and `S3Storage` takes no
|
|
102
|
+
clock, so a test that throttles one object write three times waits about 3.5 s at most (the publish's waits before a
|
|
103
|
+
fresh row write do use the store's clock). **A bare `429`** (AWS S3 does not send one; some S3-compatible services do)
|
|
104
|
+
**is a throttle, handled exactly as a `503` is**, and one that ends a call is a `TransientError`.
|
|
89
105
|
- **Reads can be timed, and writes are not.** Nothing is timed unless you set `readTimeoutMs`. Set, a read that has
|
|
90
106
|
not finished in that many ms throws `TransientError`, and the store runs it again; AWS's guidance is to retry a GET
|
|
91
107
|
of under 512 KB after about 2 seconds. The timer covers the body as well, so a connection that sends its headers and
|
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 { S3Client } from '@aws-sdk/client-s3';
|
|
4
4
|
import type { IMetricsSink } from '@cloudbitmaps/core/driver-kit';
|
|
5
5
|
/**
|
|
@@ -9,7 +9,7 @@ import type { IMetricsSink } from '@cloudbitmaps/core/driver-kit';
|
|
|
9
9
|
export interface S3StorageOptions {
|
|
10
10
|
/** Target bucket (must already exist). */
|
|
11
11
|
readonly bucket: string;
|
|
12
|
-
/** Optional key prefix under which everything lives — generations and the registry alike. */
|
|
12
|
+
/** Optional key prefix under which everything lives — generations and the registry alike. A string, else `ValidationError`. */
|
|
13
13
|
readonly prefix?: string;
|
|
14
14
|
/**
|
|
15
15
|
* A constructed client. Supply one for a credential chain the SDK cannot infer; otherwise one is built. Its retry
|
|
@@ -17,14 +17,27 @@ export interface S3StorageOptions {
|
|
|
17
17
|
* configured to do: a registry row once, and a generation's object again only after a throttle, under its own backoff.
|
|
18
18
|
*/
|
|
19
19
|
readonly client?: S3Client;
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Region for the client built when `client` is absent (refused beside `client`): a non-empty string, else
|
|
22
|
+
* `ValidationError`. Falls back to the SDK's own resolution.
|
|
23
|
+
*/
|
|
21
24
|
readonly region?: string;
|
|
22
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Endpoint for an S3-compatible store (MinIO, Ceph, R2): an http or https URL, since an empty one would send
|
|
27
|
+
* requests to AWS, else `ValidationError`. Refused beside `client`, which carries its own.
|
|
28
|
+
*/
|
|
23
29
|
readonly endpoint?: string;
|
|
24
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Path-style addressing, which most S3-compatible stores require: a boolean, else `ValidationError`. Refused beside
|
|
32
|
+
* `client`, which carries its own.
|
|
33
|
+
*/
|
|
25
34
|
readonly pathStyle?: boolean;
|
|
26
35
|
/**
|
|
27
|
-
* Static credentials, for the S3-compatible stores that issue them (MinIO, Ceph, R2)
|
|
36
|
+
* Static credentials, for the S3-compatible stores that issue them (MinIO, Ceph, R2): non-empty `accessKeyId` and
|
|
37
|
+
* `secretAccessKey` strings and an optional `sessionToken`, else `ValidationError`, which never prints a value. No other
|
|
38
|
+
* key is taken except `$source`, `credentialScope`, `accountId` and `expiration`, which are tolerated and not read:
|
|
39
|
+
* the SDK stamps `$source` on a credentials object it is given, so one used for two stores carries it, and a credential
|
|
40
|
+
* provider's result carries the others. A provider function is not taken here; put it on an `S3Client` given as `client`.
|
|
28
41
|
*
|
|
29
42
|
* On AWS itself, leave this unset — the SDK's own chain (instance role, SSO, environment, profile) is what
|
|
30
43
|
* you want, and hard-coding keys to reach it would be a downgrade. It exists because the alternative for a
|
|
@@ -48,7 +61,7 @@ export interface S3StorageOptions {
|
|
|
48
61
|
*/
|
|
49
62
|
readonly maxSockets?: number;
|
|
50
63
|
/**
|
|
51
|
-
* Largest object the backend will write and advertise. Default = `partBytes × 10,000` (≈
|
|
64
|
+
* Largest object the backend will write and advertise. Default = `partBytes × 10,000` (≈ 78 GiB at the default
|
|
52
65
|
* 8 MiB part) — the honest ceiling reachable within S3's 10,000-part limit. Set it higher and `partBytes`
|
|
53
66
|
* auto-grows so 10,000 parts still cover it (raising peak write memory to ~one part); up to the 5 TiB S3 max, and a
|
|
54
67
|
* larger cap, or a `partBytes` above S3's 5 GiB part limit, is refused with `ValidationError`.
|
|
@@ -128,10 +141,32 @@ export declare class S3Storage implements StorageBackend {
|
|
|
128
141
|
/** The client both halves share — built here unless one was supplied. */
|
|
129
142
|
readonly client: S3Client;
|
|
130
143
|
private readonly sockets;
|
|
144
|
+
private readonly bucket;
|
|
145
|
+
/** The key prefix every object sits under: `''`, or `trimmed/`. */
|
|
146
|
+
private readonly keyPrefix;
|
|
147
|
+
private readonly readTimeoutMs;
|
|
148
|
+
/**
|
|
149
|
+
* Builds the S3 backend over one bucket, which `bucket` names. Pass `client`, or `region`, `endpoint`, `pathStyle`,
|
|
150
|
+
* `credentials` and `maxSockets` to have one built, not both. `prefix` puts every object under a key prefix. Throws
|
|
151
|
+
* `ValidationError` for an unknown option, no bucket, a `now` that is not a function, a client setting beside
|
|
152
|
+
* `client`, a `maxSockets` (with no `client`) that is not a positive integer, a `partBytes` or `maxObjectBytes` that
|
|
153
|
+
* is not a positive integer, a `conditionalDelete` that is not a boolean, a `region` or `endpoint` that is empty or not a string, a `pathStyle` that
|
|
154
|
+
* is not a boolean, `credentials` that are not complete, or an invalid `prefix` or `readTimeoutMs`.
|
|
155
|
+
*/
|
|
131
156
|
constructor(options: S3StorageOptions);
|
|
132
157
|
/**
|
|
133
158
|
* Called by a store given a metrics sink. After the first read, a client whose socket pool is smaller than twice the
|
|
134
159
|
* default window (64) sends the sink one `advisory` event, once; see the production guide's socket sizing.
|
|
135
160
|
*/
|
|
136
161
|
attachMetrics(sink: IMetricsSink): void;
|
|
162
|
+
/**
|
|
163
|
+
* Reads the bucket's lifecycle configuration (one `GetBucketLifecycleConfiguration` request, plus the SDK's own
|
|
164
|
+
* retries, bounded by `readTimeoutMs` when it is set) and reports a rule that would expire, or move to GLACIER,
|
|
165
|
+
* DEEP_ARCHIVE or a class it does not know, the current objects under this store's prefix, generations and registry
|
|
166
|
+
* rows alike. Needs `s3:GetLifecycleConfiguration`, which a load never uses (`store.preflight()`'s
|
|
167
|
+
* lifecycle step does): without it the
|
|
168
|
+
* answer is `unchecked`, naming the permission. A missing rule that aborts incomplete multipart uploads is advice in
|
|
169
|
+
* `detail`, not a problem. Never rejects; a `detail` carries no credential. Rule reading: see the production guide.
|
|
170
|
+
*/
|
|
171
|
+
checkLifecycle(): Promise<LifecycleVerdict>;
|
|
137
172
|
}
|