@bigbucket-ai/sdk 0.0.0-stage → 0.2.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/dist/index.js ADDED
@@ -0,0 +1,556 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AuthenticationError: () => AuthenticationError,
24
+ BigBucket: () => BigBucket,
25
+ BigBucketError: () => BigBucketError,
26
+ HttpTransport: () => HttpTransport,
27
+ ImagesResource: () => ImagesResource,
28
+ NotFoundError: () => NotFoundError,
29
+ PayloadTooLargeError: () => PayloadTooLargeError,
30
+ RateLimitError: () => RateLimitError,
31
+ StorageUploadError: () => StorageUploadError,
32
+ TimeoutError: () => TimeoutError,
33
+ UnsupportedFormatError: () => UnsupportedFormatError,
34
+ UploadsResource: () => UploadsResource,
35
+ UsageResource: () => UsageResource,
36
+ buildTransformUrl: () => buildTransformUrl
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/errors.ts
41
+ var BigBucketError = class extends Error {
42
+ statusCode;
43
+ code;
44
+ requestId;
45
+ details;
46
+ isRetryable;
47
+ constructor(options) {
48
+ super(options.message);
49
+ this.name = "BigBucketError";
50
+ this.statusCode = options.statusCode ?? 500;
51
+ this.code = options.code ?? "INTERNAL_SERVER_ERROR";
52
+ this.requestId = options.requestId;
53
+ this.details = options.details;
54
+ this.cause = options.cause;
55
+ this.isRetryable = this.statusCode === 429 || this.statusCode >= 500 && this.statusCode <= 599;
56
+ Object.setPrototypeOf(this, new.target.prototype);
57
+ }
58
+ };
59
+ var AuthenticationError = class extends BigBucketError {
60
+ constructor(options) {
61
+ super({ ...options, statusCode: options.statusCode ?? 401 });
62
+ this.name = "AuthenticationError";
63
+ }
64
+ };
65
+ var NotFoundError = class extends BigBucketError {
66
+ constructor(options) {
67
+ super({ ...options, statusCode: 404 });
68
+ this.name = "NotFoundError";
69
+ }
70
+ };
71
+ var PayloadTooLargeError = class extends BigBucketError {
72
+ constructor(options) {
73
+ super({ ...options, statusCode: 413 });
74
+ this.name = "PayloadTooLargeError";
75
+ }
76
+ };
77
+ var UnsupportedFormatError = class extends BigBucketError {
78
+ constructor(options) {
79
+ super({ ...options, statusCode: 415 });
80
+ this.name = "UnsupportedFormatError";
81
+ }
82
+ };
83
+ var RateLimitError = class extends BigBucketError {
84
+ constructor(options) {
85
+ super({ ...options, statusCode: 429 });
86
+ this.name = "RateLimitError";
87
+ }
88
+ };
89
+ var StorageUploadError = class extends BigBucketError {
90
+ constructor(message, cause) {
91
+ super({
92
+ message,
93
+ statusCode: 502,
94
+ code: "STORAGE_UPLOAD_FAILED",
95
+ cause
96
+ });
97
+ this.name = "StorageUploadError";
98
+ }
99
+ };
100
+ var TimeoutError = class extends BigBucketError {
101
+ constructor(message = "Request timed out") {
102
+ super({
103
+ message,
104
+ statusCode: 408,
105
+ code: "REQUEST_TIMEOUT"
106
+ });
107
+ this.name = "TimeoutError";
108
+ }
109
+ };
110
+
111
+ // src/http/transport.ts
112
+ var HttpTransport = class {
113
+ apiKey;
114
+ baseUrl;
115
+ timeout;
116
+ customFetch;
117
+ constructor(config) {
118
+ if (!config.apiKey || typeof config.apiKey !== "string") {
119
+ throw new Error("A valid BigBucket API key is required (e.g. bb_live_...)");
120
+ }
121
+ this.apiKey = config.apiKey.trim();
122
+ this.baseUrl = (config.baseUrl || "http://localhost:8000/api/v1").replace(/\/+$/, "");
123
+ this.timeout = config.timeout ?? 3e4;
124
+ this.customFetch = config.fetch ?? globalThis.fetch;
125
+ if (!this.customFetch) {
126
+ throw new Error("No native fetch implementation found. Pass a custom fetch in options.");
127
+ }
128
+ }
129
+ /**
130
+ * Execute an authenticated request against the BigBucket API.
131
+ */
132
+ async request(path, options = {}) {
133
+ const url = `${this.baseUrl}${path.startsWith("/") ? "" : "/"}${path}`;
134
+ const headers = {
135
+ Accept: "application/json",
136
+ "X-API-Key": this.apiKey,
137
+ ...options.headers
138
+ };
139
+ if (options.body && typeof options.body === "string" && !headers["Content-Type"]) {
140
+ headers["Content-Type"] = "application/json";
141
+ }
142
+ const controller = new AbortController();
143
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
144
+ let response;
145
+ try {
146
+ response = await this.customFetch(url, {
147
+ ...options,
148
+ headers,
149
+ signal: controller.signal
150
+ });
151
+ } catch (err) {
152
+ clearTimeout(timeoutId);
153
+ if (err instanceof Error && err.name === "AbortError") {
154
+ throw new TimeoutError(`Request timed out after ${this.timeout}ms`);
155
+ }
156
+ throw new BigBucketError({
157
+ message: `Network request failed: ${err instanceof Error ? err.message : String(err)}`,
158
+ statusCode: 0,
159
+ cause: err
160
+ });
161
+ } finally {
162
+ clearTimeout(timeoutId);
163
+ }
164
+ const requestId = response.headers.get("X-Request-ID") ?? void 0;
165
+ let responseBody = null;
166
+ const contentType = response.headers.get("content-type");
167
+ if (contentType && contentType.includes("application/json")) {
168
+ try {
169
+ responseBody = await response.json();
170
+ } catch {
171
+ }
172
+ }
173
+ if (!response.ok) {
174
+ const errPayload = responseBody?.error ?? {};
175
+ const message = errPayload.message || `Request failed with status ${response.status}`;
176
+ const code = errPayload.code || "REQUEST_FAILED";
177
+ const details = errPayload.details;
178
+ const errorOptions = {
179
+ message,
180
+ statusCode: response.status,
181
+ code,
182
+ requestId: errPayload.request_id || requestId,
183
+ details
184
+ };
185
+ if (response.status === 401) throw new AuthenticationError(errorOptions);
186
+ if (response.status === 404) throw new NotFoundError(errorOptions);
187
+ if (response.status === 413) throw new PayloadTooLargeError(errorOptions);
188
+ if (response.status === 415) throw new UnsupportedFormatError(errorOptions);
189
+ if (response.status === 429) throw new RateLimitError(errorOptions);
190
+ throw new BigBucketError(errorOptions);
191
+ }
192
+ return responseBody;
193
+ }
194
+ /**
195
+ * Upload binary data directly to the storage provider (Cloudflare R2 or Local Storage).
196
+ * Note: Permanent BigBucket credentials MUST NOT be forwarded to the storage provider.
197
+ */
198
+ async uploadToPresignedUrl(uploadUrl, method, signedHeaders, body) {
199
+ const controller = new AbortController();
200
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout * 2);
201
+ try {
202
+ const response = await this.customFetch(uploadUrl, {
203
+ method: method || "PUT",
204
+ headers: signedHeaders,
205
+ body,
206
+ signal: controller.signal
207
+ });
208
+ if (!response.ok) {
209
+ throw new StorageUploadError(
210
+ `Direct storage upload failed with status ${response.status} ${response.statusText}`
211
+ );
212
+ }
213
+ } catch (err) {
214
+ clearTimeout(timeoutId);
215
+ if (err instanceof BigBucketError) {
216
+ throw err;
217
+ }
218
+ if (err instanceof Error && err.name === "AbortError") {
219
+ throw new TimeoutError(`Direct storage upload timed out after ${this.timeout * 2}ms`);
220
+ }
221
+ throw new StorageUploadError(
222
+ `Failed to upload image bytes to storage provider: ${err instanceof Error ? err.message : String(err)}`,
223
+ err
224
+ );
225
+ } finally {
226
+ clearTimeout(timeoutId);
227
+ }
228
+ }
229
+ };
230
+
231
+ // src/resources/images.ts
232
+ var ImagesResource = class {
233
+ constructor(transport) {
234
+ this.transport = transport;
235
+ }
236
+ transport;
237
+ mapAsset(d) {
238
+ return {
239
+ id: d.id,
240
+ projectId: d.project_id,
241
+ url: d.url,
242
+ originalFilename: d.original_filename,
243
+ mimeType: d.mime_type,
244
+ fileSize: d.file_size,
245
+ width: d.width,
246
+ height: d.height,
247
+ format: d.format,
248
+ status: d.status,
249
+ createdAt: d.created_at,
250
+ updatedAt: d.updated_at,
251
+ variants: (d.variants || []).map((v) => ({
252
+ variantName: v.variant_name,
253
+ url: v.url,
254
+ mimeType: v.mime_type,
255
+ fileSize: v.file_size,
256
+ width: v.width,
257
+ height: v.height,
258
+ format: v.format
259
+ }))
260
+ };
261
+ }
262
+ /**
263
+ * Retrieve metadata and stable delivery URL for an image.
264
+ */
265
+ async get(imageId) {
266
+ const res = await this.transport.request(
267
+ `/images/${encodeURIComponent(imageId)}`,
268
+ {
269
+ method: "GET"
270
+ }
271
+ );
272
+ return this.mapAsset(res.data);
273
+ }
274
+ /**
275
+ * List and paginate media assets belonging to the authenticated project.
276
+ */
277
+ async list(options = {}) {
278
+ const params = new URLSearchParams();
279
+ if (options.page !== void 0) params.set("page", String(options.page));
280
+ if (options.pageSize !== void 0) params.set("page_size", String(options.pageSize));
281
+ if (options.status !== void 0) params.set("status", options.status);
282
+ if (options.format !== void 0) params.set("format", options.format);
283
+ const query = params.toString() ? `?${params.toString()}` : "";
284
+ const res = await this.transport.request(
285
+ `/images${query}`,
286
+ {
287
+ method: "GET"
288
+ }
289
+ );
290
+ return {
291
+ data: (res.data || []).map((item) => this.mapAsset(item)),
292
+ pagination: {
293
+ total: res.pagination.total,
294
+ page: res.pagination.page,
295
+ pageSize: res.pagination.page_size,
296
+ hasNext: res.pagination.has_next
297
+ }
298
+ };
299
+ }
300
+ /**
301
+ * Delete an image from storage and mark it deleted in BigBucket.
302
+ */
303
+ async delete(imageId) {
304
+ const res = await this.transport.request(
305
+ `/images/${encodeURIComponent(imageId)}`,
306
+ {
307
+ method: "DELETE"
308
+ }
309
+ );
310
+ return {
311
+ success: true,
312
+ id: res.data.id
313
+ };
314
+ }
315
+ };
316
+
317
+ // src/resources/uploads.ts
318
+ var UploadsResource = class {
319
+ constructor(transport) {
320
+ this.transport = transport;
321
+ }
322
+ transport;
323
+ /**
324
+ * Request permission and generate a presigned upload URL.
325
+ */
326
+ async create(options) {
327
+ const res = await this.transport.request("/uploads", {
328
+ method: "POST",
329
+ body: JSON.stringify({
330
+ filename: options.filename,
331
+ content_type: options.contentType,
332
+ file_size: options.fileSize
333
+ })
334
+ });
335
+ const d = res.data;
336
+ return {
337
+ uploadId: d.upload_id,
338
+ objectKey: d.object_key,
339
+ presigned: {
340
+ url: d.presigned.url,
341
+ method: d.presigned.method,
342
+ headers: d.presigned.headers
343
+ },
344
+ expiresAt: d.expires_at
345
+ };
346
+ }
347
+ /**
348
+ * Finalize an upload session once the client has uploaded bytes to the presigned URL.
349
+ */
350
+ async complete(uploadId) {
351
+ const res = await this.transport.request(
352
+ `/uploads/${encodeURIComponent(uploadId)}/complete`,
353
+ {
354
+ method: "POST"
355
+ }
356
+ );
357
+ const d = res.data;
358
+ return {
359
+ id: d.id,
360
+ projectId: d.project_id,
361
+ url: d.url,
362
+ originalFilename: d.original_filename,
363
+ mimeType: d.mime_type,
364
+ fileSize: d.file_size,
365
+ width: d.width,
366
+ height: d.height,
367
+ format: d.format,
368
+ status: d.status,
369
+ createdAt: d.created_at,
370
+ updatedAt: d.updated_at,
371
+ variants: (d.variants || []).map((v) => ({
372
+ variantName: v.variant_name,
373
+ url: v.url,
374
+ mimeType: v.mime_type,
375
+ fileSize: v.file_size,
376
+ width: v.width,
377
+ height: v.height,
378
+ format: v.format
379
+ }))
380
+ };
381
+ }
382
+ /**
383
+ * Check the status of an ongoing or completed upload session.
384
+ */
385
+ async get(uploadId) {
386
+ const res = await this.transport.request(
387
+ `/uploads/${encodeURIComponent(uploadId)}`,
388
+ {
389
+ method: "GET"
390
+ }
391
+ );
392
+ const d = res.data;
393
+ return {
394
+ id: d.id,
395
+ projectId: d.project_id,
396
+ objectKey: d.object_key,
397
+ expectedFileSize: d.expected_file_size,
398
+ expectedMimeType: d.expected_mime_type,
399
+ status: d.status,
400
+ expiresAt: d.expires_at,
401
+ createdAt: d.created_at,
402
+ completedAt: d.completed_at ?? null,
403
+ assetId: d.asset_id ?? null
404
+ };
405
+ }
406
+ };
407
+
408
+ // src/resources/usage.ts
409
+ var UsageResource = class {
410
+ constructor(transport) {
411
+ this.transport = transport;
412
+ }
413
+ transport;
414
+ /**
415
+ * Retrieve usage metrics for a project or the account.
416
+ */
417
+ async get(projectId) {
418
+ const path = projectId ? `/projects/${encodeURIComponent(projectId)}/usage` : "/usage";
419
+ const res = await this.transport.request(path, {
420
+ method: "GET"
421
+ });
422
+ const d = res.data;
423
+ return {
424
+ totalStoredBytes: d.total_stored_bytes,
425
+ totalReadyImages: d.total_ready_images,
426
+ successfulUploads: d.successful_uploads,
427
+ deletedImages: d.deleted_images
428
+ };
429
+ }
430
+ };
431
+
432
+ // src/transform.ts
433
+ function buildTransformUrl(baseUrl, options) {
434
+ if (!baseUrl) return "";
435
+ if (!options || Object.keys(options).length === 0) return baseUrl;
436
+ const url = new URL(baseUrl);
437
+ if (options.tr) {
438
+ url.searchParams.set("tr", options.tr);
439
+ }
440
+ const w = options.width ?? options.w;
441
+ if (w !== void 0) {
442
+ url.searchParams.set("w", String(w));
443
+ }
444
+ const h = options.height ?? options.h;
445
+ if (h !== void 0) {
446
+ url.searchParams.set("h", String(h));
447
+ }
448
+ if (options.fit) {
449
+ url.searchParams.set("fit", options.fit);
450
+ }
451
+ const q = options.quality ?? options.q;
452
+ if (q !== void 0) {
453
+ url.searchParams.set("q", String(q));
454
+ }
455
+ const fmt = options.format ?? options.fmt;
456
+ if (fmt) {
457
+ url.searchParams.set("fmt", fmt);
458
+ }
459
+ return url.toString();
460
+ }
461
+
462
+ // src/client.ts
463
+ var BigBucket = class {
464
+ transport;
465
+ images;
466
+ uploads;
467
+ usage;
468
+ constructor(options) {
469
+ this.transport = new HttpTransport({
470
+ apiKey: options.apiKey,
471
+ baseUrl: options.baseUrl,
472
+ timeout: options.timeout,
473
+ fetch: options.fetch
474
+ });
475
+ this.images = new ImagesResource(this.transport);
476
+ this.uploads = new UploadsResource(this.transport);
477
+ this.usage = new UsageResource(this.transport);
478
+ }
479
+ /**
480
+ * Helper method to normalize arbitrary input types (Buffer, Uint8Array, Blob, etc.)
481
+ * and resolve byte length.
482
+ */
483
+ resolveFileData(file, explicitSize) {
484
+ if (typeof Blob !== "undefined" && file instanceof Blob) {
485
+ return { body: file, size: explicitSize ?? file.size };
486
+ }
487
+ if (file instanceof ArrayBuffer) {
488
+ return { body: file, size: explicitSize ?? file.byteLength };
489
+ }
490
+ if (file instanceof Uint8Array) {
491
+ return { body: file, size: explicitSize ?? file.byteLength };
492
+ }
493
+ if (typeof file === "object" && file !== null && "buffer" in file && file.buffer instanceof ArrayBuffer) {
494
+ const buffer = file.buffer;
495
+ return { body: buffer, size: explicitSize ?? buffer.byteLength };
496
+ }
497
+ if (typeof file === "string") {
498
+ const encoded = new TextEncoder().encode(file);
499
+ return { body: encoded, size: explicitSize ?? encoded.byteLength };
500
+ }
501
+ if (explicitSize !== void 0) {
502
+ return { body: file, size: explicitSize };
503
+ }
504
+ throw new Error(
505
+ "Unable to determine file size. Please provide 'options.fileSize' explicitly."
506
+ );
507
+ }
508
+ /**
509
+ * Complete end-to-end image upload workflow:
510
+ * 1. Requests an upload session and receives presigned storage URL.
511
+ * 2. Uploads binary data directly to the storage provider (Cloudflare R2).
512
+ * 3. Finalizes the upload with server verification and content inspection.
513
+ * 4. Returns verified ImageAsset with delivery URL.
514
+ */
515
+ async upload(file, options) {
516
+ const { body, size } = this.resolveFileData(file, options.fileSize);
517
+ const session = await this.uploads.create({
518
+ filename: options.filename,
519
+ contentType: options.contentType,
520
+ fileSize: size
521
+ });
522
+ await this.transport.uploadToPresignedUrl(
523
+ session.presigned.url,
524
+ session.presigned.method,
525
+ session.presigned.headers,
526
+ body
527
+ );
528
+ return await this.uploads.complete(session.uploadId);
529
+ }
530
+ /**
531
+ * Helper method to generate an optimized, transformed image delivery URL.
532
+ *
533
+ * @param baseUrl The original delivery URL of the image asset.
534
+ * @param options Dynamic transformation parameters (width, height, quality, format, or Zepto shorthand).
535
+ */
536
+ transformUrl(baseUrl, options) {
537
+ return buildTransformUrl(baseUrl, options);
538
+ }
539
+ };
540
+ // Annotate the CommonJS export names for ESM import in node:
541
+ 0 && (module.exports = {
542
+ AuthenticationError,
543
+ BigBucket,
544
+ BigBucketError,
545
+ HttpTransport,
546
+ ImagesResource,
547
+ NotFoundError,
548
+ PayloadTooLargeError,
549
+ RateLimitError,
550
+ StorageUploadError,
551
+ TimeoutError,
552
+ UnsupportedFormatError,
553
+ UploadsResource,
554
+ UsageResource,
555
+ buildTransformUrl
556
+ });