@dropthis/cli 0.20.0 → 0.22.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.
@@ -185,11 +185,13 @@ The `drops.publish()` and `drops.updateContent()` methods accept:
185
185
  - **URL object** -- `new URL("https://example.com/page")` (source fetch)
186
186
  - **Bytes** -- `new Uint8Array(...)` (raw bytes)
187
187
  - **Explicit content** -- `{ kind: "content", content: "...", contentType?: "text/html", path?: "page.html" }`
188
- - **Source URL** -- `{ kind: "source_url", sourceUrl: "https://example.com/page" }`
189
- - **File bundle** -- `{ kind: "files", files: [{ path, content?, contentBase64?, bytes?, contentType? }], entry? }`
188
+ - **Source URL** -- `{ kind: "source_url", sourceUrl: "https://example.com/page" }` (the server fetches it; an HTML page becomes a site, any other file becomes a single-file drop with a `rawUrl`)
189
+ - **File bundle** -- `{ kind: "files", files: [{ path, content? | contentBase64? | bytes? | sourceUrl?, contentType? }], entry? }`. Give each file its bytes inline (`content`/`contentBase64`/`bytes`) **or** a `sourceUrl` for the server to fetch — never both on one file. Mix them freely in one bundle.
190
190
 
191
191
  Drop **settings** (title, visibility, password, noindex, expiresAt, metadata) go in the second `options` argument, not in the input object.
192
192
 
193
+ > **A `source_url` to a non-HTML file now becomes a single-file drop.** Point a top-level source URL (`new URL(...)` or `{ kind: "source_url" }`) at an image, PDF, or text file and you get a single-file `file_viewer` drop with `rawUrl` set — the same as publishing those bytes directly. Previously a source URL only worked for HTML pages.
194
+
193
195
  All inputs are uploaded through staged presigned URLs — one signed PUT per file, up to 5 files in parallel. The SDK handles this transparently.
194
196
 
195
197
  ### Explicit input examples
@@ -220,6 +222,22 @@ await dropthis.drops.publish(
220
222
  },
221
223
  { title: "My Site" },
222
224
  );
225
+
226
+ // Publish by reference: inline your HTML, let the server fetch the images.
227
+ // Each `sourceUrl` file is fetched server-side (no bytes pass through your
228
+ // process) and lands at its path in the bundle — one self-contained drop.
229
+ await dropthis.drops.publish({
230
+ kind: "files",
231
+ files: [
232
+ {
233
+ path: "index.html",
234
+ content: '<h1>Gallery</h1><img src="hero.png"><img src="logo.svg">',
235
+ },
236
+ { path: "hero.png", sourceUrl: "https://cdn.example.com/hero.png" },
237
+ { path: "logo.svg", sourceUrl: "https://cdn.example.com/logo.svg" },
238
+ ],
239
+ entry: "index.html",
240
+ });
223
241
  ```
224
242
 
225
243
  ## Prepare (validate without sending)
@@ -102,7 +102,6 @@ type DropDeploymentResponse = {
102
102
  dropId: string;
103
103
  revision: number;
104
104
  status: string;
105
- storagePrefix: string;
106
105
  entry: string | null;
107
106
  contentType: string;
108
107
  renderMode: string;
@@ -204,11 +203,33 @@ type ListDropsParams = {
204
203
  /** Only drops mounted on this custom domain hostname (e.g. "reports.example.com"). */
205
204
  domain?: string;
206
205
  };
206
+ /**
207
+ * One file in an upload manifest. A discriminated union:
208
+ *
209
+ * - **client-put** — the SDK pushes the bytes through a signed PUT. Requires
210
+ * `contentType` and `sizeBytes`; an optional `checksumSha256` locks the
211
+ * transfer integrity.
212
+ * - **remote** — the server fetches the bytes itself from `sourceUrl` during
213
+ * `POST /uploads/{id}/ingest`. No bytes leave this process, so `sizeBytes`,
214
+ * `contentType`, and `checksumSha256` are all optional (the server infers
215
+ * them on fetch). Carries NO signed PUT target.
216
+ *
217
+ * `sourceUrl` is the discriminant: present ⇒ remote, absent ⇒ client-put. The
218
+ * camelCase `sourceUrl` becomes the wire field `source_url` via the transport's
219
+ * snake_case conversion (exactly like `contentType` → `content_type`).
220
+ */
207
221
  type UploadManifestFile = {
208
222
  path: string;
209
223
  contentType: string;
210
224
  sizeBytes: number;
211
225
  checksumSha256?: string | null;
226
+ sourceUrl?: never;
227
+ } | {
228
+ path: string;
229
+ sourceUrl: string;
230
+ contentType?: string;
231
+ sizeBytes?: number;
232
+ checksumSha256?: string | null;
212
233
  };
213
234
  type CreateUploadSessionRequest = {
214
235
  schemaVersion?: 1;
@@ -315,13 +336,61 @@ type PublishOptions = DropOptions & PrepareOptions & RequestControls;
315
336
  type UpdateContentOptions = PrepareOptions & RequestControls & {
316
337
  /** Entry file for multi-file bundles (default: index.html). */
317
338
  entry?: string;
339
+ /**
340
+ * How the supplied files combine with what the drop already serves
341
+ * (partial-by-default, ADR 0065):
342
+ *
343
+ * - `"patch"` (default): the supplied files upsert by path and every
344
+ * unmentioned file is carried forward, so editing one file never drops the
345
+ * rest. Use `deletePaths` to remove files.
346
+ * - `"replace"`: the supplied files become the drop's entire content set —
347
+ * a full swap. Anything not supplied is gone. `deletePaths` is invalid here.
348
+ *
349
+ * Omit to default to `"patch"`.
350
+ */
351
+ mode?: "patch" | "replace";
352
+ /**
353
+ * Paths to remove from the drop's content (patch-mode only). Each path must
354
+ * exist or the server rejects the update (loud, never a silent no-op). Invalid
355
+ * with `mode: "replace"`. Wire field: `delete_paths`.
356
+ */
357
+ deletePaths?: string[];
318
358
  };
359
+ /**
360
+ * One file in a multi-file `{ kind: "files" }` bundle. Supply the bytes inline
361
+ * exactly one way — `content` (UTF-8 text), `contentBase64`, or `bytes` — OR set
362
+ * `sourceUrl` to a public http(s) URL and let the server fetch that file for you
363
+ * server-side (no bytes pass through your process). A single entry may NOT carry
364
+ * both inline bytes and `sourceUrl`. Mix freely within one bundle: e.g. inline
365
+ * `content` for `index.html` plus a `sourceUrl` for each image referenced by it,
366
+ * yielding one self-contained drop.
367
+ */
319
368
  type PublishFileInput = {
320
369
  path: string;
321
370
  contentType?: string;
322
371
  content?: string;
323
372
  contentBase64?: string;
324
373
  bytes?: Uint8Array;
374
+ /**
375
+ * Public http(s) URL the server fetches this file's bytes from during publish
376
+ * (publish by reference). Mutually exclusive with `content`/`contentBase64`/`bytes`.
377
+ */
378
+ sourceUrl?: string;
379
+ /**
380
+ * Declared byte size of the remote file (optional hint for `sourceUrl` files).
381
+ * When provided, the server uses this for upfront quota admission before fetching,
382
+ * so a large file is rejected immediately rather than after the server downloads it.
383
+ * Ignored for inline files (size is computed from the bytes).
384
+ */
385
+ sizeBytes?: number;
386
+ /**
387
+ * Expected SHA-256 hex digest of the remote file (optional hint for `sourceUrl` files).
388
+ * When provided, the server verifies the fetched content matches this checksum and
389
+ * rejects the publish if it does not, giving you integrity verification without
390
+ * downloading the bytes yourself.
391
+ * Ignored for inline files (checksum is computed by the SDK/server from actual bytes).
392
+ */
393
+ checksumSha256?: string;
325
394
  };
326
395
  type PublishInput = string | string[] | URL | Uint8Array | {
327
396
  kind: "content";
@@ -485,17 +554,32 @@ declare class Transport {
485
554
  }
486
555
 
487
556
  /**
488
- * A file ready to be staged for upload. The body is lazy: the orchestrator
489
- * calls `getBody()` per file when it is ready to push bytes to the signed URL.
490
- * This keeps the resolution layer pure and lets the filesystem layer (node.ts)
491
- * defer reads/streams until upload time.
557
+ * A file ready to be staged for upload. Two shapes:
558
+ *
559
+ * - **client-put** the SDK pushes the bytes. The body is lazy: the orchestrator
560
+ * calls `getBody()` per file when it is ready to push bytes to the signed URL.
561
+ * This keeps the resolution layer pure and lets the filesystem layer (node.ts)
562
+ * defer reads/streams until upload time.
563
+ * - **remote** (`sourceUrl` set, no `getBody`) — the server fetches the bytes from
564
+ * `sourceUrl` during `POST /uploads/{id}/ingest`. No bytes leave this process, so
565
+ * it has no `getBody`, no `sizeBytes`, and consumes no signed PUT target.
492
566
  */
493
567
  type PreparedUploadFile = {
494
568
  path: string;
495
569
  contentType: string;
496
570
  sizeBytes: number;
497
571
  checksumSha256?: string;
572
+ sourceUrl?: never;
498
573
  getBody(): Promise<Uint8Array | Blob | ReadableStream>;
574
+ } | {
575
+ path: string;
576
+ sourceUrl: string;
577
+ contentType?: string;
578
+ /** Optional hint: declared byte size for upfront quota admission (server-side). */
579
+ sizeBytes?: number;
580
+ /** Optional hint: expected SHA-256 hex digest for server-side integrity check. */
581
+ checksumSha256?: string;
582
+ getBody?: never;
499
583
  };
500
584
  /**
501
585
  * Resolves a publish input into a {@link PreparedPublishRequest}. Two implementations exist:
@@ -102,7 +102,6 @@ type DropDeploymentResponse = {
102
102
  dropId: string;
103
103
  revision: number;
104
104
  status: string;
105
- storagePrefix: string;
106
105
  entry: string | null;
107
106
  contentType: string;
108
107
  renderMode: string;
@@ -204,11 +203,33 @@ type ListDropsParams = {
204
203
  /** Only drops mounted on this custom domain hostname (e.g. "reports.example.com"). */
205
204
  domain?: string;
206
205
  };
206
+ /**
207
+ * One file in an upload manifest. A discriminated union:
208
+ *
209
+ * - **client-put** — the SDK pushes the bytes through a signed PUT. Requires
210
+ * `contentType` and `sizeBytes`; an optional `checksumSha256` locks the
211
+ * transfer integrity.
212
+ * - **remote** — the server fetches the bytes itself from `sourceUrl` during
213
+ * `POST /uploads/{id}/ingest`. No bytes leave this process, so `sizeBytes`,
214
+ * `contentType`, and `checksumSha256` are all optional (the server infers
215
+ * them on fetch). Carries NO signed PUT target.
216
+ *
217
+ * `sourceUrl` is the discriminant: present ⇒ remote, absent ⇒ client-put. The
218
+ * camelCase `sourceUrl` becomes the wire field `source_url` via the transport's
219
+ * snake_case conversion (exactly like `contentType` → `content_type`).
220
+ */
207
221
  type UploadManifestFile = {
208
222
  path: string;
209
223
  contentType: string;
210
224
  sizeBytes: number;
211
225
  checksumSha256?: string | null;
226
+ sourceUrl?: never;
227
+ } | {
228
+ path: string;
229
+ sourceUrl: string;
230
+ contentType?: string;
231
+ sizeBytes?: number;
232
+ checksumSha256?: string | null;
212
233
  };
213
234
  type CreateUploadSessionRequest = {
214
235
  schemaVersion?: 1;
@@ -315,13 +336,61 @@ type PublishOptions = DropOptions & PrepareOptions & RequestControls;
315
336
  type UpdateContentOptions = PrepareOptions & RequestControls & {
316
337
  /** Entry file for multi-file bundles (default: index.html). */
317
338
  entry?: string;
339
+ /**
340
+ * How the supplied files combine with what the drop already serves
341
+ * (partial-by-default, ADR 0065):
342
+ *
343
+ * - `"patch"` (default): the supplied files upsert by path and every
344
+ * unmentioned file is carried forward, so editing one file never drops the
345
+ * rest. Use `deletePaths` to remove files.
346
+ * - `"replace"`: the supplied files become the drop's entire content set —
347
+ * a full swap. Anything not supplied is gone. `deletePaths` is invalid here.
348
+ *
349
+ * Omit to default to `"patch"`.
350
+ */
351
+ mode?: "patch" | "replace";
352
+ /**
353
+ * Paths to remove from the drop's content (patch-mode only). Each path must
354
+ * exist or the server rejects the update (loud, never a silent no-op). Invalid
355
+ * with `mode: "replace"`. Wire field: `delete_paths`.
356
+ */
357
+ deletePaths?: string[];
318
358
  };
359
+ /**
360
+ * One file in a multi-file `{ kind: "files" }` bundle. Supply the bytes inline
361
+ * exactly one way — `content` (UTF-8 text), `contentBase64`, or `bytes` — OR set
362
+ * `sourceUrl` to a public http(s) URL and let the server fetch that file for you
363
+ * server-side (no bytes pass through your process). A single entry may NOT carry
364
+ * both inline bytes and `sourceUrl`. Mix freely within one bundle: e.g. inline
365
+ * `content` for `index.html` plus a `sourceUrl` for each image referenced by it,
366
+ * yielding one self-contained drop.
367
+ */
319
368
  type PublishFileInput = {
320
369
  path: string;
321
370
  contentType?: string;
322
371
  content?: string;
323
372
  contentBase64?: string;
324
373
  bytes?: Uint8Array;
374
+ /**
375
+ * Public http(s) URL the server fetches this file's bytes from during publish
376
+ * (publish by reference). Mutually exclusive with `content`/`contentBase64`/`bytes`.
377
+ */
378
+ sourceUrl?: string;
379
+ /**
380
+ * Declared byte size of the remote file (optional hint for `sourceUrl` files).
381
+ * When provided, the server uses this for upfront quota admission before fetching,
382
+ * so a large file is rejected immediately rather than after the server downloads it.
383
+ * Ignored for inline files (size is computed from the bytes).
384
+ */
385
+ sizeBytes?: number;
386
+ /**
387
+ * Expected SHA-256 hex digest of the remote file (optional hint for `sourceUrl` files).
388
+ * When provided, the server verifies the fetched content matches this checksum and
389
+ * rejects the publish if it does not, giving you integrity verification without
390
+ * downloading the bytes yourself.
391
+ * Ignored for inline files (checksum is computed by the SDK/server from actual bytes).
392
+ */
393
+ checksumSha256?: string;
325
394
  };
326
395
  type PublishInput = string | string[] | URL | Uint8Array | {
327
396
  kind: "content";
@@ -485,17 +554,32 @@ declare class Transport {
485
554
  }
486
555
 
487
556
  /**
488
- * A file ready to be staged for upload. The body is lazy: the orchestrator
489
- * calls `getBody()` per file when it is ready to push bytes to the signed URL.
490
- * This keeps the resolution layer pure and lets the filesystem layer (node.ts)
491
- * defer reads/streams until upload time.
557
+ * A file ready to be staged for upload. Two shapes:
558
+ *
559
+ * - **client-put** the SDK pushes the bytes. The body is lazy: the orchestrator
560
+ * calls `getBody()` per file when it is ready to push bytes to the signed URL.
561
+ * This keeps the resolution layer pure and lets the filesystem layer (node.ts)
562
+ * defer reads/streams until upload time.
563
+ * - **remote** (`sourceUrl` set, no `getBody`) — the server fetches the bytes from
564
+ * `sourceUrl` during `POST /uploads/{id}/ingest`. No bytes leave this process, so
565
+ * it has no `getBody`, no `sizeBytes`, and consumes no signed PUT target.
492
566
  */
493
567
  type PreparedUploadFile = {
494
568
  path: string;
495
569
  contentType: string;
496
570
  sizeBytes: number;
497
571
  checksumSha256?: string;
572
+ sourceUrl?: never;
498
573
  getBody(): Promise<Uint8Array | Blob | ReadableStream>;
574
+ } | {
575
+ path: string;
576
+ sourceUrl: string;
577
+ contentType?: string;
578
+ /** Optional hint: declared byte size for upfront quota admission (server-side). */
579
+ sizeBytes?: number;
580
+ /** Optional hint: expected SHA-256 hex digest for server-side integrity check. */
581
+ checksumSha256?: string;
582
+ getBody?: never;
499
583
  };
500
584
  /**
501
585
  * Resolves a publish input into a {@link PreparedPublishRequest}. Two implementations exist:
@@ -220,18 +220,34 @@ function updateContentOptions(options) {
220
220
  if (options.idempotencyKey !== void 0)
221
221
  out.idempotencyKey = options.idempotencyKey;
222
222
  if (options.ifRevision !== void 0) out.ifRevision = options.ifRevision;
223
+ if (options.mode !== void 0) out.mode = options.mode;
224
+ if (options.deletePaths !== void 0) out.deletePaths = options.deletePaths;
223
225
  return out;
224
226
  }
225
227
  function buildStagedRequest(files, options, entry) {
226
228
  assertNonEmptyBundle(files.map((f) => f.path));
227
229
  const normalizedPaths = files.map((f) => normalizeManifestPath(f.path));
228
230
  assertNoDuplicatePaths(normalizedPaths);
229
- const manifestFiles = files.map((file) => ({
230
- path: normalizeManifestPath(file.path),
231
- contentType: file.contentType,
232
- sizeBytes: file.sizeBytes,
233
- ...file.checksumSha256 ? { checksumSha256: file.checksumSha256 } : {}
234
- }));
231
+ const manifestFiles = files.map(
232
+ (file) => file.sourceUrl !== void 0 ? (
233
+ // Remote entry: the server fetches the bytes on /ingest. Emit sourceUrl
234
+ // plus optional metadata hints (contentType, sizeBytes, checksumSha256)
235
+ // when the caller declared them. The transport snake_cases all camelCase
236
+ // keys to source_url / content_type / size_bytes / checksum_sha256.
237
+ {
238
+ path: normalizeManifestPath(file.path),
239
+ sourceUrl: file.sourceUrl,
240
+ ...file.contentType ? { contentType: file.contentType } : {},
241
+ ...file.sizeBytes !== void 0 ? { sizeBytes: file.sizeBytes } : {},
242
+ ...file.checksumSha256 ? { checksumSha256: file.checksumSha256 } : {}
243
+ }
244
+ ) : {
245
+ path: normalizeManifestPath(file.path),
246
+ contentType: file.contentType,
247
+ sizeBytes: file.sizeBytes,
248
+ ...file.checksumSha256 ? { checksumSha256: file.checksumSha256 } : {}
249
+ }
250
+ );
235
251
  const resolvedEntry = entry ?? options.entry;
236
252
  if (resolvedEntry !== void 0) assertValidManifestPath(resolvedEntry);
237
253
  const manifest = {
@@ -277,9 +293,48 @@ function fileBytes(file) {
277
293
  "missing_file_bytes",
278
294
  `File "${file.path}" is missing content bytes.`,
279
295
  file.path,
280
- "Provide one of content, contentBase64, or bytes."
296
+ "Provide one of content, contentBase64, or bytes, or set sourceUrl."
281
297
  );
282
298
  }
299
+ function hasInlineBytes(file) {
300
+ return file.content !== void 0 || file.contentBase64 !== void 0 || file.bytes !== void 0;
301
+ }
302
+ function prepareBundleFile(file) {
303
+ assertValidManifestPath(file.path);
304
+ if (file.sourceUrl !== void 0) {
305
+ if (hasInlineBytes(file)) {
306
+ throw new PublishInputError(
307
+ "conflicting_file_source",
308
+ `File "${file.path}" sets both inline content and sourceUrl; pick one.`,
309
+ file.path,
310
+ "Drop sourceUrl to upload bytes, or drop content/contentBase64/bytes to fetch by reference."
311
+ );
312
+ }
313
+ if (!isHttpUrl(file.sourceUrl)) {
314
+ throw new PublishInputError(
315
+ "invalid_source_url",
316
+ `File "${file.path}" sourceUrl must be an http(s) URL.`,
317
+ file.path,
318
+ "Fetch the bytes first and pass them inline, or use an http(s) URL."
319
+ );
320
+ }
321
+ return {
322
+ path: file.path,
323
+ sourceUrl: file.sourceUrl,
324
+ ...file.contentType ? { contentType: file.contentType } : {},
325
+ ...file.sizeBytes !== void 0 ? { sizeBytes: file.sizeBytes } : {},
326
+ ...file.checksumSha256 ? { checksumSha256: file.checksumSha256 } : {}
327
+ };
328
+ }
329
+ const bytes = fileBytes(file);
330
+ const contentType = file.contentType ?? detectBytesContentType(bytes);
331
+ return {
332
+ path: file.path,
333
+ contentType,
334
+ sizeBytes: bytes.byteLength,
335
+ getBody: async () => bytes
336
+ };
337
+ }
283
338
  function singleStagedFile(path, contentType, bytes, options) {
284
339
  return buildStagedRequest(
285
340
  [
@@ -322,20 +377,17 @@ async function resolveInMemory(input, options = {}) {
322
377
  if (input.kind === "source_url") {
323
378
  return buildSourceRequest(input.sourceUrl, options);
324
379
  }
325
- const files = input.files.map((file) => {
326
- assertValidManifestPath(file.path);
327
- const bytes = fileBytes(file);
328
- const contentType = file.contentType ?? detectBytesContentType(bytes);
329
- return {
330
- path: file.path,
331
- contentType,
332
- sizeBytes: bytes.byteLength,
333
- getBody: async () => bytes
334
- };
335
- });
380
+ const files = input.files.map(prepareBundleFile);
336
381
  return buildStagedRequest(files, options, input.entry);
337
382
  }
383
+ function partialUpdateBody(options) {
384
+ return {
385
+ ...options.mode !== void 0 ? { mode: options.mode } : {},
386
+ ...options.deletePaths !== void 0 ? { deletePaths: options.deletePaths } : {}
387
+ };
388
+ }
338
389
  var UPLOAD_CONCURRENCY = 5;
390
+ var INGEST_TIMEOUT_MS = 12e4;
339
391
  function errorResult(result) {
340
392
  if (!result.error) throw new Error("Expected an error result");
341
393
  return { data: null, error: result.error, headers: result.headers };
@@ -391,6 +443,7 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
391
443
  if (createResult.error) return errorResult(createResult);
392
444
  const jobs = [];
393
445
  for (const file of prepared.files) {
446
+ if (file.sourceUrl !== void 0) continue;
394
447
  const target = createResult.data.files.find((f) => f.path === file.path);
395
448
  if (!target) {
396
449
  return createErrorResult(
@@ -410,6 +463,18 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
410
463
  }
411
464
  const uploadFailure = await uploadStagedFiles(transport, jobs);
412
465
  if (uploadFailure) return uploadFailure;
466
+ const hasRemote = prepared.files.some((f) => f.sourceUrl !== void 0);
467
+ if (hasRemote) {
468
+ const ingestResult = await transport.request(
469
+ "POST",
470
+ `/uploads/${encodeURIComponent(createResult.data.uploadId)}/ingest`,
471
+ {
472
+ idempotencyKey: `${baseKey}:ingest-upload`,
473
+ timeoutMs: INGEST_TIMEOUT_MS
474
+ }
475
+ );
476
+ if (ingestResult.error) return errorResult(ingestResult);
477
+ }
413
478
  const completeResult = await transport.request(
414
479
  "POST",
415
480
  `/uploads/${encodeURIComponent(createResult.data.uploadId)}/complete`,
@@ -422,7 +487,8 @@ async function publishStaged(transport, prepared, finalPath, options = {}) {
422
487
  body: {
423
488
  uploadId: createResult.data.uploadId,
424
489
  ...Object.keys(prepared.options).length > 0 ? { options: prepared.options } : {},
425
- ...prepared.metadata ? { metadata: prepared.metadata } : {}
490
+ ...prepared.metadata ? { metadata: prepared.metadata } : {},
491
+ ...partialUpdateBody(options)
426
492
  },
427
493
  idempotencyKey: `${baseKey}:publish`
428
494
  };
@@ -436,7 +502,8 @@ async function publishSource(transport, prepared, finalPath, options = {}) {
436
502
  body: {
437
503
  sourceUrl: prepared.sourceUrl,
438
504
  ...Object.keys(prepared.options).length > 0 ? { options: prepared.options } : {},
439
- ...prepared.metadata ? { metadata: prepared.metadata } : {}
505
+ ...prepared.metadata ? { metadata: prepared.metadata } : {},
506
+ ...partialUpdateBody(options)
440
507
  },
441
508
  idempotencyKey: `${baseKey}:publish`,
442
509
  ...options.ifRevision !== void 0 ? { ifRevision: options.ifRevision } : {}
@@ -804,7 +871,7 @@ function toSnakeCase(value) {
804
871
 
805
872
  // src/transport.ts
806
873
  var DEFAULT_BASE_URL = "https://api.dropthis.app";
807
- var SDK_VERSION = "0.18.0";
874
+ var SDK_VERSION = "0.20.0";
808
875
  var Transport = class {
809
876
  apiKey;
810
877
  baseUrl;