@thailandhosting/object-storage 0.0.0-stage → 1.0.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 +177 -2
- package/dist/client.d.ts +260 -0
- package/dist/client.js +688 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +37 -0
- package/dist/errors.js +48 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/signing.d.ts +24 -0
- package/dist/signing.js +48 -0
- package/dist/signing.js.map +1 -0
- package/package.json +28 -4
package/README.md
CHANGED
|
@@ -1,3 +1,178 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ThailandHosting Object Storage — Node.js SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript/JavaScript client for ThailandHosting Object Storage: buckets,
|
|
4
|
+
objects, large files and share links over the ThailandHosting API.
|
|
5
|
+
|
|
6
|
+
```
|
|
7
|
+
npm install @thailandhosting/object-storage
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Node.js 18.17+. ESM with TypeScript types. No runtime dependencies: it uses
|
|
11
|
+
Node's built-in `fetch` (keep-alive connection pooling) and streams.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Client, NotFoundError } from '@thailandhosting/object-storage';
|
|
17
|
+
|
|
18
|
+
const client = new Client({
|
|
19
|
+
endpoint: 'https://objects.thailandhosting.com/th-bkk-1',
|
|
20
|
+
accessKeyId: process.env.TH_ACCESS_KEY_ID!,
|
|
21
|
+
secretAccessKey: process.env.TH_SECRET_ACCESS_KEY!,
|
|
22
|
+
});
|
|
23
|
+
const bucket = client.bucket('my-bucket');
|
|
24
|
+
|
|
25
|
+
// Any size: large files go up as parallel parts.
|
|
26
|
+
await bucket.uploadFile('backups/db.dump', '/var/backups/db.dump');
|
|
27
|
+
|
|
28
|
+
// Large objects come down as parallel ranges.
|
|
29
|
+
await bucket.downloadFile('backups/db.dump', '/tmp/db.dump');
|
|
30
|
+
|
|
31
|
+
// Small objects
|
|
32
|
+
await bucket.put('notes/hello.txt', 'สวัสดี', { metadata: { author: 'me' } });
|
|
33
|
+
console.log(await bucket.getText('notes/hello.txt'));
|
|
34
|
+
|
|
35
|
+
// Stream an upload of unknown length (an HTTP request, a pipe, a generated export)
|
|
36
|
+
await bucket.upload('exports/report.csv', someReadable);
|
|
37
|
+
|
|
38
|
+
// Stream a download
|
|
39
|
+
const dl = await bucket.get('videos/intro.mp4');
|
|
40
|
+
dl.toNodeStream().pipe(res);
|
|
41
|
+
|
|
42
|
+
// Every object under a prefix
|
|
43
|
+
for await (const obj of bucket.objects('backups/')) console.log(obj.key, obj.size);
|
|
44
|
+
|
|
45
|
+
// A link that works without a key for one hour
|
|
46
|
+
console.log((await bucket.createLink('backups/db.dump', { expiresIn: 3600 })).url);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await bucket.head('missing');
|
|
50
|
+
} catch (e) {
|
|
51
|
+
if (e instanceof NotFoundError) console.log(e.code); // object_not_found
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Create access keys in the Console (Object Storage → Access keys). Reuse one
|
|
56
|
+
`Client`: it keeps connections open. Every call takes an optional
|
|
57
|
+
`AbortSignal`.
|
|
58
|
+
|
|
59
|
+
## Authentication
|
|
60
|
+
|
|
61
|
+
Every request is signed with HTTP Message Signatures (RFC 9421,
|
|
62
|
+
HMAC-SHA256 keyed with the secret access key). The secret itself is never
|
|
63
|
+
sent: the request carries `Signature-Input` (covering the method, host, path,
|
|
64
|
+
query and, with a body, its `Content-Digest`) and `Signature`. Each attempt is
|
|
65
|
+
signed afresh, so the machine's clock must be within 5 minutes of the
|
|
66
|
+
service's. A client without a key sends no signature.
|
|
67
|
+
|
|
68
|
+
`signRequest` and `contentDigest` are exported for signing requests made
|
|
69
|
+
with your own HTTP client.
|
|
70
|
+
|
|
71
|
+
## Conditional writes
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { PreconditionFailedError } from '@thailandhosting/object-storage';
|
|
75
|
+
|
|
76
|
+
// Create only if the key is free (also on uploadFile / upload, where the
|
|
77
|
+
// condition is checked when a large upload's parts are joined).
|
|
78
|
+
await bucket.put('locks/job-42', owner, { ifNoneMatch: '*' });
|
|
79
|
+
|
|
80
|
+
// Replace only the version you read; delete only if unchanged.
|
|
81
|
+
const info = await bucket.head('config.json');
|
|
82
|
+
try {
|
|
83
|
+
await bucket.put('config.json', next, { ifMatch: info.etag });
|
|
84
|
+
} catch (e) {
|
|
85
|
+
if (e instanceof PreconditionFailedError) console.log('changed meanwhile'); // code precondition_failed
|
|
86
|
+
}
|
|
87
|
+
await bucket.delete('config.json', { ifMatch: info.etag });
|
|
88
|
+
|
|
89
|
+
// Reads: NotModifiedError when the cached copy is current.
|
|
90
|
+
await bucket.get('logo.png', { ifNoneMatch: cachedETag });
|
|
91
|
+
await bucket.head('logo.png', { ifModifiedSince: cachedAt });
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The service checks `ifMatch` / `ifNoneMatch` atomically with the write. A
|
|
95
|
+
failed condition is `PreconditionFailedError` (HTTP 412) and is never retried.
|
|
96
|
+
|
|
97
|
+
## Public buckets and folders
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// Anyone may read images/ (and nothing else) without a key; needs a key
|
|
101
|
+
// allowed to manage buckets. [] makes the bucket private again.
|
|
102
|
+
await bucket.setPublicAccess([{ prefix: 'images/' }]);
|
|
103
|
+
console.log(bucket.publicUrl('images/logo.png'));
|
|
104
|
+
// https://objects.thailandhosting.com/th-bkk-1/my-bucket/images/logo.png
|
|
105
|
+
|
|
106
|
+
// A client without a key reads (and, where allowed, lists) public objects.
|
|
107
|
+
const anon = new Client({ endpoint: 'https://objects.thailandhosting.com/th-bkk-1' });
|
|
108
|
+
const logo = await anon.bucket('my-bucket').getBytes('images/logo.png');
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
What a key may do (buckets, folders, read / list / write / delete,
|
|
112
|
+
managing buckets, an expiry date) is set on the key in the Console.
|
|
113
|
+
|
|
114
|
+
## Performance
|
|
115
|
+
|
|
116
|
+
| | |
|
|
117
|
+
|---|---|
|
|
118
|
+
| Connections | Node's `fetch` (undici): keep-alive pooling, no per-request handshake |
|
|
119
|
+
| Uploads | `uploadFile` reads parts with positional reads and sends them in parallel; `upload` streams any `Readable` / `ReadableStream` / async iterable, holding at most `concurrency + 1` parts in memory |
|
|
120
|
+
| Downloads | `downloadFile` fetches parallel ranges and writes each straight to its place in the file; `get` streams |
|
|
121
|
+
| Small data | one request; a stream that turns out small is one request too |
|
|
122
|
+
| Tuning | `partSize` (default 16 MiB), `concurrency` (default 8), `multipartThreshold` (default 32 MiB) |
|
|
123
|
+
|
|
124
|
+
## Reliability
|
|
125
|
+
|
|
126
|
+
- Requests that are safe to repeat are retried with exponential backoff and
|
|
127
|
+
jitter (`maxRetries`, default 4) on 429, 500, 502, 503, 504 and network
|
|
128
|
+
errors, waiting at least as long as `Retry-After` asks.
|
|
129
|
+
- Every request body (each part, and JSON) carries a `Content-Digest`
|
|
130
|
+
(SHA-256); the service checks it as the bytes arrive and refuses a body
|
|
131
|
+
that changed on the way (`bad_digest`, not retried). Streams are cut into
|
|
132
|
+
parts in memory, so their digest is known before they are sent.
|
|
133
|
+
- Every single upload and every part is checked against the MD5 the service
|
|
134
|
+
reports; damaged data throws `IntegrityError` and is never accepted.
|
|
135
|
+
- An object uploaded in one request has a `Repr-Digest` (`info.digest`).
|
|
136
|
+
Whole-object downloads (`getBytes`, `getText`, `get` without a range,
|
|
137
|
+
`downloadFile` including its parallel ranges) are checked against it and
|
|
138
|
+
throw `IntegrityError` if the bytes differ. Multipart uploads have none.
|
|
139
|
+
- A failed multipart upload is aborted, so its parts are not billed.
|
|
140
|
+
- `downloadFile` writes to `path + '.download'` and renames it into place;
|
|
141
|
+
every range is pinned to the object's ETag, and a broken range resumes
|
|
142
|
+
where it stopped.
|
|
143
|
+
|
|
144
|
+
## API
|
|
145
|
+
|
|
146
|
+
| `Client` | |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `listBuckets()`, `createBucket(name)`, `getBucket(name)`, `deleteBucket(name)` | buckets |
|
|
149
|
+
| `bucket(name)` | a `Bucket` handle |
|
|
150
|
+
|
|
151
|
+
| `Bucket` | |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `put`, `upload`, `uploadFile` | upload (`contentType`, `metadata`, `cacheControl`, `contentDisposition`, `contentEncoding`, `ifMatch`, `ifNoneMatch`, `ifNotExists`) |
|
|
154
|
+
| `get` (`offset`/`length`, `ifNoneMatch` / `ifModifiedSince` → `NotModifiedError`, `ifMatch` / `ifUnmodifiedSince`), `getBytes`, `getText`, `downloadFile` | download |
|
|
155
|
+
| `head` (same conditions as `get`), `exists`, `delete` (`ifMatch`, `ifNoneMatch`), `deleteMany`, `copy` | object operations |
|
|
156
|
+
| `list` (one page), `objects` (async iterator) | listing |
|
|
157
|
+
| `createLink(key, { expiresIn, upload })` | a link that needs no key |
|
|
158
|
+
| `publicAccess()`, `setPublicAccess(rules)`, `publicUrl(key)` | make the bucket or some folders readable by anyone, and their URLs |
|
|
159
|
+
| `createMultipartUpload`, `resumeMultipartUpload` → `uploadPart`, `parts`, `complete` (`ifMatch`, `ifNoneMatch`), `abort` | do-it-yourself multipart |
|
|
160
|
+
|
|
161
|
+
The service answers errors as Problem Details (RFC 9457,
|
|
162
|
+
`application/problem+json`). They throw `ObjectStorageError` with `status`,
|
|
163
|
+
`code` (for example `object_not_found`, `bucket_already_exists`, `busy`), the
|
|
164
|
+
problem's title in `message`, and `type` / `instance`. `NotFoundError` is the
|
|
165
|
+
404 case and `PreconditionFailedError` the 412 one.
|
|
166
|
+
|
|
167
|
+
## Development
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
npm install
|
|
171
|
+
npm test # runs the TypeScript tests directly (Node 22.6+ type stripping)
|
|
172
|
+
npm run build # dist/ (ESM + .d.ts)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`npm test` uses an in-memory fake of the service. Set
|
|
176
|
+
`TH_OBJECT_STORAGE_ENDPOINT`, `TH_ACCESS_KEY_ID` and `TH_SECRET_ACCESS_KEY`
|
|
177
|
+
to also run a round trip against a real endpoint (it creates and deletes a
|
|
178
|
+
bucket).
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
export declare const VERSION = "1.0.0";
|
|
3
|
+
export interface ClientOptions {
|
|
4
|
+
/** The region's endpoint, e.g. https://objects.bkk.thailandhosting.com */
|
|
5
|
+
endpoint: string;
|
|
6
|
+
/** With secretAccessKey; leave both out for an anonymous client that reads only what buckets made public.
|
|
7
|
+
* Requests are signed with the secret (HTTP Message Signatures); the secret itself is never sent. */
|
|
8
|
+
accessKeyId?: string;
|
|
9
|
+
secretAccessKey?: string;
|
|
10
|
+
/** Size of each upload part and download range (default 16 MiB, at least 5 MiB). */
|
|
11
|
+
partSize?: number;
|
|
12
|
+
/** Size above which transfers are split (default 2 × partSize). */
|
|
13
|
+
multipartThreshold?: number;
|
|
14
|
+
/** Parts or ranges in flight at once (default 8). */
|
|
15
|
+
concurrency?: number;
|
|
16
|
+
/** Retries of requests that are safe to repeat (default 4). */
|
|
17
|
+
maxRetries?: number;
|
|
18
|
+
/** Appended to the SDK's User-Agent. */
|
|
19
|
+
userAgent?: string;
|
|
20
|
+
/** A fetch implementation (default: the global fetch). */
|
|
21
|
+
fetch?: typeof fetch;
|
|
22
|
+
}
|
|
23
|
+
export interface BucketInfo {
|
|
24
|
+
name: string;
|
|
25
|
+
region: string;
|
|
26
|
+
createdAt?: Date;
|
|
27
|
+
/** What anyone may read; getBucket fills it, listBuckets does not. */
|
|
28
|
+
public?: PublicRule[];
|
|
29
|
+
}
|
|
30
|
+
/** Makes the objects under `prefix` ("" is the whole bucket) readable by anyone without a key; `list` also lets anyone list those keys. */
|
|
31
|
+
export interface PublicRule {
|
|
32
|
+
prefix: string;
|
|
33
|
+
list?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface ObjectInfo {
|
|
36
|
+
key: string;
|
|
37
|
+
size: number;
|
|
38
|
+
etag: string;
|
|
39
|
+
lastModified?: Date;
|
|
40
|
+
contentType?: string;
|
|
41
|
+
metadata: Record<string, string>;
|
|
42
|
+
/** The object's Repr-Digest (`sha-256=:<base64>:`), from get and head, when it was uploaded in
|
|
43
|
+
* one request; full downloads are checked against it. Multipart uploads have none. */
|
|
44
|
+
digest?: string;
|
|
45
|
+
}
|
|
46
|
+
/** Conditions checked by the service atomically with a write or delete; a false one throws
|
|
47
|
+
* PreconditionFailedError. An ETag (quotes optional), a comma-separated list of them, or `*`. */
|
|
48
|
+
export interface WriteConditions {
|
|
49
|
+
/** Only if the object's ETag is one of these (`*`: only if it exists). */
|
|
50
|
+
ifMatch?: string;
|
|
51
|
+
/** Only if the object's ETag is none of these (`*`: only if the key is free). */
|
|
52
|
+
ifNoneMatch?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface PutOptions extends WriteConditions {
|
|
55
|
+
contentType?: string;
|
|
56
|
+
cacheControl?: string;
|
|
57
|
+
contentDisposition?: string;
|
|
58
|
+
contentEncoding?: string;
|
|
59
|
+
/** Stored with the object, returned as X-Meta-<name> (2 KB in all). */
|
|
60
|
+
metadata?: Record<string, string>;
|
|
61
|
+
/** Fail with code `precondition_failed` if the key already holds an object (`ifNoneMatch: '*'`). */
|
|
62
|
+
ifNotExists?: boolean;
|
|
63
|
+
signal?: AbortSignal;
|
|
64
|
+
}
|
|
65
|
+
export interface HeadOptions {
|
|
66
|
+
/** Throw NotModifiedError when the object's ETag is one of these. */
|
|
67
|
+
ifNoneMatch?: string;
|
|
68
|
+
/** Throw PreconditionFailedError unless the object's ETag is one of these. */
|
|
69
|
+
ifMatch?: string;
|
|
70
|
+
/** Throw NotModifiedError unless the object changed after this. */
|
|
71
|
+
ifModifiedSince?: Date;
|
|
72
|
+
/** Throw PreconditionFailedError if the object changed after this. */
|
|
73
|
+
ifUnmodifiedSince?: Date;
|
|
74
|
+
signal?: AbortSignal;
|
|
75
|
+
}
|
|
76
|
+
export interface GetOptions extends HeadOptions {
|
|
77
|
+
/** Download `length` bytes from `offset` (length omitted: to the end). */
|
|
78
|
+
offset?: number;
|
|
79
|
+
length?: number;
|
|
80
|
+
}
|
|
81
|
+
export interface DeleteOptions extends WriteConditions {
|
|
82
|
+
signal?: AbortSignal;
|
|
83
|
+
}
|
|
84
|
+
export interface CompleteOptions extends WriteConditions {
|
|
85
|
+
signal?: AbortSignal;
|
|
86
|
+
}
|
|
87
|
+
export interface ListOptions {
|
|
88
|
+
prefix?: string;
|
|
89
|
+
/** Roll keys up into folders ("/" lists one level). */
|
|
90
|
+
delimiter?: string;
|
|
91
|
+
cursor?: string;
|
|
92
|
+
/** Page size, 1-1000 (default 1000). */
|
|
93
|
+
limit?: number;
|
|
94
|
+
signal?: AbortSignal;
|
|
95
|
+
}
|
|
96
|
+
export interface ListPage {
|
|
97
|
+
objects: ObjectInfo[];
|
|
98
|
+
folders: string[];
|
|
99
|
+
/** Continues the listing; undefined on the last page. */
|
|
100
|
+
nextCursor?: string;
|
|
101
|
+
}
|
|
102
|
+
export interface Part {
|
|
103
|
+
number: number;
|
|
104
|
+
etag: string;
|
|
105
|
+
size: number;
|
|
106
|
+
}
|
|
107
|
+
export interface Link {
|
|
108
|
+
url: string;
|
|
109
|
+
method: 'GET' | 'PUT';
|
|
110
|
+
expiresAt?: Date;
|
|
111
|
+
}
|
|
112
|
+
/** A download in progress. Read `body` (a web stream) or `toNodeStream()`. */
|
|
113
|
+
export interface ObjectDownload {
|
|
114
|
+
info: ObjectInfo;
|
|
115
|
+
contentRange?: string;
|
|
116
|
+
body: ReadableStream<Uint8Array>;
|
|
117
|
+
toNodeStream(): Readable;
|
|
118
|
+
}
|
|
119
|
+
type Body = string | Uint8Array;
|
|
120
|
+
interface RequestInit2 {
|
|
121
|
+
method: string;
|
|
122
|
+
path: string;
|
|
123
|
+
query?: Record<string, string | number | undefined>;
|
|
124
|
+
headers?: Record<string, string>;
|
|
125
|
+
body?: Uint8Array;
|
|
126
|
+
/** Retry a POST on answers that mean nothing was done (429, 503). */
|
|
127
|
+
retryPost?: boolean;
|
|
128
|
+
/** Statuses that are not errors (default: any 2xx). */
|
|
129
|
+
expect?: number[];
|
|
130
|
+
signal?: AbortSignal;
|
|
131
|
+
}
|
|
132
|
+
/** A client of one region's Object Storage endpoint. Reuse it: it keeps connections open. */
|
|
133
|
+
export declare class Client {
|
|
134
|
+
readonly partSize: number;
|
|
135
|
+
readonly multipartThreshold: number;
|
|
136
|
+
readonly concurrency: number;
|
|
137
|
+
readonly maxRetries: number;
|
|
138
|
+
private readonly base;
|
|
139
|
+
/** @internal */ readonly origin: string;
|
|
140
|
+
private readonly keyId;
|
|
141
|
+
private readonly secret;
|
|
142
|
+
private readonly ua;
|
|
143
|
+
private readonly fetch;
|
|
144
|
+
constructor(opts: ClientOptions);
|
|
145
|
+
/** @internal Sends a request with retries; the caller reads or cancels the body. */
|
|
146
|
+
request(r: RequestInit2): Promise<Response>;
|
|
147
|
+
/** @internal */
|
|
148
|
+
json<T = any>(method: string, path: string, payload?: unknown, opts?: {
|
|
149
|
+
query?: RequestInit2['query'];
|
|
150
|
+
retryPost?: boolean;
|
|
151
|
+
signal?: AbortSignal;
|
|
152
|
+
headers?: Record<string, string>;
|
|
153
|
+
}): Promise<T>;
|
|
154
|
+
/** The buckets this access key can use. */
|
|
155
|
+
listBuckets(signal?: AbortSignal): Promise<BucketInfo[]>;
|
|
156
|
+
/** Create a bucket (3-63 characters of a-z, 0-9, dots and hyphens). */
|
|
157
|
+
createBucket(name: string, signal?: AbortSignal): Promise<BucketInfo>;
|
|
158
|
+
getBucket(name: string, signal?: AbortSignal): Promise<BucketInfo>;
|
|
159
|
+
/** Delete an empty bucket. */
|
|
160
|
+
deleteBucket(name: string, signal?: AbortSignal): Promise<void>;
|
|
161
|
+
/** A handle for one bucket's objects (no request is made). */
|
|
162
|
+
bucket(name: string): Bucket;
|
|
163
|
+
/** @internal */
|
|
164
|
+
partSizeFor(size: number): number;
|
|
165
|
+
}
|
|
166
|
+
/** The objects of one bucket. */
|
|
167
|
+
export declare class Bucket {
|
|
168
|
+
readonly name: string;
|
|
169
|
+
/** @internal */ readonly c: Client;
|
|
170
|
+
/** @internal */ readonly path: string;
|
|
171
|
+
constructor(client: Client, name: string);
|
|
172
|
+
private obj;
|
|
173
|
+
/** Upload a string or bytes in one request. */
|
|
174
|
+
put(key: string, data: Body, opts?: PutOptions): Promise<ObjectInfo>;
|
|
175
|
+
/** Stream an object (or a range of it). `info.size` is the whole object's size. A whole-object
|
|
176
|
+
* download with a Repr-Digest is checked as it is read: the body fails with IntegrityError at
|
|
177
|
+
* its end if the bytes do not match. */
|
|
178
|
+
get(key: string, opts?: GetOptions): Promise<ObjectDownload>;
|
|
179
|
+
/** Download a whole object into memory, checked against its Repr-Digest when it has one. */
|
|
180
|
+
getBytes(key: string, signal?: AbortSignal): Promise<Uint8Array>;
|
|
181
|
+
/** Download a whole object as UTF-8 text. */
|
|
182
|
+
getText(key: string, signal?: AbortSignal): Promise<string>;
|
|
183
|
+
/** An object's info without its data. With `ifNoneMatch` / `ifModifiedSince` it throws
|
|
184
|
+
* NotModifiedError when the object is unchanged. */
|
|
185
|
+
head(key: string, opts?: AbortSignal | HeadOptions): Promise<ObjectInfo>;
|
|
186
|
+
exists(key: string, signal?: AbortSignal): Promise<boolean>;
|
|
187
|
+
/** Delete an object; deleting a missing one succeeds (unless `ifMatch` wanted one). */
|
|
188
|
+
delete(key: string, opts?: AbortSignal | DeleteOptions): Promise<void>;
|
|
189
|
+
/** Delete keys in parallel. */
|
|
190
|
+
deleteMany(keys: string[], signal?: AbortSignal): Promise<void>;
|
|
191
|
+
/** Copy inside the service, without downloading. */
|
|
192
|
+
copy(srcKey: string, dstKey: string, opts?: {
|
|
193
|
+
sourceBucket?: string;
|
|
194
|
+
metadata?: Record<string, string>;
|
|
195
|
+
signal?: AbortSignal;
|
|
196
|
+
}): Promise<ObjectInfo>;
|
|
197
|
+
/** One page of objects (and folders, with a delimiter). */
|
|
198
|
+
list(opts?: ListOptions): Promise<ListPage>;
|
|
199
|
+
/** Every object under prefix: `for await (const obj of bucket.objects('photos/'))`. */
|
|
200
|
+
objects(prefix?: string, opts?: {
|
|
201
|
+
pageSize?: number;
|
|
202
|
+
signal?: AbortSignal;
|
|
203
|
+
}): AsyncGenerator<ObjectInfo>;
|
|
204
|
+
/** A URL that downloads (or, with `upload: true`, uploads) one object without a key. */
|
|
205
|
+
createLink(key: string, opts?: {
|
|
206
|
+
expiresIn?: number;
|
|
207
|
+
upload?: boolean;
|
|
208
|
+
signal?: AbortSignal;
|
|
209
|
+
}): Promise<Link>;
|
|
210
|
+
/** What anyone may read in the bucket. */
|
|
211
|
+
publicAccess(opts?: {
|
|
212
|
+
signal?: AbortSignal;
|
|
213
|
+
}): Promise<PublicRule[]>;
|
|
214
|
+
/**
|
|
215
|
+
* Replace what anyone may read: `[]` makes the bucket private, `[{ prefix: '' }]` opens all of it,
|
|
216
|
+
* `[{ prefix: 'images/' }]` one folder. Needs a key allowed to manage buckets; takes effect within
|
|
217
|
+
* about 15 seconds. Resolves to the rules as stored.
|
|
218
|
+
*/
|
|
219
|
+
setPublicAccess(rules: PublicRule[], opts?: {
|
|
220
|
+
signal?: AbortSignal;
|
|
221
|
+
}): Promise<PublicRule[]>;
|
|
222
|
+
/** The address anyone can open `key` at once its folder (or the bucket) is public. No request is made. */
|
|
223
|
+
publicUrl(key: string): string;
|
|
224
|
+
/** Start a multipart upload (uploadFile and upload do this for you). */
|
|
225
|
+
createMultipartUpload(key: string, opts?: PutOptions): Promise<MultipartUpload>;
|
|
226
|
+
resumeMultipartUpload(key: string, uploadId: string): MultipartUpload;
|
|
227
|
+
/** Upload a local file. Large files go up as parts read from the file and
|
|
228
|
+
* sent in parallel; a part is re-read if it must be sent again. `ifMatch` /
|
|
229
|
+
* `ifNoneMatch` are checked when the parts are joined. */
|
|
230
|
+
uploadFile(key: string, path: string, opts?: PutOptions): Promise<ObjectInfo>;
|
|
231
|
+
/** Upload what a stream yields (a pipe, an HTTP request, a generated
|
|
232
|
+
* export) without knowing its size. Parts are sent in parallel, holding at
|
|
233
|
+
* most `concurrency + 1` in memory; every part (or a small stream, sent in
|
|
234
|
+
* one request) is buffered, so its Content-Digest is known before it is sent. */
|
|
235
|
+
upload(key: string, source: AsyncIterable<Uint8Array> | ReadableStream<Uint8Array>, opts?: PutOptions): Promise<ObjectInfo>;
|
|
236
|
+
/** Save an object to `path`. Large objects come down as parallel ranges
|
|
237
|
+
* pinned to the object's ETag, written straight to their place in the
|
|
238
|
+
* file; the data goes to `path + '.download'` and is renamed into place
|
|
239
|
+
* only once it matches the object's Repr-Digest (when it has one). */
|
|
240
|
+
downloadFile(key: string, path: string, signal?: AbortSignal): Promise<ObjectInfo>;
|
|
241
|
+
private downloadRange;
|
|
242
|
+
}
|
|
243
|
+
/** A large upload in progress: send parts (any order, in parallel), then complete() or abort(). */
|
|
244
|
+
export declare class MultipartUpload {
|
|
245
|
+
readonly bucket: Bucket;
|
|
246
|
+
readonly key: string;
|
|
247
|
+
readonly uploadId: string;
|
|
248
|
+
constructor(bucket: Bucket, key: string, uploadId: string);
|
|
249
|
+
private get path();
|
|
250
|
+
/** Send part `n` (1-10000); every part but the last must be at least 5 MiB. */
|
|
251
|
+
uploadPart(n: number, data: Body, signal?: AbortSignal): Promise<Part>;
|
|
252
|
+
parts(signal?: AbortSignal): Promise<Part[]>;
|
|
253
|
+
/** Join the parts into the object; without `parts`, every part received, in order.
|
|
254
|
+
* `ifMatch` / `ifNoneMatch` are checked against the key atomically with the join. */
|
|
255
|
+
complete(parts?: Part[], opts?: AbortSignal | CompleteOptions): Promise<ObjectInfo>;
|
|
256
|
+
abort(signal?: AbortSignal): Promise<void>;
|
|
257
|
+
/** @internal */
|
|
258
|
+
abortQuietly(): Promise<void>;
|
|
259
|
+
}
|
|
260
|
+
export {};
|