@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.
@@ -0,0 +1,778 @@
1
+ // src/errors.ts
2
+ var API_KEY_RE = /sk_[A-Za-z0-9_-]+/g;
3
+ function redactSecrets(message) {
4
+ return message.replace(API_KEY_RE, "sk_[redacted]");
5
+ }
6
+ function createErrorResult(code, message, statusCode, extra = {}) {
7
+ return {
8
+ data: null,
9
+ error: {
10
+ code,
11
+ message: redactSecrets(message),
12
+ statusCode,
13
+ ...extra.type ? { type: extra.type } : {},
14
+ ...extra.title !== void 0 ? { title: extra.title } : {},
15
+ ...extra.detail !== void 0 ? { detail: extra.detail } : {},
16
+ ...extra.instance !== void 0 ? { instance: extra.instance } : {},
17
+ ...extra.param !== void 0 ? { param: extra.param } : {},
18
+ ...extra.currentRevision !== void 0 ? { currentRevision: extra.currentRevision } : {},
19
+ ...extra.requestId !== void 0 ? { requestId: extra.requestId } : {},
20
+ ...extra.suggestion !== void 0 ? { suggestion: extra.suggestion } : {},
21
+ ...extra.retryable !== void 0 ? { retryable: extra.retryable } : {},
22
+ ...extra.body !== void 0 ? { body: extra.body } : {}
23
+ },
24
+ headers: extra.headers ?? {}
25
+ };
26
+ }
27
+ var PublishInputError = class extends Error {
28
+ constructor(code, message, param, suggestion) {
29
+ super(message);
30
+ this.code = code;
31
+ this.param = param;
32
+ this.suggestion = suggestion;
33
+ this.name = "PublishInputError";
34
+ }
35
+ code;
36
+ param;
37
+ suggestion;
38
+ };
39
+ function toErrorResult(err) {
40
+ if (err instanceof PublishInputError)
41
+ return createErrorResult(err.code, err.message, null, {
42
+ param: err.param ?? null,
43
+ suggestion: err.suggestion ?? null
44
+ });
45
+ return createErrorResult(
46
+ "sdk_error",
47
+ err instanceof Error ? err.message : "Unknown SDK error",
48
+ null
49
+ );
50
+ }
51
+
52
+ // src/publish/detect.ts
53
+ function isHttpUrl(value) {
54
+ try {
55
+ const url = new URL(value);
56
+ return url.protocol === "http:" || url.protocol === "https:";
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+ function containsHtmlTag(value) {
62
+ return /<[a-z!/][^>]*>/i.test(value);
63
+ }
64
+ function contentTypeForString(value, override) {
65
+ if (override) return override;
66
+ return containsHtmlTag(value) ? "text/html" : "text/plain; charset=utf-8";
67
+ }
68
+ function detectBytesContentType(bytes) {
69
+ const text = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
70
+ if (text.includes("\uFFFD")) return "application/octet-stream";
71
+ return containsHtmlTag(text) ? "text/html" : "text/plain; charset=utf-8";
72
+ }
73
+ function entryForContentType(contentType) {
74
+ const m = contentType.toLowerCase().split(";", 1)[0]?.trim() ?? "";
75
+ if (m === "text/html" || m === "application/xhtml+xml") return "index.html";
76
+ if (m === "application/json") return "index.json";
77
+ if (m === "text/css") return "index.css";
78
+ if (m === "text/javascript" || m === "application/javascript")
79
+ return "index.js";
80
+ if (m.startsWith("text/")) return "index.txt";
81
+ return "index";
82
+ }
83
+
84
+ // src/publish/paths.ts
85
+ function normalizeManifestPath(path) {
86
+ return path.replace(/\\/g, "/").replace(/^\.\//, "");
87
+ }
88
+ function assertValidManifestPath(path) {
89
+ if (path.includes("\\"))
90
+ throw new PublishInputError(
91
+ "invalid_path",
92
+ `Manifest path must use "/": "${path}".`,
93
+ path
94
+ );
95
+ const p = normalizeManifestPath(path);
96
+ if (p === "")
97
+ throw new PublishInputError(
98
+ "invalid_path",
99
+ "Manifest path must not be empty.",
100
+ path
101
+ );
102
+ if (p.startsWith("/"))
103
+ throw new PublishInputError(
104
+ "invalid_path",
105
+ `Manifest path must be relative: "${path}".`,
106
+ path
107
+ );
108
+ if (p.startsWith("~"))
109
+ throw new PublishInputError(
110
+ "invalid_path",
111
+ `Manifest path must not start with "~": "${path}".`,
112
+ path
113
+ );
114
+ if (p.split("/").includes(".."))
115
+ throw new PublishInputError(
116
+ "invalid_path",
117
+ `Manifest path must not contain "..": "${path}".`,
118
+ path
119
+ );
120
+ }
121
+ function assertNoDuplicatePaths(paths) {
122
+ const seen = /* @__PURE__ */ new Set();
123
+ for (const raw of paths) {
124
+ const p = normalizeManifestPath(raw);
125
+ if (seen.has(p))
126
+ throw new PublishInputError(
127
+ "duplicate_path",
128
+ `Duplicate file path after normalization: "${p}".`,
129
+ p,
130
+ "Give each file a unique path; basename mapping for string[] can collide."
131
+ );
132
+ seen.add(p);
133
+ }
134
+ }
135
+ function assertNonEmptyBundle(paths) {
136
+ if (paths.length === 0)
137
+ throw new PublishInputError(
138
+ "empty_bundle",
139
+ "Nothing to publish: the bundle has no files.",
140
+ void 0,
141
+ "Pass at least one file, or a directory that contains files."
142
+ );
143
+ }
144
+
145
+ // src/publish/core.ts
146
+ var VALID_VISIBILITIES = ["public", "unlisted"];
147
+ var MAX_TITLE_LENGTH = 500;
148
+ function validatePublishOptions(options) {
149
+ if (options.title !== void 0 && options.title !== null && options.title.length > MAX_TITLE_LENGTH) {
150
+ throw new Error(`Title must be ${MAX_TITLE_LENGTH} characters or less.`);
151
+ }
152
+ if (options.visibility !== void 0 && options.visibility !== null) {
153
+ if (!VALID_VISIBILITIES.includes(options.visibility)) {
154
+ throw new Error(
155
+ `Invalid visibility "${options.visibility}". Use "public" or "unlisted".`
156
+ );
157
+ }
158
+ }
159
+ if (options.expiresAt !== void 0 && options.expiresAt !== null) {
160
+ if (options.expiresAt instanceof Date) {
161
+ if (Number.isNaN(options.expiresAt.getTime())) {
162
+ throw new Error("Invalid expiresAt date.");
163
+ }
164
+ } else if (typeof options.expiresAt === "string") {
165
+ const parsed = new Date(options.expiresAt);
166
+ if (Number.isNaN(parsed.getTime())) {
167
+ throw new Error("Invalid expiresAt date.");
168
+ }
169
+ }
170
+ }
171
+ }
172
+ function optionsBody(options) {
173
+ validatePublishOptions(options);
174
+ return {
175
+ title: options.title,
176
+ visibility: options.visibility,
177
+ password: options.password,
178
+ noindex: options.noindex,
179
+ expiresAt: options.expiresAt instanceof Date ? options.expiresAt.toISOString() : options.expiresAt
180
+ };
181
+ }
182
+ function buildStagedRequest(files, options, entry) {
183
+ assertNonEmptyBundle(files.map((f) => f.path));
184
+ const normalizedPaths = files.map((f) => normalizeManifestPath(f.path));
185
+ assertNoDuplicatePaths(normalizedPaths);
186
+ const manifestFiles = files.map((file) => ({
187
+ path: normalizeManifestPath(file.path),
188
+ contentType: file.contentType,
189
+ sizeBytes: file.sizeBytes,
190
+ ...file.checksumSha256 ? { checksumSha256: file.checksumSha256 } : {}
191
+ }));
192
+ const resolvedEntry = entry ?? options.entry;
193
+ if (resolvedEntry !== void 0) assertValidManifestPath(resolvedEntry);
194
+ const manifest = {
195
+ schemaVersion: 1,
196
+ files: manifestFiles,
197
+ ...resolvedEntry ? { entry: resolvedEntry } : {}
198
+ };
199
+ const prepared = {
200
+ kind: "staged",
201
+ manifest,
202
+ files,
203
+ options: optionsBody(options)
204
+ };
205
+ if (options.metadata) prepared.metadata = options.metadata;
206
+ return prepared;
207
+ }
208
+ function buildSourceRequest(sourceUrl, options) {
209
+ if (!isHttpUrl(sourceUrl)) {
210
+ throw new PublishInputError(
211
+ "invalid_source_url",
212
+ "source_url must be an http(s) URL.",
213
+ sourceUrl,
214
+ "Fetch the content first, then pass it as content/files."
215
+ );
216
+ }
217
+ const prepared = {
218
+ kind: "source",
219
+ sourceUrl,
220
+ options: optionsBody(options)
221
+ };
222
+ if (options.metadata) prepared.metadata = options.metadata;
223
+ return prepared;
224
+ }
225
+ function decodeBase64(value) {
226
+ const binary = atob(value);
227
+ return Uint8Array.from(binary, (c) => c.charCodeAt(0));
228
+ }
229
+ function fileBytes(file) {
230
+ if (file.content !== void 0) return new TextEncoder().encode(file.content);
231
+ if (file.contentBase64 !== void 0) return decodeBase64(file.contentBase64);
232
+ if (file.bytes !== void 0) return file.bytes;
233
+ throw new PublishInputError(
234
+ "missing_file_bytes",
235
+ `File "${file.path}" is missing content bytes.`,
236
+ file.path,
237
+ "Provide one of content, contentBase64, or bytes."
238
+ );
239
+ }
240
+ function singleStagedFile(path, contentType, bytes, options) {
241
+ return buildStagedRequest(
242
+ [
243
+ {
244
+ path,
245
+ contentType,
246
+ sizeBytes: bytes.byteLength,
247
+ getBody: async () => bytes
248
+ }
249
+ ],
250
+ options
251
+ );
252
+ }
253
+ async function resolveInMemory(input, options = {}) {
254
+ if (input instanceof URL) {
255
+ return buildSourceRequest(input.toString(), options);
256
+ }
257
+ if (typeof input === "string") {
258
+ if (isHttpUrl(input)) return buildSourceRequest(input, options);
259
+ const contentType = contentTypeForString(input, options.contentType);
260
+ if (options.path !== void 0) assertValidManifestPath(options.path);
261
+ const path = options.path ?? entryForContentType(contentType);
262
+ const bytes = new TextEncoder().encode(input);
263
+ return singleStagedFile(path, contentType, bytes, options);
264
+ }
265
+ if (input instanceof Uint8Array) {
266
+ const contentType = options.contentType ?? detectBytesContentType(input);
267
+ if (options.path !== void 0) assertValidManifestPath(options.path);
268
+ const path = options.path ?? entryForContentType(contentType);
269
+ return singleStagedFile(path, contentType, input, options);
270
+ }
271
+ if (input.kind === "content") {
272
+ const contentType = input.contentType ?? options.contentType ?? "text/html";
273
+ if (input.path !== void 0) assertValidManifestPath(input.path);
274
+ if (options.path !== void 0) assertValidManifestPath(options.path);
275
+ const path = input.path ?? options.path ?? entryForContentType(contentType);
276
+ const bytes = new TextEncoder().encode(input.content);
277
+ return singleStagedFile(path, contentType, bytes, options);
278
+ }
279
+ if (input.kind === "source_url") {
280
+ return buildSourceRequest(input.sourceUrl, options);
281
+ }
282
+ const files = input.files.map((file) => {
283
+ assertValidManifestPath(file.path);
284
+ const bytes = fileBytes(file);
285
+ const contentType = file.contentType ?? detectBytesContentType(bytes);
286
+ return {
287
+ path: file.path,
288
+ contentType,
289
+ sizeBytes: bytes.byteLength,
290
+ getBody: async () => bytes
291
+ };
292
+ });
293
+ return buildStagedRequest(files, options, input.entry);
294
+ }
295
+ function errorResult(result) {
296
+ if (!result.error) throw new Error("Expected an error result");
297
+ return { data: null, error: result.error, headers: result.headers };
298
+ }
299
+ async function publishStaged(transport, prepared, finalPath, options = {}) {
300
+ const baseKey = options.idempotencyKey ?? `sdk-${crypto.randomUUID()}`;
301
+ const createResult = await transport.request(
302
+ "POST",
303
+ "/uploads",
304
+ {
305
+ body: prepared.manifest,
306
+ idempotencyKey: `${baseKey}:create-upload`
307
+ }
308
+ );
309
+ if (createResult.error) return errorResult(createResult);
310
+ for (const file of prepared.files) {
311
+ const target = createResult.data.files.find((f) => f.path === file.path);
312
+ if (!target) {
313
+ return createErrorResult(
314
+ "upload_target_missing",
315
+ `Upload target missing for ${file.path}`,
316
+ null
317
+ );
318
+ }
319
+ if (target.upload.strategy !== "single_put") {
320
+ return createErrorResult(
321
+ "unsupported_upload_strategy",
322
+ `Unsupported upload strategy "${target.upload.strategy}" for ${file.path}. This SDK uploads via single PUT only.`,
323
+ null
324
+ );
325
+ }
326
+ const put = await transport.putSignedUrl(
327
+ target.upload.url,
328
+ await file.getBody(),
329
+ target.upload.headers
330
+ );
331
+ if (put.error) return errorResult(put);
332
+ }
333
+ const completeResult = await transport.request(
334
+ "POST",
335
+ `/uploads/${encodeURIComponent(createResult.data.uploadId)}/complete`,
336
+ {
337
+ body: { files: {} },
338
+ idempotencyKey: `${baseKey}:complete-upload`
339
+ }
340
+ );
341
+ if (completeResult.error) return errorResult(completeResult);
342
+ const finalOptions = {
343
+ body: {
344
+ uploadId: createResult.data.uploadId,
345
+ options: prepared.options,
346
+ ...prepared.metadata ? { metadata: prepared.metadata } : {}
347
+ },
348
+ idempotencyKey: `${baseKey}:publish`
349
+ };
350
+ if (options.ifRevision !== void 0)
351
+ finalOptions.ifRevision = options.ifRevision;
352
+ return transport.request("POST", finalPath, finalOptions);
353
+ }
354
+ async function publishSource(transport, prepared, finalPath, options = {}) {
355
+ const baseKey = options.idempotencyKey ?? `sdk-${crypto.randomUUID()}`;
356
+ return transport.request("POST", finalPath, {
357
+ body: {
358
+ sourceUrl: prepared.sourceUrl,
359
+ options: prepared.options,
360
+ ...prepared.metadata ? { metadata: prepared.metadata } : {}
361
+ },
362
+ idempotencyKey: `${baseKey}:publish`,
363
+ ...options.ifRevision !== void 0 ? { ifRevision: options.ifRevision } : {}
364
+ });
365
+ }
366
+
367
+ // src/resources/account.ts
368
+ var AccountResource = class {
369
+ constructor(transport) {
370
+ this.transport = transport;
371
+ }
372
+ transport;
373
+ get() {
374
+ return this.transport.request("GET", "/account");
375
+ }
376
+ update(input) {
377
+ return this.transport.request("PATCH", "/account", { body: input });
378
+ }
379
+ delete() {
380
+ return this.transport.request("DELETE", "/account");
381
+ }
382
+ };
383
+
384
+ // src/resources/api-keys.ts
385
+ var ApiKeysResource = class {
386
+ constructor(transport) {
387
+ this.transport = transport;
388
+ }
389
+ transport;
390
+ list() {
391
+ return this.transport.request("GET", "/api-keys");
392
+ }
393
+ create(input) {
394
+ return this.transport.request("POST", "/api-keys", { body: input });
395
+ }
396
+ delete(keyId) {
397
+ return this.transport.request(
398
+ "DELETE",
399
+ `/api-keys/${encodeURIComponent(keyId)}`
400
+ );
401
+ }
402
+ };
403
+
404
+ // src/resources/deployments.ts
405
+ var DeploymentsResource = class {
406
+ constructor(transport) {
407
+ this.transport = transport;
408
+ }
409
+ transport;
410
+ list(dropId, params = {}) {
411
+ return this.transport.request(
412
+ "GET",
413
+ `/drops/${encodeURIComponent(dropId)}/deployments`,
414
+ { params }
415
+ );
416
+ }
417
+ get(dropId, deploymentId) {
418
+ return this.transport.request(
419
+ "GET",
420
+ `/drops/${encodeURIComponent(dropId)}/deployments/${encodeURIComponent(deploymentId)}`
421
+ );
422
+ }
423
+ };
424
+
425
+ // src/pagination.ts
426
+ var CursorPage = class {
427
+ object = "list";
428
+ data;
429
+ hasMore;
430
+ nextCursor;
431
+ fetchNextPage;
432
+ constructor(input) {
433
+ this.data = input.data;
434
+ this.hasMore = input.hasMore;
435
+ this.nextCursor = input.nextCursor;
436
+ this.fetchNextPage = input.fetchNextPage;
437
+ }
438
+ async autoPagingToArray(options = {}) {
439
+ const items = [];
440
+ for await (const item of this) {
441
+ items.push(item);
442
+ if (options.limit !== void 0 && items.length >= options.limit) break;
443
+ }
444
+ return items;
445
+ }
446
+ async *[Symbol.asyncIterator]() {
447
+ for (const item of this.data) yield item;
448
+ let current = this;
449
+ while (current.hasMore && current.fetchNextPage) {
450
+ const next = await current.fetchNextPage();
451
+ if (next.error) break;
452
+ current = next.data;
453
+ for (const item of current.data) yield item;
454
+ }
455
+ }
456
+ };
457
+
458
+ // src/resources/drops.ts
459
+ var DropsResource = class {
460
+ constructor(transport) {
461
+ this.transport = transport;
462
+ }
463
+ transport;
464
+ async list(params = {}) {
465
+ const result = await this.transport.request("GET", "/drops", {
466
+ params
467
+ });
468
+ if (result.error) return result;
469
+ const page = new CursorPage({
470
+ data: result.data.drops,
471
+ nextCursor: result.data.nextCursor,
472
+ hasMore: result.data.nextCursor !== null,
473
+ fetchNextPage: result.data.nextCursor ? () => this.list({ ...params, cursor: result.data.nextCursor }) : void 0
474
+ });
475
+ return { data: page, error: null, headers: result.headers };
476
+ }
477
+ get(dropId) {
478
+ return this.transport.request(
479
+ "GET",
480
+ `/drops/${encodeURIComponent(dropId)}`
481
+ );
482
+ }
483
+ update(dropId, options = {}) {
484
+ const requestOptions = {
485
+ body: updateBody(options)
486
+ };
487
+ if (options.idempotencyKey)
488
+ requestOptions.idempotencyKey = options.idempotencyKey;
489
+ if (options.ifRevision !== void 0)
490
+ requestOptions.ifRevision = options.ifRevision;
491
+ return this.transport.request(
492
+ "PATCH",
493
+ `/drops/${encodeURIComponent(dropId)}`,
494
+ requestOptions
495
+ );
496
+ }
497
+ delete(dropId) {
498
+ return this.transport.request(
499
+ "DELETE",
500
+ `/drops/${encodeURIComponent(dropId)}`
501
+ );
502
+ }
503
+ };
504
+ var SETTINGS = [
505
+ "title",
506
+ "visibility",
507
+ "password",
508
+ "noindex",
509
+ "expiresAt",
510
+ "slug"
511
+ ];
512
+ function updateBody(options) {
513
+ const out = {};
514
+ for (const key of SETTINGS)
515
+ if (options[key] !== void 0) out[key] = options[key];
516
+ return {
517
+ options: out,
518
+ ...options.metadata !== void 0 ? { metadata: options.metadata } : {}
519
+ };
520
+ }
521
+
522
+ // src/case.ts
523
+ function toCamelKey(key) {
524
+ return key.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
525
+ }
526
+ function toSnakeKey(key) {
527
+ return key.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`);
528
+ }
529
+ var PASSTHROUGH_KEYS = /* @__PURE__ */ new Set(["metadata"]);
530
+ function toCamelCase(value) {
531
+ if (Array.isArray(value)) return value.map(toCamelCase);
532
+ if (value && typeof value === "object") {
533
+ return Object.fromEntries(
534
+ Object.entries(value).map(([key, item]) => [
535
+ toCamelKey(key),
536
+ PASSTHROUGH_KEYS.has(key) ? item : toCamelCase(item)
537
+ ])
538
+ );
539
+ }
540
+ return value;
541
+ }
542
+ function toSnakeCase(value) {
543
+ if (Array.isArray(value)) return value.map(toSnakeCase);
544
+ if (value && typeof value === "object") {
545
+ return Object.fromEntries(
546
+ Object.entries(value).filter(([, item]) => item !== void 0).map(([key, item]) => [
547
+ toSnakeKey(key),
548
+ PASSTHROUGH_KEYS.has(key) ? item : toSnakeCase(item)
549
+ ])
550
+ );
551
+ }
552
+ return value;
553
+ }
554
+
555
+ // src/transport.ts
556
+ var DEFAULT_BASE_URL = "https://api.dropthis.app";
557
+ var SDK_VERSION = "0.6.0";
558
+ var Transport = class {
559
+ apiKey;
560
+ baseUrl;
561
+ timeoutMs;
562
+ uploadTimeoutMs;
563
+ fetchImpl;
564
+ constructor(options = {}) {
565
+ const resolved = typeof options === "string" ? { apiKey: options } : options;
566
+ this.apiKey = resolved.apiKey ?? (typeof process !== "undefined" ? process.env?.DROPTHIS_API_KEY : void 0);
567
+ this.baseUrl = (resolved.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
568
+ this.timeoutMs = resolved.timeoutMs ?? 3e4;
569
+ this.uploadTimeoutMs = resolved.uploadTimeoutMs ?? 12e4;
570
+ this.fetchImpl = resolved.fetch ?? globalThis.fetch;
571
+ }
572
+ async putSignedUrl(url, body, headers) {
573
+ const controller = new AbortController();
574
+ const timeout = setTimeout(() => controller.abort(), this.uploadTimeoutMs);
575
+ try {
576
+ const request = {
577
+ method: "PUT",
578
+ headers,
579
+ body,
580
+ signal: controller.signal
581
+ };
582
+ if (body instanceof ReadableStream) request.duplex = "half";
583
+ const response = await this.fetchImpl(url, request);
584
+ const responseHeaders = headersToObject(response.headers);
585
+ if (!response.ok) {
586
+ const text = await response.text();
587
+ return createErrorResult(
588
+ `signed_upload_${response.status}`,
589
+ text || response.statusText,
590
+ response.status,
591
+ { headers: responseHeaders }
592
+ );
593
+ }
594
+ return {
595
+ data: {
596
+ etag: response.headers.get("ETag") ?? responseHeaders.etag ?? null
597
+ },
598
+ error: null,
599
+ headers: responseHeaders
600
+ };
601
+ } catch (error) {
602
+ const message = error instanceof Error && error.name === "AbortError" ? "Request timed out" : error instanceof Error ? error.message : "Network error";
603
+ return createErrorResult(
604
+ error instanceof Error && error.name === "AbortError" ? "timeout" : "network_error",
605
+ message,
606
+ null
607
+ );
608
+ } finally {
609
+ clearTimeout(timeout);
610
+ }
611
+ }
612
+ async request(method, path, options = {}) {
613
+ const authenticated = options.authenticated ?? true;
614
+ if (authenticated && !this.apiKey) {
615
+ return createErrorResult(
616
+ "missing_api_key",
617
+ "No API key provided. Pass apiKey or set DROPTHIS_API_KEY.",
618
+ null
619
+ );
620
+ }
621
+ const headers = {
622
+ "user-agent": `dropthis-node/${SDK_VERSION}`
623
+ };
624
+ if (authenticated && this.apiKey)
625
+ headers.authorization = `Bearer ${this.apiKey}`;
626
+ if (options.idempotencyKey)
627
+ headers["idempotency-key"] = options.idempotencyKey;
628
+ if (options.ifRevision !== void 0)
629
+ headers["if-revision"] = String(options.ifRevision);
630
+ if (options.body !== void 0 && !(options.body instanceof FormData))
631
+ headers["content-type"] = "application/json";
632
+ const url = new URL(`${this.baseUrl}${path}`);
633
+ const wireParams = options.bodyCase === "raw" ? options.params : toSnakeCase(options.params ?? {});
634
+ for (const [key, value] of Object.entries(wireParams ?? {})) {
635
+ if (value !== void 0 && value !== null)
636
+ url.searchParams.set(key, String(value));
637
+ }
638
+ const controller = new AbortController();
639
+ const timeout = setTimeout(
640
+ () => controller.abort(),
641
+ options.timeoutMs ?? this.timeoutMs
642
+ );
643
+ try {
644
+ const request = {
645
+ method,
646
+ headers,
647
+ signal: controller.signal
648
+ };
649
+ if (options.body instanceof FormData) {
650
+ request.body = options.body;
651
+ } else if (options.body !== void 0) {
652
+ const wireBody = options.bodyCase === "raw" ? options.body : toSnakeCase(options.body);
653
+ request.body = JSON.stringify(wireBody);
654
+ }
655
+ const response = await this.fetchImpl(url.toString(), request);
656
+ const responseHeaders = headersToObject(response.headers);
657
+ const text = await response.text();
658
+ const parsed = parseJson(text);
659
+ if (!response.ok) {
660
+ const body = parsed && typeof parsed === "object" ? parsed : {};
661
+ const message = stringValue(body.detail) ?? stringValue(body.title) ?? response.statusText;
662
+ const code = stringValue(body.code) ?? stringValue(body.error_code) ?? `http_${response.status}`;
663
+ const currentRevision = numberValue(body.current_revision);
664
+ const type = stringValue(body.type);
665
+ const title = stringValue(body.title);
666
+ const instance = stringValue(body.instance);
667
+ return createErrorResult(code, message, response.status, {
668
+ body,
669
+ ...type !== null ? { type } : {},
670
+ ...title !== null ? { title } : {},
671
+ ...instance !== null ? { instance } : {},
672
+ detail: stringValue(body.detail),
673
+ param: stringValue(body.param),
674
+ ...currentRevision !== void 0 ? { currentRevision } : {},
675
+ requestId: stringValue(body.request_id) ?? responseHeaders["x-request-id"] ?? null,
676
+ suggestion: stringValue(body.suggestion),
677
+ retryable: booleanValue(body.retryable),
678
+ headers: responseHeaders
679
+ });
680
+ }
681
+ return {
682
+ data: toCamelCase(parsed),
683
+ error: null,
684
+ headers: responseHeaders
685
+ };
686
+ } catch (error) {
687
+ const message = error instanceof Error && error.name === "AbortError" ? "Request timed out" : error instanceof Error ? error.message : "Network error";
688
+ return createErrorResult(
689
+ error instanceof Error && error.name === "AbortError" ? "timeout" : "network_error",
690
+ message,
691
+ null
692
+ );
693
+ } finally {
694
+ clearTimeout(timeout);
695
+ }
696
+ }
697
+ };
698
+ function headersToObject(headers) {
699
+ const result = {};
700
+ headers.forEach((value, key) => {
701
+ result[key.toLowerCase()] = value;
702
+ });
703
+ return result;
704
+ }
705
+ function parseJson(text) {
706
+ if (!text) return null;
707
+ try {
708
+ return JSON.parse(text);
709
+ } catch {
710
+ return text;
711
+ }
712
+ }
713
+ function stringValue(value) {
714
+ return typeof value === "string" ? value : null;
715
+ }
716
+ function numberValue(value) {
717
+ return typeof value === "number" ? value : void 0;
718
+ }
719
+ function booleanValue(value) {
720
+ return typeof value === "boolean" ? value : null;
721
+ }
722
+
723
+ // src/edge.ts
724
+ var DropthisEdge = class {
725
+ transport;
726
+ dropsResource;
727
+ accountResource;
728
+ apiKeysResource;
729
+ deploymentsResource;
730
+ constructor(options = {}) {
731
+ this.transport = new Transport(options);
732
+ }
733
+ get drops() {
734
+ if (!this.dropsResource)
735
+ this.dropsResource = new DropsResource(this.transport);
736
+ return this.dropsResource;
737
+ }
738
+ get account() {
739
+ if (!this.accountResource)
740
+ this.accountResource = new AccountResource(this.transport);
741
+ return this.accountResource;
742
+ }
743
+ get apiKeys() {
744
+ if (!this.apiKeysResource)
745
+ this.apiKeysResource = new ApiKeysResource(this.transport);
746
+ return this.apiKeysResource;
747
+ }
748
+ get deployments() {
749
+ if (!this.deploymentsResource)
750
+ this.deploymentsResource = new DeploymentsResource(this.transport);
751
+ return this.deploymentsResource;
752
+ }
753
+ /** Publish a NEW drop from in-memory content. */
754
+ async publish(input, options = {}) {
755
+ let prepared;
756
+ try {
757
+ prepared = await resolveInMemory(input, options);
758
+ } catch (e) {
759
+ return toErrorResult(e);
760
+ }
761
+ return prepared.kind === "source" ? publishSource(this.transport, prepared, "/drops", options) : publishStaged(this.transport, prepared, "/drops", options);
762
+ }
763
+ /** Publish a NEW content version to an existing drop, keeping its URL. */
764
+ async deploy(dropId, input, options = {}) {
765
+ let prepared;
766
+ try {
767
+ prepared = await resolveInMemory(input, options);
768
+ } catch (e) {
769
+ return toErrorResult(e);
770
+ }
771
+ const path = `/drops/${encodeURIComponent(dropId)}/deployments`;
772
+ return prepared.kind === "source" ? publishSource(this.transport, prepared, path, options) : publishStaged(this.transport, prepared, path, options);
773
+ }
774
+ };
775
+ export {
776
+ DropthisEdge
777
+ };
778
+ //# sourceMappingURL=edge.mjs.map