@ustorage/sdk 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) UStorage
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 ADDED
@@ -0,0 +1,343 @@
1
+ # @ustorage/sdk
2
+
3
+ Official TypeScript SDK for [UStorage](https://ustorage.app) — chunked uploads, resume, and file URL resolution against the UStorage upload API.
4
+
5
+ Supports **Node.js** (≥20) and **browser** runtimes with separate entry points. Ships ESM and CommonJS with TypeScript declarations.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @ustorage/sdk
11
+ # or
12
+ pnpm add @ustorage/sdk
13
+ yarn add @ustorage/sdk
14
+ ```
15
+
16
+ ## Entry points
17
+
18
+ | Import | Use when |
19
+ |--------|----------|
20
+ | `@ustorage/sdk` | Shared types, `UploadApi`, `Uploader`, errors — no runtime client |
21
+ | `@ustorage/sdk/node` | `UStorageNodeClient` — servers, scripts, workers |
22
+ | `@ustorage/sdk/browser` | `UStorageBrowserClient` — web apps (no `secretKey`) |
23
+
24
+ ```ts
25
+ import type { CreateUploadSessionRequest, UploadObjectResult } from '@ustorage/sdk';
26
+ import { UStorageNodeClient } from '@ustorage/sdk/node';
27
+ import { UStorageBrowserClient } from '@ustorage/sdk/browser';
28
+ ```
29
+
30
+ ## Requirements
31
+
32
+ - **Node.js** ≥ 20 (for `@ustorage/sdk/node`)
33
+ - A running UStorage upload service (`uploadBaseUrl`, e.g. `http://localhost:6900` in development)
34
+ - Credentials scoped to a workspace (see [Authentication](#authentication))
35
+
36
+ ## Quick start (Node)
37
+
38
+ Node clients can authenticate with **access key + secret key** or a **Bearer token**. Credentials are already bound to a workspace, so uploads use `bucketName` and `key` (not workspace/bucket UUIDs).
39
+
40
+ ```ts
41
+ import { UStorageNodeClient } from '@ustorage/sdk/node';
42
+
43
+ const client = new UStorageNodeClient({
44
+ uploadBaseUrl: process.env.USTORAGE_UPLOAD_URL!,
45
+ auth: {
46
+ accessKey: process.env.USTORAGE_ACCESS_KEY!,
47
+ secretKey: process.env.USTORAGE_SECRET_KEY!,
48
+ },
49
+ });
50
+
51
+ const result = await client.putObject({
52
+ bucketName: 'videos',
53
+ key: 'campaigns/intro.mp4',
54
+ body: './video.mp4',
55
+ });
56
+
57
+ console.log(result.url, result.fileId);
58
+ ```
59
+
60
+ Upload from a file path (key defaults to the basename of the path):
61
+
62
+ ```ts
63
+ await client.uploadFile('./avatar.png', {
64
+ bucketName: 'assets',
65
+ key: 'users/u_123/avatar.png',
66
+ resume: true,
67
+ onProgress: (e) => console.log(`${e.progress}%`),
68
+ });
69
+ ```
70
+
71
+ Buffer upload:
72
+
73
+ ```ts
74
+ await client.putObject({
75
+ bucketName: 'assets',
76
+ key: 'data/report.json',
77
+ body: Buffer.from('{"ok":true}'),
78
+ contentType: 'application/json',
79
+ });
80
+ ```
81
+
82
+ ### Bearer token (Node)
83
+
84
+ ```ts
85
+ const client = new UStorageNodeClient({
86
+ uploadBaseUrl: process.env.USTORAGE_UPLOAD_URL!,
87
+ auth: {
88
+ bearerToken: () => fetchNewToken(), // string or async factory
89
+ },
90
+ });
91
+ ```
92
+
93
+ ## Quick start (Browser)
94
+
95
+ **Never pass `secretKey` in the browser.** Mint a short-lived upload token on your backend, then pass it to the client.
96
+
97
+ ```ts
98
+ import { UStorageBrowserClient } from '@ustorage/sdk/browser';
99
+
100
+ const uploadToken = await fetch('/api/upload-token', {
101
+ method: 'POST',
102
+ headers: { 'content-type': 'application/json' },
103
+ body: JSON.stringify({ bucketName: 'assets', key: `uploads/${file.name}` }),
104
+ }).then((r) => r.text());
105
+
106
+ const client = new UStorageBrowserClient({
107
+ uploadBaseUrl: import.meta.env.VITE_USTORAGE_UPLOAD_URL,
108
+ uploadToken,
109
+ });
110
+
111
+ await client.uploadFile(file, {
112
+ bucketName: 'assets',
113
+ key: `uploads/${file.name}`,
114
+ onProgress: (event) => console.log(event.progress),
115
+ });
116
+ ```
117
+
118
+ `File` / `Blob` via `putObject`:
119
+
120
+ ```ts
121
+ await client.putObject({
122
+ bucketName: 'assets',
123
+ key: 'uploads/photo.jpg',
124
+ body: file,
125
+ });
126
+ ```
127
+
128
+ Browser auth also supports `bearerToken` (same shape as Node) if your app already uses Bearer auth end-to-end.
129
+
130
+ ## Authentication
131
+
132
+ | Runtime | Method | Headers |
133
+ |---------|--------|---------|
134
+ | Node | `accessKey` + `secretKey` | `x-access-key`, `x-secret-key` |
135
+ | Node / Browser | `bearerToken` | `Authorization: Bearer …` |
136
+ | Browser | `uploadToken` | `x-upload-token` |
137
+
138
+ `bearerToken` and `uploadToken` accept a static string or `() => string | Promise<string>` for refresh.
139
+
140
+ Node credential auth uses raw access/secret headers because the API stores only a hash of `secretKey`; signed `x-ustorage-*` request auth is not available yet (`nodeSigner.supported === false`).
141
+
142
+ ### Minting upload tokens (backend)
143
+
144
+ Use `client.uploads.createUploadToken` on a Node client (or call `POST /uploads/tokens` directly):
145
+
146
+ ```ts
147
+ const { token, expires_at } = await serverClient.uploads.createUploadToken({
148
+ bucket_name: 'assets',
149
+ key: 'uploads/demo.png',
150
+ mime_type: 'image/png',
151
+ max_file_size: 10 * 1024 * 1024,
152
+ expires_in: 900,
153
+ });
154
+ // Return `token` to the browser as `uploadToken`
155
+ ```
156
+
157
+ ## Object keys and buckets
158
+
159
+ - `bucketName` — bucket name in the credential’s workspace.
160
+ - `key` — S3-style object key: path segments are folders; the last segment is the filename. Missing folders are created by the backend.
161
+ - `workspaceName` — optional override when the API allows it.
162
+
163
+ ## Resolving URLs
164
+
165
+ ```ts
166
+ // Public or private URL by bucket + key
167
+ const file = await client.getObjectUrl({
168
+ bucket: 'videos',
169
+ key: 'movies/demo.mp4',
170
+ });
171
+
172
+ // Signed / time-limited URL by file ID
173
+ const signed = await client.getSignedUrl({
174
+ fileId: 'file_uuid',
175
+ expiresIn: 3600,
176
+ });
177
+
178
+ console.log(file.url, signed.expiresAt);
179
+ ```
180
+
181
+ `getSignedUrl` is an alias of `getObjectUrl`; both call `POST /files/url`.
182
+
183
+ ## Upload options
184
+
185
+ | Option | Default | Description |
186
+ |--------|---------|-------------|
187
+ | `bucketName` | — | Target bucket (required) |
188
+ | `key` | — | Object key (required for `putObject`; optional for `uploadFile`, derived from path/name) |
189
+ | `workspaceName` | — | Optional workspace name |
190
+ | `contentType` | inferred | MIME type; inferred from extension / file metadata when omitted |
191
+ | `metadata` | — | Arbitrary JSON metadata on the session |
192
+ | `overwrite` | `true` | Replace existing object at the same key (backend soft-deletes the old object) |
193
+ | `visibility` | — | `'public'` or `'private'` |
194
+ | `checksum` | `false` | `false`, `'sha256'` (per-chunk hash), or `{ file?, chunks? }` |
195
+ | `chunkSize` | `8388608` (8 MiB) | Bytes per chunk |
196
+ | `resume` | `false` | Enable resume via `resumeStore` |
197
+ | `resumeKey` | fingerprint | Custom resume key; implies resume when set |
198
+ | `signal` | — | `AbortSignal` to cancel in-flight chunk uploads |
199
+ | `onProgress` | — | Callback after each chunk / status poll |
200
+
201
+ ### Upload result
202
+
203
+ `putObject` / `uploadFile` resolve to `UploadObjectResult`:
204
+
205
+ ```ts
206
+ {
207
+ uploadId: string;
208
+ fileId: string;
209
+ publicId?: string;
210
+ visibility?: 'public' | 'private' | string;
211
+ url?: string;
212
+ expiresAt?: string | null;
213
+ status: string;
214
+ // …plus snake_case fields from the API response
215
+ }
216
+ ```
217
+
218
+ ## Resume
219
+
220
+ Resume is **opt-in** (`resume: true` or `resumeKey`). The SDK stores `upload_id` under a fingerprint of the source + key, polls missing chunks on retry, and clears the entry after completion.
221
+
222
+ **Node** — pass `resumeStore` on the client:
223
+
224
+ ```ts
225
+ import { UStorageNodeClient, NodeFileResumeStore } from '@ustorage/sdk/node';
226
+
227
+ const client = new UStorageNodeClient({
228
+ uploadBaseUrl: '…',
229
+ auth: { … },
230
+ resumeStore: new NodeFileResumeStore('.ustorage-resume.json'),
231
+ });
232
+ ```
233
+
234
+ Also available: `NodeMemoryResumeStore` (in-memory).
235
+
236
+ **Browser** — defaults to `BrowserResumeStore` (`localStorage` with prefix `ustorage:upload:`, falls back to memory). Override via `resumeStore` in client options.
237
+
238
+ Implement `ResumeStore` for custom persistence:
239
+
240
+ ```ts
241
+ interface ResumeStore {
242
+ get(key: string): Promise<string | undefined> | string | undefined;
243
+ set(key: string, uploadId: string): Promise<void> | void;
244
+ delete(key: string): Promise<void> | void;
245
+ }
246
+ ```
247
+
248
+ ## Low-level upload API
249
+
250
+ Both clients expose `client.uploads` (`UploadApi`) for direct control:
251
+
252
+ | Method | HTTP | Description |
253
+ |--------|------|-------------|
254
+ | `createUploadToken` | `POST /uploads/tokens` | Mint browser upload token |
255
+ | `createSession` | `POST /uploads/sessions` | Start chunked session |
256
+ | `uploadChunk` | `PUT /uploads/:id/chunks/:index` | Upload one chunk |
257
+ | `getStatus` | `GET /uploads/:id/status` | Session status / missing chunks |
258
+ | `complete` | `POST /uploads/:id/complete` | Finalize upload |
259
+ | `cancel` | `DELETE /uploads/:id` | Cancel session |
260
+ | `createFileUrl` | `POST /files/url` | Resolve CDN URL |
261
+
262
+ The high-level `Uploader` (used by `putObject` / `uploadFile`) orchestrates session creation, chunk loop, checksum headers (`x-chunk-checksum`), and completion.
263
+
264
+ ## Error handling
265
+
266
+ Failed API responses throw `UStorageError`:
267
+
268
+ ```ts
269
+ import { UStorageError } from '@ustorage/sdk';
270
+
271
+ try {
272
+ await client.putObject({ … });
273
+ } catch (err) {
274
+ if (err instanceof UStorageError) {
275
+ console.error(err.code, err.status, err.requestId, err.details);
276
+ }
277
+ }
278
+ ```
279
+
280
+ ## TypeScript
281
+
282
+ Types are exported from `@ustorage/sdk` (and re-exported from `/node` and `/browser`). Request/response DTOs use snake_case matching the HTTP API; client helpers like `getObjectUrl` accept camelCase (`fileId`, `expiresIn`, …).
283
+
284
+ ## Development
285
+
286
+ From the `sdk` package directory:
287
+
288
+ ```bash
289
+ yarn install
290
+ yarn build # tsup → dist/
291
+ yarn typecheck
292
+ yarn test:upload # node test.js (requires env vars)
293
+ ```
294
+
295
+ Environment variables used by `examples/node-upload.ts` and `test.js`:
296
+
297
+ - `USTORAGE_UPLOAD_URL` or hardcoded base URL
298
+ - `USTORAGE_ACCESS_KEY`, `USTORAGE_SECRET_KEY`
299
+ - `USTORAGE_BUCKET_NAME`, `USTORAGE_OBJECT_KEY` (for tests)
300
+
301
+ ## Package exports
302
+
303
+ ```json
304
+ {
305
+ ".": "./dist/core/index.js",
306
+ "./browser": "./dist/browser/index.js",
307
+ "./node": "./dist/node/index.js"
308
+ }
309
+ ```
310
+
311
+ `"type": "module"` with dual ESM/CJS builds. Default `main`/`module` point at the Node build; import the subpath you need in application code.
312
+
313
+ ## Publish to npm
314
+
315
+ From the `sdk` directory (scoped package `@ustorage` must be published as **public**):
316
+
317
+ ```bash
318
+ # 1. Login (once)
319
+ npm login
320
+
321
+ # 2. Build + dry-run — inspect tarball contents
322
+ npm run build
323
+ npm pack --dry-run
324
+
325
+ # 3. Publish (prepack rebuilds dist; prepublishOnly runs typecheck)
326
+ npm publish --access public
327
+ ```
328
+
329
+ What gets published is controlled by `package.json` → `"files": ["dist", "README.md", "LICENSE"]`. Source, tests, and examples are excluded (see also `.npmignore`).
330
+
331
+ Before the first publish, set `repository` in `package.json` if you have a public Git URL:
332
+
333
+ ```json
334
+ "repository": {
335
+ "type": "git",
336
+ "url": "https://github.com/your-org/ustorage.git",
337
+ "directory": "sdk"
338
+ }
339
+ ```
340
+
341
+ ## License
342
+
343
+ [MIT](./LICENSE)