upthing-sdk 1.0.1 → 1.0.3

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 CHANGED
@@ -8,38 +8,64 @@ TypeScript SDK for [upthing.2klabs.xyz](https://upthing.2klabs.xyz) — upload f
8
8
  npm install upthing-sdk
9
9
  ```
10
10
 
11
- ## Quick start
11
+ ## Two entry points
12
+
13
+ | Import | Environment | Has `uploadFile`? |
14
+ | ------ | ----------- | ----------------- |
15
+ | `upthing-sdk` | Browser + Node.js | No (`fs` would break bundlers) |
16
+ | `upthing-sdk/node` | Node.js only | Yes |
17
+
18
+ Use the default import for browser/edge/client-side code (Next.js `"use client"`, React, etc.).
19
+ Use `upthing-sdk/node` in server-only code when you need `uploadFile()`.
20
+
21
+ ## Quick start (Node.js)
12
22
 
13
23
  ```typescript
14
- import { UpThingClient } from "upthing-sdk";
24
+ import { UpThingNodeClient } from "upthing-sdk/node";
15
25
 
16
- const upthing = new UpThingClient({
26
+ const upthing = new UpThingNodeClient({
17
27
  apiKey: process.env.UPTHING_API_KEY!,
18
28
  });
19
29
 
20
- // Upload a file
30
+ // Upload a file from disk
21
31
  const file = await upthing.uploadFile("./photo.jpg");
22
32
  console.log(file.url);
23
33
  // https://upthing.2klabs.xyz/f/UgjIzXI3ZolZ
24
34
 
25
- // Proxy an external image — get a URL you can use directly
35
+ // Proxy an external image
26
36
  const src = upthing.proxyUrl("https://example.com/banner.jpg");
27
- // <img src={src} />
37
+ ```
38
+
39
+ ## Quick start (Browser / Next.js client)
40
+
41
+ ```typescript
42
+ "use client";
43
+ import { UpThingClient } from "upthing-sdk";
44
+
45
+ // No API key needed for domain-auth proxy
46
+ const upthing = new UpThingClient();
47
+
48
+ // Use directly in JSX
49
+ <img src={upthing.proxyUrl("https://example.com/photo.jpg", {
50
+ projectUuid: "07595179-..."
51
+ })} />
28
52
  ```
29
53
 
30
54
  ## API
31
55
 
32
- ### `new UpThingClient(options)`
56
+ ### `new UpThingClient(options?)`
33
57
 
34
- | Option | Type | Default |
35
- | --------- | ------ | -------------------------------- |
36
- | `apiKey` | string | **required** |
37
- | `baseUrl` | string | `https://upthing.2klabs.xyz` |
58
+ | Option | Type | Default |
59
+ | --------- | ------ | ----------------------------- |
60
+ | `apiKey` | string | `""` (optional for proxy URL builders) |
61
+ | `baseUrl` | string | `https://upthing.2klabs.xyz` |
62
+
63
+ `UpThingNodeClient` (from `upthing-sdk/node`) extends `UpThingClient` and adds `uploadFile()`.
38
64
 
39
65
  ### Upload methods
40
66
 
41
67
  ```typescript
42
- // From a file path (Node.js)
68
+ // From a file path (Node.js only — use UpThingNodeClient)
43
69
  await upthing.uploadFile("./photo.jpg");
44
70
  await upthing.uploadFile("./photo.jpg", { ttlSeconds: 3600 });
45
71
 
@@ -61,17 +87,48 @@ await upthing.uploadBase64(base64String, "avatar.png");
61
87
  | `projectUuid` | string | Target project (if key isn't pinned) |
62
88
  | `ttlSeconds` | number | Auto-delete after N seconds |
63
89
 
90
+ ### File management
91
+
92
+ ```typescript
93
+ // List files (paginated)
94
+ const { data, pagination } = await upthing.listFiles({ limit: 20, offset: 0 });
95
+ console.log(`${pagination.total} files total`);
96
+
97
+ // Filter by project
98
+ const { data: projectFiles } = await upthing.listFiles({
99
+ projectUuid: "07595179-...",
100
+ });
101
+
102
+ // Get a single file
103
+ const file = await upthing.getFile("d98b6a92-...");
104
+ console.log(file.url, file.mime_type, file.size_bytes);
105
+
106
+ // Delete a file (removes S3 object + soft-deletes DB row)
107
+ await upthing.deleteFile("d98b6a92-...");
108
+
109
+ // Generate a signed URL (for files with url_mode: "signed")
110
+ const { url, expires_in } = await upthing.getSignedUrl("d98b6a92-...", 7200);
111
+ ```
112
+
113
+ #### List files options
114
+
115
+ | Option | Type | Default | Description |
116
+ | ------------- | ------ | ------- | ------------------------------------ |
117
+ | `projectUuid` | string | — | Filter by project |
118
+ | `limit` | number | 50 | Results per page (1–200) |
119
+ | `offset` | number | 0 | Pagination offset |
120
+
64
121
  ### Proxy
65
122
 
66
123
  ```typescript
67
- // Build a URL that serves the proxied image (use in <img>, fetch, etc.)
124
+ // Build a URL for domain-auth proxy (no API key needed)
68
125
  const src = upthing.proxyUrl("https://example.com/image.jpg");
69
126
  const src = upthing.proxyUrl("https://example.com/image.jpg", {
70
127
  projectUuid: "07595179-...",
71
128
  ttlSeconds: 3600,
72
129
  });
73
130
 
74
- // Fetch the image bytes server-side
131
+ // Fetch the image bytes server-side (requires API key)
75
132
  const response = await upthing.proxyFetch("https://example.com/image.jpg");
76
133
  const buffer = await response.arrayBuffer();
77
134
  ```
@@ -90,7 +147,7 @@ upthing.permanentUrl("UgjIzXI3ZolZ");
90
147
  import { UpThingSDKError } from "upthing-sdk";
91
148
 
92
149
  try {
93
- await upthing.uploadFile("./huge.mp4");
150
+ await upthing.uploadBlob(file, "huge.mp4");
94
151
  } catch (err) {
95
152
  if (err instanceof UpThingSDKError) {
96
153
  console.error(err.status, err.message);
@@ -99,20 +156,26 @@ try {
99
156
  }
100
157
  ```
101
158
 
102
- ## Browser usage
159
+ All methods except `proxyUrl()` and `permanentUrl()` require an API key and throw immediately with a helpful message if none was provided.
160
+
161
+ ## Browser / React / Next.js usage
103
162
 
104
163
  ```typescript
105
- const upthing = new UpThingClient({ apiKey: "ak_..." });
164
+ "use client";
165
+ import { UpThingClient } from "upthing-sdk";
166
+
167
+ const upthing = new UpThingClient();
106
168
 
107
- // Proxy images directly in HTML — no async needed
108
- document.querySelector("img")!.src = upthing.proxyUrl("https://example.com/photo.jpg");
169
+ // Proxy images directly in HTML — no API key needed with domain auth
170
+ <img src={upthing.proxyUrl("https://example.com/photo.jpg")} />
109
171
 
110
- // Upload from a file input
172
+ // Upload from a file input (requires API key)
173
+ const uploader = new UpThingClient({ apiKey: "ak_..." });
111
174
  const input = document.querySelector<HTMLInputElement>("#file-input")!;
112
175
  input.addEventListener("change", async () => {
113
176
  const file = input.files?.[0];
114
177
  if (!file) return;
115
- const result = await upthing.uploadBlob(file, file.name);
178
+ const result = await uploader.uploadBlob(file, file.name);
116
179
  console.log("Uploaded:", result.url);
117
180
  });
118
181
  ```
package/dist/index.d.ts CHANGED
@@ -20,6 +20,23 @@ export interface UploadOptions {
20
20
  projectUuid?: string;
21
21
  ttlSeconds?: number;
22
22
  }
23
+ export interface ListFilesOptions {
24
+ projectUuid?: string;
25
+ limit?: number;
26
+ offset?: number;
27
+ }
28
+ export interface ListFilesResponse {
29
+ data: UpThingFile[];
30
+ pagination: {
31
+ limit: number;
32
+ offset: number;
33
+ total: number;
34
+ };
35
+ }
36
+ export interface SignedUrlResponse {
37
+ url: string;
38
+ expires_in: number;
39
+ }
23
40
  export interface ProxyOptions {
24
41
  projectUuid?: string;
25
42
  ttlSeconds?: number;
@@ -41,10 +58,6 @@ export declare class UpThingClient {
41
58
  private readonly apiKey;
42
59
  private readonly baseUrl;
43
60
  constructor(options?: UpThingClientOptions);
44
- /**
45
- * Upload a file from a local path (Node.js only).
46
- */
47
- uploadFile(filePath: string, options?: UploadOptions): Promise<UpThingFile>;
48
61
  /**
49
62
  * Upload a Blob/File (works in Node.js and browsers).
50
63
  */
@@ -57,6 +70,26 @@ export declare class UpThingClient {
57
70
  * Upload base64-encoded data.
58
71
  */
59
72
  uploadBase64(base64Data: string, filename: string, options?: UploadOptions): Promise<UpThingFile>;
73
+ /**
74
+ * List files owned by the authenticated user.
75
+ * Requires the `read` scope if the API key uses scoped permissions.
76
+ */
77
+ listFiles(options?: ListFilesOptions): Promise<ListFilesResponse>;
78
+ /**
79
+ * Get a single file's metadata by UUID.
80
+ * Requires the `read` scope if the API key uses scoped permissions.
81
+ */
82
+ getFile(uuid: string): Promise<UpThingFile>;
83
+ /**
84
+ * Delete a file by UUID. Removes the S3 object and soft-deletes the DB row.
85
+ * Requires the `delete` scope if the API key uses scoped permissions.
86
+ */
87
+ deleteFile(uuid: string): Promise<void>;
88
+ /**
89
+ * Generate a time-limited signed delivery URL for a file.
90
+ * Requires the `read` scope if the API key uses scoped permissions.
91
+ */
92
+ getSignedUrl(uuid: string, ttlSeconds?: number): Promise<SignedUrlResponse>;
60
93
  /**
61
94
  * Build a proxy query-string URL (without auth). Useful when the
62
95
  * project uses `domain` auth mode and no API key is needed in the
@@ -73,7 +106,9 @@ export declare class UpThingClient {
73
106
  * Build a permanent delivery URL for a public_id.
74
107
  */
75
108
  permanentUrl(publicId: string): string;
76
- private requireKey;
77
- private request;
109
+ /** @internal */
110
+ requireKey(method: string): void;
111
+ /** @internal */
112
+ request<T>(method: string, path: string, body?: FormData | Record<string, unknown>): Promise<T>;
78
113
  }
79
114
  export default UpThingClient;
package/dist/index.js CHANGED
@@ -16,22 +16,6 @@ export class UpThingClient {
16
16
  this.baseUrl = (options.baseUrl ?? "https://upthing.2klabs.xyz").replace(/\/$/, "");
17
17
  }
18
18
  // ── Upload ──────────────────────────────────────────────────────────
19
- /**
20
- * Upload a file from a local path (Node.js only).
21
- */
22
- async uploadFile(filePath, options) {
23
- const fs = await import("fs");
24
- const path = await import("path");
25
- const form = new FormData();
26
- const blob = new Blob([fs.readFileSync(filePath)]);
27
- const name = options?.filename ?? path.basename(filePath);
28
- form.append("file", blob, name);
29
- if (options?.projectUuid)
30
- form.append("project_uuid", options.projectUuid);
31
- if (options?.ttlSeconds != null)
32
- form.append("ttl_seconds", String(options.ttlSeconds));
33
- return this.request("POST", "/api/upload", form).then((r) => r.file);
34
- }
35
19
  /**
36
20
  * Upload a Blob/File (works in Node.js and browsers).
37
21
  */
@@ -62,6 +46,47 @@ export class UpThingClient {
62
46
  ttl_seconds: options?.ttlSeconds,
63
47
  }).then((r) => r.file);
64
48
  }
49
+ // ── Files ───────────────────────────────────────────────────────────
50
+ /**
51
+ * List files owned by the authenticated user.
52
+ * Requires the `read` scope if the API key uses scoped permissions.
53
+ */
54
+ async listFiles(options) {
55
+ const params = new URLSearchParams();
56
+ if (options?.projectUuid)
57
+ params.set("project_uuid", options.projectUuid);
58
+ if (options?.limit != null)
59
+ params.set("limit", String(options.limit));
60
+ if (options?.offset != null)
61
+ params.set("offset", String(options.offset));
62
+ const qs = params.toString();
63
+ return this.request("GET", `/api/files${qs ? `?${qs}` : ""}`);
64
+ }
65
+ /**
66
+ * Get a single file's metadata by UUID.
67
+ * Requires the `read` scope if the API key uses scoped permissions.
68
+ */
69
+ async getFile(uuid) {
70
+ return this.request("GET", `/api/files/${uuid}`).then((r) => r.file);
71
+ }
72
+ /**
73
+ * Delete a file by UUID. Removes the S3 object and soft-deletes the DB row.
74
+ * Requires the `delete` scope if the API key uses scoped permissions.
75
+ */
76
+ async deleteFile(uuid) {
77
+ await this.request("DELETE", `/api/files/${uuid}`);
78
+ }
79
+ /**
80
+ * Generate a time-limited signed delivery URL for a file.
81
+ * Requires the `read` scope if the API key uses scoped permissions.
82
+ */
83
+ async getSignedUrl(uuid, ttlSeconds) {
84
+ const params = new URLSearchParams();
85
+ if (ttlSeconds != null)
86
+ params.set("ttl_seconds", String(ttlSeconds));
87
+ const qs = params.toString();
88
+ return this.request("GET", `/api/files/${uuid}/signed-url${qs ? `?${qs}` : ""}`);
89
+ }
65
90
  // ── Proxy ───────────────────────────────────────────────────────────
66
91
  /**
67
92
  * Build a proxy query-string URL (without auth). Useful when the
@@ -101,12 +126,14 @@ export class UpThingClient {
101
126
  return `${this.baseUrl}/f/${publicId}`;
102
127
  }
103
128
  // ── Internal ────────────────────────────────────────────────────────
129
+ /** @internal */
104
130
  requireKey(method) {
105
131
  if (!this.apiKey) {
106
132
  throw new Error(`UpThingClient.${method}() requires an apiKey. ` +
107
133
  `Pass one via new UpThingClient({ apiKey: "ak_..." }).`);
108
134
  }
109
135
  }
136
+ /** @internal */
110
137
  async request(method, path, body) {
111
138
  this.requireKey("request");
112
139
  const headers = {
@@ -125,6 +152,8 @@ export class UpThingClient {
125
152
  headers,
126
153
  body: requestBody,
127
154
  });
155
+ if (res.status === 204)
156
+ return undefined;
128
157
  if (!res.ok) {
129
158
  const err = await res.json().catch(() => ({ error: `HTTP ${res.status}`, code: 0 }));
130
159
  throw new UpThingSDKError(res.status, err);
package/dist/node.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { UpThingClient, type UploadOptions, type UpThingFile } from "./index.js";
2
+ export declare class UpThingNodeClient extends UpThingClient {
3
+ /**
4
+ * Upload a file from a local path (Node.js only).
5
+ */
6
+ uploadFile(filePath: string, options?: UploadOptions): Promise<UpThingFile>;
7
+ }
8
+ export * from "./index.js";
9
+ export default UpThingNodeClient;
package/dist/node.js ADDED
@@ -0,0 +1,22 @@
1
+ import { UpThingClient } from "./index.js";
2
+ import { readFileSync } from "fs";
3
+ import { basename } from "path";
4
+ export class UpThingNodeClient extends UpThingClient {
5
+ /**
6
+ * Upload a file from a local path (Node.js only).
7
+ */
8
+ async uploadFile(filePath, options) {
9
+ const form = new FormData();
10
+ const blob = new Blob([readFileSync(filePath)]);
11
+ const name = options?.filename ?? basename(filePath);
12
+ form.append("file", blob, name);
13
+ if (options?.projectUuid)
14
+ form.append("project_uuid", options.projectUuid);
15
+ if (options?.ttlSeconds != null)
16
+ form.append("ttl_seconds", String(options.ttlSeconds));
17
+ this.requireKey("uploadFile");
18
+ return this.request("POST", "/api/upload", form).then((r) => r.file);
19
+ }
20
+ }
21
+ export * from "./index.js";
22
+ export default UpThingNodeClient;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "upthing-sdk",
3
- "version": "1.0.1",
4
- "description": "TypeScript SDK for upthing.2klabs.xyz — upload files, proxy images",
3
+ "version": "1.0.3",
4
+ "description": "TypeScript SDK for upthing.2klabs.xyz — upload, manage, and proxy files",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -12,6 +12,10 @@
12
12
  ".": {
13
13
  "import": "./dist/index.js",
14
14
  "types": "./dist/index.d.ts"
15
+ },
16
+ "./node": {
17
+ "import": "./dist/node.js",
18
+ "types": "./dist/node.d.ts"
15
19
  }
16
20
  },
17
21
  "scripts": {