@vertracloud/blob 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Vertra Cloud
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,170 @@
1
- # Temporary Holding Version
1
+ # @vertracloud/blob
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Official JavaScript/TypeScript SDK for Vertra Cloud Blob Storage. Works in Node.js 18+, browsers and edge runtimes, using the native `fetch`.
4
+
5
+ > **Status: under development, not published yet.** The API surface below may still change before the first release.
6
+
7
+ Full documentation: <https://docs.vertracloud.app>
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @vertracloud/blob
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ ```ts
18
+ import { BlobClient } from "@vertracloud/blob";
19
+
20
+ const blob = new BlobClient({ apiKey: process.env.VERTRA_API_KEY });
21
+
22
+ const object = await blob.put("images/logo.png", fileBytes, { visibility: "public" });
23
+ console.log(object.id, object.url);
24
+ ```
25
+
26
+ ### Client options
27
+
28
+ | Option | Default | Notes |
29
+ |---|---|---|
30
+ | `apiKey` | | API key with `blob:read` / `blob:write`. Sent as `Authorization: Bearer <key>`. |
31
+ | `uploadToken` | | An upload token (`vcup_…`) instead of an API key. Only valid on the upload routes. |
32
+ | `baseUrl` | `https://blob.vertracloud.app/v1` | Includes the version prefix; every route is appended to it (`${baseUrl}/objects`). |
33
+ | `fetch` | global `fetch` | Custom implementation. When set, upload progress is reported per finished part instead of per byte. |
34
+ | `credentials` | | Passed to every API call (for example `"include"` with a cookie session; no `Authorization` header is needed then). Never sent to signed upload URLs. |
35
+ | `workspaceId` | | Added as `?workspace_id=` to every call, to act on the blob of a workspace owner. |
36
+ | `headers` | | Extra headers for every API call. |
37
+ | `partConcurrency` | `4` | Parts uploaded at the same time. |
38
+ | `maxRetries` | `3` | Retries for `GET` and for the PUT of a part or single upload, on 5xx and network errors. Never on writes to the API or on `429`. |
39
+ | `retryDelayMs` | `300` | First retry delay; doubles each attempt, with jitter. |
40
+ | `timeout` | `30000` | Per API call, in ms. Every method also accepts `{ timeout, signal }`. |
41
+ | `uploadTimeout` | `300000` | Per request that sends file bytes. |
42
+
43
+ ### Naming: camelCase in, snake_case out
44
+
45
+ Options you pass use camelCase (`expiresIn`, `maxDownloads`, `randomSuffix`) and are converted to the API's snake_case internally.
46
+ The objects the SDK returns are the API payloads **as they are on the wire**, in snake_case (`object.content_type`, `upload.part_size`, `stats.used_bytes`). They are typed by `@vertracloud/api-types`, which this package re-exports.
47
+
48
+ ## Uploading
49
+
50
+ ```ts
51
+ const object = await blob.put("videos/intro.mp4", file, {
52
+ visibility: "public",
53
+ metadata: { owner: "alice" },
54
+ signal: controller.signal, // AbortSignal
55
+ onProgress: ({ loaded, total }) => bar.set(loaded / total),
56
+ });
57
+ ```
58
+
59
+ `data` can be a `Blob`, `File`, `ArrayBuffer`, `Uint8Array`, `string` or `ReadableStream` (a stream also needs `size`).
60
+
61
+ - Up to 5 MB the file goes in one `POST /objects` request.
62
+ - Above that the SDK starts a direct upload and follows the mode the API returns: a single signed PUT, or a multipart upload whose parts are sent in parallel (`partConcurrency`), each retried on its own.
63
+ - Progress is by byte in browsers (the SDK uses `XMLHttpRequest` when `onProgress` is set and you did not pass a custom `fetch`) and by finished part in Node.js and edge runtimes.
64
+ - Aborting `signal` during a direct upload cancels it on the server (best effort) and throws a `BlobError` with code `ABORTED`.
65
+ - Any other failure leaves the upload open on the server so you can resume it.
66
+
67
+ ### Resume
68
+
69
+ ```ts
70
+ let uploadId: string;
71
+ try {
72
+ await blob.put("backup.tar", file, { onUpload: (upload) => (uploadId = upload.id) });
73
+ } catch {
74
+ // later, with the same file
75
+ await blob.resume(uploadId, file, { onProgress }); // sends only the missing parts
76
+ }
77
+
78
+ const open = await blob.listUploads(); // direct uploads still in progress
79
+ await blob.abortUpload(uploadId);
80
+ ```
81
+
82
+ ### Browser with an upload token
83
+
84
+ Create a short-lived token on your server and hand it to the browser:
85
+
86
+ ```ts
87
+ // server
88
+ const { token } = await new BlobClient({ apiKey }).createUploadToken({ prefix: "avatars/", maxSize: 5_000_000, allowedExtensions: ["png", "jpg"], expiresIn: 900 });
89
+
90
+ // browser
91
+ const blob = new BlobClient({ uploadToken: token });
92
+ await blob.put("avatars/me.png", file, { onProgress });
93
+ ```
94
+
95
+ ### Edge runtimes
96
+
97
+ Nothing special is needed: the SDK only uses `fetch`, `Blob`, `FormData` and `AbortController`.
98
+
99
+ ## Objects
100
+
101
+ ```ts
102
+ for await (const obj of blob.list({ prefix: "images/" })) {} // follows the cursor for you
103
+ const page = await blob.listPage({ prefix: "images/", delimiter: "/", limit: 100 });
104
+ page.objects; page.folders; page.cursor;
105
+
106
+ await blob.get(id);
107
+ await blob.update(id, { metadata: { owner: "alice" }, visibility: "private" }); // null restores a default
108
+ await blob.updateMany(ids, { visibility: "private" }); // any number of ids (sent in batches of 100)
109
+ await blob.copy(id, "backup/logo.png");
110
+ await blob.move(id, "img/logo.png"); // keeps the id
111
+ await blob.delete(id);
112
+ await blob.deleteMany({ prefix: "tmp/" }); // repeats while the API reports `remaining`
113
+ await blob.deleteMany({ ids });
114
+
115
+ const { url, expires_at } = await blob.downloadUrl(id, { expiresIn: 600, disposition: "attachment", filename: "logo.png" });
116
+ blob.imageUrl(obj, { width: 400, format: "webp" }); // pure: builds ?w=400&format=webp on the public URL
117
+ ```
118
+
119
+ `imageUrl` throws for a private object. `fit`, `height`, `quality` are also accepted.
120
+
121
+ ## Shares, folder rules, domains, account
122
+
123
+ ```ts
124
+ await blob.shares.create(id, { expiresIn: 3600, maxDownloads: 10, password });
125
+ await blob.shares.list({ objectId: id });
126
+ await blob.shares.revoke(shareId);
127
+
128
+ const { rules, etag } = await blob.rules.get();
129
+ await blob.rules.set([{ prefix: "tmp/", deleteAfterDays: 30 }], etag); // sends If-Match; a stale etag fails with BLOB_RULES_CONFLICT
130
+ await blob.rules.put("tmp/", { deleteAfterDays: 7 });
131
+ await blob.rules.delete("tmp/");
132
+
133
+ await blob.domains.add("cdn.example.com");
134
+ await blob.domains.verify(domainId);
135
+ await blob.domains.list();
136
+ await blob.domains.remove(domainId);
137
+
138
+ await blob.stats(); // usage, quota and plan limits
139
+ const s3 = await blob.s3Credentials(); // for aws-cli, rclone, boto3
140
+ ```
141
+
142
+ ## Errors
143
+
144
+ Every failure is a `BlobError`:
145
+
146
+ ```ts
147
+ import { BlobError } from "@vertracloud/blob";
148
+
149
+ try {
150
+ await blob.put("a.png", data);
151
+ } catch (error) {
152
+ if (error instanceof BlobError) {
153
+ error.status; // HTTP status, 0 when no response was received
154
+ error.code; // for example "BLOB_OBJECT_ALREADY_EXISTS", "STORAGE_QUOTA_EXCEEDED"
155
+ error.message;
156
+ error.details; // for example { path: "..." } on VALIDATION_ERROR
157
+ error.retryAfter; // seconds, on 429
158
+ }
159
+ }
160
+ ```
161
+
162
+ The SDK itself raises `NETWORK_ERROR`, `TIMEOUT`, `ABORTED`, `INVALID_ARGUMENT`, `UPLOAD_FAILED` and `INVALID_RESPONSE`. Errors never include request headers, credentials or signed upload URLs. A `429` is never retried: wait `retryAfter` seconds yourself.
163
+
164
+ ## Types
165
+
166
+ Payload types and enums (`BlobVisibility`, `BlobUploadMode`, `APIBlobObject`, `BlobObject`, the `REST…` request types, ...) are re-exported from [`@vertracloud/api-types`](https://www.npmjs.com/package/@vertracloud/api-types), installed with this package, so you can import everything from `@vertracloud/blob`.
167
+
168
+ ## License
169
+
170
+ MIT