@dropthis/cli 0.4.1 → 0.6.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.
@@ -68,39 +68,96 @@ await dropthis.update("drop_abc123", { title: "New title" });
68
68
 
69
69
  ## Supported inputs
70
70
 
71
- The `publish()` method accepts:
71
+ The `publish()` and `deploy()` methods accept:
72
72
 
73
- - **HTML string** -- `"<h1>Hello</h1>"`
74
- - **File path** -- `"./report.html"`
75
- - **Directory** -- `"./dist"`
76
- - **Bytes** -- `new Uint8Array(...)`
77
- - **Explicit object** -- `{ content: "...", contentType: "text/html" }` or `{ files: [...] }`
73
+ - **HTML/text string** -- `"<h1>Hello</h1>"` (auto-detected as inline content)
74
+ - **File path** -- `"./report.html"` (local file)
75
+ - **Directory** -- `"./dist"` (local directory, bundled)
76
+ - **Array of paths** -- `["./dist", "./extra.css"]` (multi-path bundle)
77
+ - **URL object** -- `new URL("https://example.com/page")` (source fetch)
78
+ - **Bytes** -- `new Uint8Array(...)` (raw bytes)
79
+ - **Explicit content** -- `{ kind: "content", content: "...", contentType?: "text/html", path?: "page.html" }`
80
+ - **Source URL** -- `{ kind: "source_url", sourceUrl: "https://example.com/page" }`
81
+ - **File bundle** -- `{ kind: "files", files: [{ path, content?, contentBase64?, bytes?, contentType? }], entry? }`
82
+
83
+ Drop **settings** (title, visibility, password, noindex, expiresAt, slug, metadata) go in the second `options` argument, not in the input object.
78
84
 
79
85
  All inputs are uploaded through staged presigned URLs. The SDK handles this transparently.
80
86
 
87
+ ### Explicit input examples
88
+
89
+ ```typescript
90
+ // Inline content with explicit MIME type
91
+ await dropthis.publish({
92
+ kind: "content",
93
+ content: "<h1>Hello</h1>",
94
+ contentType: "text/html",
95
+ });
96
+
97
+ // Fetch and re-publish a remote URL
98
+ await dropthis.publish({
99
+ kind: "source_url",
100
+ sourceUrl: "https://example.com/report",
101
+ });
102
+
103
+ // Multi-file bundle with explicit entry point
104
+ await dropthis.publish(
105
+ {
106
+ kind: "files",
107
+ files: [
108
+ { path: "index.html", content: "<h1>Hello</h1>" },
109
+ { path: "style.css", content: "body { margin: 0; }" },
110
+ ],
111
+ entry: "index.html",
112
+ },
113
+ { title: "My Site" },
114
+ );
115
+ ```
116
+
117
+ ## Prepare (validate without sending)
118
+
119
+ `prepare()` resolves and validates the input locally, returning the prepared request object without making any API calls. It throws `PublishInputError` on invalid input (e.g. missing file).
120
+
121
+ ```typescript
122
+ import { Dropthis, PublishInputError } from "@dropthis/node";
123
+
124
+ try {
125
+ const prepared = await dropthis.prepare("./dist");
126
+ console.log("Ready to publish:", prepared.kind);
127
+ } catch (e) {
128
+ if (e instanceof PublishInputError) {
129
+ console.error("Bad input:", e.message);
130
+ }
131
+ }
132
+ ```
133
+
81
134
  ## Error handling
82
135
 
83
- All methods return `{ data, error, headers }`. Check `error` before using `data`.
136
+ All methods return `DropthisResult<T>` -- either `{ data: T, error: null, headers }` or `{ data: null, error, headers }`. API errors never throw; check `error` before using `data`.
84
137
 
85
138
  ```typescript
86
139
  const result = await dropthis.drops.get("drop_abc123");
87
140
 
88
141
  if (result.error) {
89
142
  console.error(result.error.code, result.error.message);
90
- // error.statusCode, error.requestId also available
143
+ // Also available: error.statusCode, error.requestId, error.suggestion,
144
+ // error.retryable, error.param, error.currentRevision
91
145
  } else {
92
146
  console.log(result.data);
93
147
  }
94
148
  ```
95
149
 
150
+ Local input validation errors (e.g. `file_not_found`) are also returned as `{ error: { code: "file_not_found", ... } }` rather than thrown -- except for `prepare()`, which throws `PublishInputError`.
151
+
96
152
  ## Configuration
97
153
 
98
154
  ```typescript
99
155
  const dropthis = new Dropthis({
100
- apiKey: "sk_...", // Required. Defaults to DROPTHIS_API_KEY env var.
101
- baseUrl: "https://...", // Override API base URL.
102
- timeoutMs: 30_000, // Request timeout in milliseconds.
103
- fetch: customFetch, // Custom fetch implementation.
156
+ apiKey: "sk_...", // Required. Defaults to DROPTHIS_API_KEY env var.
157
+ baseUrl: "https://...", // Override API base URL.
158
+ timeoutMs: 30_000, // Request timeout in milliseconds (default: 30s).
159
+ uploadTimeoutMs: 120_000, // Timeout for signed-PUT file uploads (default: 120s).
160
+ fetch: customFetch, // Custom fetch implementation.
104
161
  });
105
162
  ```
106
163
 
@@ -117,8 +174,6 @@ const dropthis = new Dropthis("sk_...");
117
174
  ```typescript
118
175
  await dropthis.drops.list({ limit: 20 });
119
176
  await dropthis.drops.get("drop_abc123");
120
- await dropthis.drops.create({ uploadId: "upl_abc123", options: { title: "New" } });
121
- await dropthis.drops.createRaw({ upload_id: "upl_abc123" }); // no snake_case conversion
122
177
  await dropthis.drops.update("drop_abc123", { title: "Updated" });
123
178
  await dropthis.drops.delete("drop_abc123");
124
179
  ```
@@ -135,11 +190,11 @@ for await (const drop of page.data) {
135
190
  }
136
191
  ```
137
192
 
193
+ > To change a drop's content, use `client.deploy(dropId, newInput)`. `drops.update()` is for settings only (title, visibility, password, noindex, expiresAt, slug, metadata).
194
+
138
195
  ### deployments
139
196
 
140
197
  ```typescript
141
- await dropthis.deployments.create("drop_abc123", { uploadId: "upl_xyz789" });
142
- await dropthis.deployments.createRaw("drop_abc123", { upload_id: "upl_xyz789" });
143
198
  await dropthis.deployments.list("drop_abc123");
144
199
  await dropthis.deployments.get("drop_abc123", "dep_xyz789");
145
200
  ```
@@ -154,10 +209,6 @@ await dropthis.uploads.create({
154
209
  files: [{ path: "index.html", contentType: "text/html", sizeBytes: 1024 }],
155
210
  });
156
211
  await dropthis.uploads.get("upl_abc123");
157
- await dropthis.uploads.createPartTargets("upl_abc123", {
158
- fileId: "file_xyz",
159
- partNumbers: [1, 2, 3],
160
- });
161
212
  await dropthis.uploads.complete("upl_abc123", { files: {} });
162
213
  await dropthis.uploads.cancel("upl_abc123");
163
214
  ```
@@ -186,6 +237,21 @@ await dropthis.account.update({ displayName: "Jane Doe" });
186
237
  await dropthis.account.delete();
187
238
  ```
188
239
 
240
+ ## Cloudflare Workers (edge)
241
+
242
+ Use the fs-free entry point for Cloudflare Workers and other edge runtimes. It does not import `node:fs`, `node:path`, or `node:crypto`.
243
+
244
+ ```typescript
245
+ import { DropthisEdge } from "@dropthis/node/edge";
246
+
247
+ const dropthis = new DropthisEdge({ apiKey: env.DROPTHIS_API_KEY });
248
+ const { data, error } = await dropthis.publish("<h1>Hello from the edge</h1>");
249
+ ```
250
+
251
+ `DropthisEdge` accepts the in-memory subset of `PublishInput`: inline strings, `Uint8Array`, `URL`, and the explicit `{ kind: "content" }`, `{ kind: "source_url" }`, and `{ kind: "files" }` forms. Local file paths and `string[]` path arrays are not supported (no filesystem on the edge).
252
+
253
+ `DropthisEdge` exposes the same `publish(input, options?)` and `deploy(dropId, input, options?)` methods, plus the `drops`, `deployments`, `account`, and `apiKeys` resource accessors.
254
+
189
255
  ## Types
190
256
 
191
257
  Key types exported from the package:
@@ -201,6 +267,8 @@ import type {
201
267
  PrepareOptions,
202
268
  RequestControls,
203
269
  PublishOptions,
270
+ PublishInput,
271
+ PublishFileInput,
204
272
  ListPage,
205
273
  CreateUploadSessionRequest,
206
274
  CreateUploadSessionResponse,
@@ -209,7 +277,11 @@ import type {
209
277
 
210
278
  ## Agent skills
211
279
 
212
- For AI coding agents (Cursor, Claude Code, Windsurf, etc.), this SDK ships with a built-in agent skill at `skills/dropthis-node/SKILL.md`. For the CLI skill, see the [CLI repo](https://github.com/dropthis-dev/dropthis-cli).
280
+ For AI coding agents (Cursor, Claude Code, Windsurf, etc.), install the [dropthis-skills](https://github.com/dropthis-dev/dropthis-skills) package:
281
+
282
+ ```bash
283
+ npx skills add dropthis-dev/dropthis-skills
284
+ ```
213
285
 
214
286
  ## Links
215
287
 
@@ -0,0 +1,394 @@
1
+ type DropthisClientOptions = {
2
+ apiKey?: string;
3
+ baseUrl?: string;
4
+ timeoutMs?: number;
5
+ uploadTimeoutMs?: number;
6
+ fetch?: typeof globalThis.fetch;
7
+ };
8
+ type RequestOptions = {
9
+ authenticated?: boolean;
10
+ idempotencyKey?: string;
11
+ ifRevision?: number;
12
+ timeoutMs?: number;
13
+ };
14
+ type DropthisErrorResponse = {
15
+ code: string;
16
+ message: string;
17
+ statusCode: number | null;
18
+ type?: string;
19
+ title?: string;
20
+ detail?: string | null;
21
+ instance?: string | null;
22
+ param?: string | null;
23
+ currentRevision?: number;
24
+ requestId?: string | null;
25
+ suggestion?: string | null;
26
+ retryable?: boolean | null;
27
+ body?: unknown;
28
+ };
29
+ type DropthisResult<T> = {
30
+ data: T;
31
+ error: null;
32
+ headers: Record<string, string>;
33
+ } | {
34
+ data: null;
35
+ error: DropthisErrorResponse;
36
+ headers: Record<string, string>;
37
+ };
38
+ type ActionResolve = {
39
+ method: string;
40
+ url?: string | null;
41
+ endpoint?: string | null;
42
+ };
43
+ type DropAction = {
44
+ code: string;
45
+ kind: "api" | "human";
46
+ priority: "required" | "suggested";
47
+ message: string;
48
+ resolve?: ActionResolve | null;
49
+ };
50
+ type TierInfo = {
51
+ name: string;
52
+ maxSizeBytes: number;
53
+ ttlDays: number | null;
54
+ persistent: boolean;
55
+ badge: boolean;
56
+ };
57
+ type Limitations = {
58
+ actions: DropAction[];
59
+ };
60
+ type DropResponse = {
61
+ id: string;
62
+ slug: string;
63
+ url: string;
64
+ deploymentId: string | null;
65
+ title: string;
66
+ contentType: string;
67
+ visibility: string;
68
+ status: string;
69
+ revision: number;
70
+ contentRevision: number;
71
+ accessRevision: number;
72
+ sizeBytes: number;
73
+ renderMode: string;
74
+ warnings: Array<Record<string, unknown>>;
75
+ createdAt: string;
76
+ expiresAt: string | null;
77
+ object: string;
78
+ accessible: boolean;
79
+ persistent: boolean;
80
+ badgeApplied: boolean;
81
+ tier: TierInfo;
82
+ limitations: Limitations;
83
+ };
84
+ type DropDeploymentResponse = {
85
+ id: string;
86
+ dropId: string;
87
+ revision: number;
88
+ status: string;
89
+ storagePrefix: string;
90
+ entry: string | null;
91
+ contentType: string;
92
+ renderMode: string;
93
+ files: Array<Record<string, unknown>>;
94
+ warnings: Array<Record<string, unknown>>;
95
+ sizeBytes: number;
96
+ classificationVersion: number;
97
+ classificationReason: string;
98
+ errorCode: string | null;
99
+ errorMessage: string | null;
100
+ createdAt: string;
101
+ readyAt: string | null;
102
+ publishedAt: string | null;
103
+ };
104
+ type ListDeploymentsParams = {
105
+ cursor?: string | null;
106
+ limit?: number;
107
+ };
108
+ type ListDeploymentsResponse = {
109
+ deployments: DropDeploymentResponse[];
110
+ nextCursor: string | null;
111
+ };
112
+ type ListPage<T> = {
113
+ object: "list";
114
+ data: T[];
115
+ hasMore: boolean;
116
+ nextCursor: string | null;
117
+ autoPagingToArray(options?: {
118
+ limit?: number;
119
+ }): Promise<T[]>;
120
+ [Symbol.asyncIterator](): AsyncIterableIterator<T>;
121
+ };
122
+ type Action = {
123
+ code: string;
124
+ kind: string;
125
+ method?: string | null;
126
+ endpoint?: string | null;
127
+ message: string;
128
+ };
129
+ type EmailOtpResponse = {
130
+ ok: true;
131
+ expiresIn: number;
132
+ nextAction?: Action | null;
133
+ };
134
+ type SessionResponse = {
135
+ object: "session";
136
+ token: string;
137
+ accountId: string;
138
+ isNewAccount: boolean;
139
+ expiresIn: number;
140
+ };
141
+ type ApiKeyResponse = {
142
+ object: "api_key";
143
+ id: string;
144
+ keyLast4: string;
145
+ label: string;
146
+ createdAt: string;
147
+ };
148
+ type ApiKeyCreatedResponse = ApiKeyResponse & {
149
+ key: string;
150
+ accountId?: string | null;
151
+ isNewAccount?: boolean;
152
+ };
153
+ type AccountResponse = {
154
+ id: string;
155
+ email: string;
156
+ displayName: string | null;
157
+ plan: string;
158
+ status: string;
159
+ createdAt: string;
160
+ };
161
+ type ListDropsParams = {
162
+ cursor?: string | null;
163
+ limit?: number;
164
+ };
165
+ type UploadManifestFile = {
166
+ path: string;
167
+ contentType: string;
168
+ sizeBytes: number;
169
+ checksumSha256?: string | null;
170
+ };
171
+ type CreateUploadSessionRequest = {
172
+ schemaVersion?: 1;
173
+ files: UploadManifestFile[];
174
+ entry?: string | null;
175
+ };
176
+ type UploadTarget = {
177
+ strategy: "single_put" | "multipart";
178
+ url: string;
179
+ headers: Record<string, string>;
180
+ expiresAt: string;
181
+ };
182
+ type CreateUploadSessionFileResponse = {
183
+ fileId: string;
184
+ path: string;
185
+ objectKey: string;
186
+ upload: UploadTarget;
187
+ };
188
+ type UploadSessionFileResponse = {
189
+ fileId: string;
190
+ path: string;
191
+ contentType: string;
192
+ sizeBytes: number;
193
+ objectKey: string;
194
+ verified: boolean;
195
+ };
196
+ type UploadSessionResponse = {
197
+ uploadId: string;
198
+ status: string;
199
+ expiresAt: string;
200
+ entry?: string | null;
201
+ files: UploadSessionFileResponse[];
202
+ nextAction?: Action | null;
203
+ };
204
+ type CreateUploadSessionResponse = {
205
+ uploadId: string;
206
+ expiresAt: string;
207
+ files: CreateUploadSessionFileResponse[];
208
+ nextAction?: Action | null;
209
+ };
210
+ type CompleteUploadSessionRequest = {
211
+ files?: Record<string, {
212
+ parts?: Array<{
213
+ partNumber: number;
214
+ etag: string;
215
+ }> | null;
216
+ }>;
217
+ };
218
+ type DropOptions = {
219
+ /** Change the vanity slug (update only). */
220
+ slug?: string;
221
+ /** Drop title. */
222
+ title?: string;
223
+ /** public (default) or unlisted. */
224
+ visibility?: "public" | "unlisted";
225
+ /** Require password to view. Pass `null` to remove password protection. */
226
+ password?: string | null;
227
+ /** Prevent search-engine indexing. Pass `null` to allow indexing (default). */
228
+ noindex?: boolean | null;
229
+ /** Auto-delete after this ISO 8601 date. Pass `null` to clear. */
230
+ expiresAt?: string | Date | null;
231
+ /** Entry file for multi-file bundles (default: index.html). */
232
+ entry?: string;
233
+ /** Attach JSON key-value pairs, e.g. `{ source: "ci" }`. */
234
+ metadata?: Record<string, unknown>;
235
+ };
236
+ type PrepareOptions = {
237
+ /** Glob patterns to ignore when publishing directories. */
238
+ ignore?: string[];
239
+ /** Disable default ignore patterns. */
240
+ ignoreDefaults?: boolean;
241
+ /** Override MIME type (auto-detected from extension). */
242
+ contentType?: string;
243
+ /** Set filename when publishing from stdin or bytes. */
244
+ path?: string;
245
+ };
246
+ type RequestControls = {
247
+ /** Prevent duplicate publishes on retry (auto-generated by CLI). */
248
+ idempotencyKey?: string;
249
+ /** Fail if current revision doesn't match -- optimistic lock (update only). */
250
+ ifRevision?: number;
251
+ };
252
+ type PublishOptions = DropOptions & PrepareOptions & RequestControls;
253
+ type PublishFileInput = {
254
+ path: string;
255
+ contentType?: string;
256
+ content?: string;
257
+ contentBase64?: string;
258
+ bytes?: Uint8Array;
259
+ };
260
+ type PublishInput = string | string[] | URL | Uint8Array | {
261
+ kind: "content";
262
+ content: string;
263
+ contentType?: string;
264
+ path?: string;
265
+ } | {
266
+ kind: "source_url";
267
+ sourceUrl: string;
268
+ } | {
269
+ kind: "files";
270
+ files: PublishFileInput[];
271
+ entry?: string;
272
+ };
273
+
274
+ declare class Transport {
275
+ readonly apiKey: string | undefined;
276
+ readonly baseUrl: string;
277
+ readonly timeoutMs: number;
278
+ readonly uploadTimeoutMs: number;
279
+ readonly fetchImpl: typeof globalThis.fetch;
280
+ constructor(options?: DropthisClientOptions | string);
281
+ putSignedUrl(url: string, body: Uint8Array | Blob | ReadableStream, headers: Record<string, string>): Promise<DropthisResult<{
282
+ etag: string | null;
283
+ }>>;
284
+ request<T>(method: string, path: string, options?: RequestOptions & {
285
+ body?: unknown;
286
+ bodyCase?: "snake" | "raw";
287
+ params?: Record<string, string | number | boolean | null | undefined>;
288
+ }): Promise<DropthisResult<T>>;
289
+ }
290
+
291
+ /**
292
+ * A file ready to be staged for upload. The body is lazy: the orchestrator
293
+ * calls `getBody()` per file when it is ready to push bytes to the signed URL.
294
+ * This keeps the resolution layer pure and lets the filesystem layer (node.ts)
295
+ * defer reads/streams until upload time.
296
+ */
297
+ type PreparedUploadFile = {
298
+ path: string;
299
+ contentType: string;
300
+ sizeBytes: number;
301
+ checksumSha256?: string;
302
+ getBody(): Promise<Uint8Array | Blob | ReadableStream>;
303
+ };
304
+ type PreparedPublishRequest = {
305
+ kind: "staged";
306
+ manifest: CreateUploadSessionRequest;
307
+ files: PreparedUploadFile[];
308
+ options: Record<string, unknown>;
309
+ metadata?: Record<string, unknown>;
310
+ } | {
311
+ kind: "source";
312
+ sourceUrl: string;
313
+ options: Record<string, unknown>;
314
+ metadata?: Record<string, unknown>;
315
+ };
316
+ /**
317
+ * The canonical publish inputs that can be resolved with no filesystem access:
318
+ * the {@link PublishInput} union minus `string[]` (which is inherently a list
319
+ * of filesystem paths handled by node.ts).
320
+ */
321
+ type InMemoryPublishInput = string | URL | Uint8Array | {
322
+ kind: "content";
323
+ content: string;
324
+ contentType?: string;
325
+ path?: string;
326
+ } | {
327
+ kind: "source_url";
328
+ sourceUrl: string;
329
+ } | {
330
+ kind: "files";
331
+ files: PublishFileInput[];
332
+ entry?: string;
333
+ };
334
+
335
+ declare class AccountResource {
336
+ private readonly transport;
337
+ constructor(transport: Transport);
338
+ get(): Promise<DropthisResult<AccountResponse>>;
339
+ update(input: {
340
+ displayName: string | null;
341
+ }): Promise<DropthisResult<AccountResponse>>;
342
+ delete(): Promise<DropthisResult<null>>;
343
+ }
344
+
345
+ declare class ApiKeysResource {
346
+ private readonly transport;
347
+ constructor(transport: Transport);
348
+ list(): Promise<DropthisResult<{
349
+ object: "list";
350
+ data: ApiKeyResponse[];
351
+ }>>;
352
+ create(input: {
353
+ label: string;
354
+ }): Promise<DropthisResult<ApiKeyCreatedResponse>>;
355
+ delete(keyId: string): Promise<DropthisResult<{
356
+ ok: true;
357
+ }>>;
358
+ }
359
+
360
+ declare class DeploymentsResource {
361
+ private readonly transport;
362
+ constructor(transport: Transport);
363
+ list(dropId: string, params?: ListDeploymentsParams): Promise<DropthisResult<ListDeploymentsResponse>>;
364
+ get(dropId: string, deploymentId: string): Promise<DropthisResult<DropDeploymentResponse>>;
365
+ }
366
+
367
+ declare class CursorPage<T> implements ListPage<T> {
368
+ readonly object: "list";
369
+ readonly data: T[];
370
+ readonly hasMore: boolean;
371
+ readonly nextCursor: string | null;
372
+ private readonly fetchNextPage;
373
+ constructor(input: {
374
+ data: T[];
375
+ hasMore: boolean;
376
+ nextCursor: string | null;
377
+ fetchNextPage?: (() => Promise<DropthisResult<CursorPage<T>>>) | undefined;
378
+ });
379
+ autoPagingToArray(options?: {
380
+ limit?: number;
381
+ }): Promise<T[]>;
382
+ [Symbol.asyncIterator](): AsyncIterableIterator<T>;
383
+ }
384
+
385
+ declare class DropsResource {
386
+ private readonly transport;
387
+ constructor(transport: Transport);
388
+ list(params?: ListDropsParams): Promise<DropthisResult<CursorPage<DropResponse>>>;
389
+ get(dropId: string): Promise<DropthisResult<DropResponse>>;
390
+ update(dropId: string, options?: DropOptions & RequestControls): Promise<DropthisResult<DropResponse>>;
391
+ delete(dropId: string): Promise<DropthisResult<null>>;
392
+ }
393
+
394
+ export { AccountResource as A, type CompleteUploadSessionRequest as C, DeploymentsResource as D, type EmailOtpResponse as E, type InMemoryPublishInput as I, type Limitations as L, type PrepareOptions as P, type RequestControls as R, type SessionResponse as S, type TierInfo as T, type UploadManifestFile as U, type ActionResolve as a, ApiKeysResource as b, type CreateUploadSessionRequest as c, type CreateUploadSessionResponse as d, CursorPage as e, type DropAction as f, type DropDeploymentResponse as g, type DropOptions as h, type DropResponse as i, DropsResource as j, type DropthisClientOptions as k, type DropthisErrorResponse as l, type DropthisResult as m, type ListDeploymentsParams as n, type ListDeploymentsResponse as o, type ListPage as p, type PreparedPublishRequest as q, type PreparedUploadFile as r, type PublishFileInput as s, type PublishInput as t, type PublishOptions as u, type RequestOptions as v, Transport as w, type UploadSessionFileResponse as x, type UploadSessionResponse as y, type UploadTarget as z };