@boltic/sdk 0.1.4 → 0.1.5

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/sdk.mjs CHANGED
@@ -234,6 +234,20 @@ Context: ${JSON.stringify(context, null, 2)}`;
234
234
  }
235
235
  return String(error);
236
236
  }
237
+ function decodeArrayBufferErrorBody(buffer) {
238
+ const txt = new TextDecoder().decode(buffer);
239
+ try {
240
+ return JSON.parse(txt);
241
+ } catch {
242
+ return txt;
243
+ }
244
+ }
245
+ function normalizeAxiosDataAfterResponse(config, status, data) {
246
+ if (config.responseType !== "arraybuffer" || !(data instanceof ArrayBuffer) || status < 400) {
247
+ return data;
248
+ }
249
+ return decodeArrayBufferErrorBody(data);
250
+ }
237
251
  class AxiosAdapter {
238
252
  constructor() {
239
253
  try {
@@ -247,21 +261,36 @@ class AxiosAdapter {
247
261
  }
248
262
  async request(config) {
249
263
  try {
264
+ const isFormData = typeof FormData !== "undefined" && config.data instanceof FormData;
265
+ let headers = config.headers;
266
+ if (isFormData && headers) {
267
+ headers = { ...headers };
268
+ delete headers["Content-Type"];
269
+ delete headers["content-type"];
270
+ }
250
271
  const axiosConfig = {
251
272
  url: config.url,
252
273
  method: config.method.toLowerCase(),
253
- headers: config.headers,
274
+ headers,
254
275
  params: config.params,
255
276
  data: config.data,
256
277
  timeout: config.timeout,
257
278
  signal: config.signal,
258
279
  validateStatus: () => true
259
280
  };
281
+ if (config.responseType === "arraybuffer") {
282
+ axiosConfig.responseType = "arraybuffer";
283
+ }
260
284
  const response = await this.axios(axiosConfig);
285
+ const responseData = normalizeAxiosDataAfterResponse(
286
+ config,
287
+ response.status,
288
+ response.data
289
+ );
261
290
  if (response.status < 200 || response.status >= 300) {
262
- const isHtmlError = typeof response.data === "string" && response.data.trim().startsWith("<!DOCTYPE") || typeof response.data === "string" && response.data.includes("<html");
291
+ const isHtmlError = typeof responseData === "string" && responseData.trim().startsWith("<!DOCTYPE") || typeof responseData === "string" && responseData.includes("<html");
263
292
  if (isHtmlError) {
264
- const htmlContent = response.data;
293
+ const htmlContent = responseData;
265
294
  const preMatch = htmlContent.match(/<pre>(.*?)<\/pre>/s);
266
295
  const errorMessage = preMatch ? preMatch[1].trim() : `HTTP ${response.status}: ${response.statusText}`;
267
296
  throw createErrorWithContext(errorMessage, {
@@ -272,9 +301,9 @@ class AxiosAdapter {
272
301
  isHtmlError: true
273
302
  });
274
303
  }
275
- if (response.data && typeof response.data === "object" && "error" in response.data) {
304
+ if (responseData && typeof responseData === "object" && "error" in responseData) {
276
305
  return {
277
- data: response.data,
306
+ data: responseData,
278
307
  status: response.status,
279
308
  statusText: response.statusText,
280
309
  headers: response.headers || {}
@@ -287,12 +316,12 @@ class AxiosAdapter {
287
316
  method: config.method,
288
317
  status: response.status,
289
318
  statusText: response.statusText,
290
- responseData: response.data
319
+ responseData
291
320
  }
292
321
  );
293
322
  }
294
323
  return {
295
- data: response.data,
324
+ data: responseData,
296
325
  status: response.status,
297
326
  statusText: response.statusText,
298
327
  headers: response.headers || {}
@@ -335,6 +364,26 @@ class AxiosAdapter {
335
364
  }
336
365
  }
337
366
  }
367
+ async function parseFetchBodyDefault(response) {
368
+ const contentType = response.headers.get("content-type");
369
+ if (contentType?.includes("application/json")) {
370
+ return response.json();
371
+ }
372
+ return response.text();
373
+ }
374
+ async function parseFetchBodyArrayBuffer(response) {
375
+ const buf = await response.arrayBuffer();
376
+ if (response.status >= 400) {
377
+ return decodeArrayBufferErrorBody(buf);
378
+ }
379
+ return buf;
380
+ }
381
+ async function parseFetchResponseData(response, config) {
382
+ if (config.responseType === "arraybuffer") {
383
+ return parseFetchBodyArrayBuffer(response);
384
+ }
385
+ return parseFetchBodyDefault(response);
386
+ }
338
387
  class FetchAdapter {
339
388
  async request(config) {
340
389
  const url = new URL(config.url);
@@ -345,16 +394,21 @@ class FetchAdapter {
345
394
  }
346
395
  });
347
396
  }
397
+ const isFormData = typeof FormData !== "undefined" && config.data instanceof FormData;
398
+ const headerMap = { ...config.headers || {} };
399
+ if (!isFormData) {
400
+ headerMap["Content-Type"] = headerMap["Content-Type"] ?? headerMap["content-type"] ?? "application/json";
401
+ } else {
402
+ delete headerMap["Content-Type"];
403
+ delete headerMap["content-type"];
404
+ }
348
405
  const init = {
349
406
  method: config.method,
350
- headers: {
351
- "Content-Type": "application/json",
352
- ...config.headers
353
- },
407
+ headers: headerMap,
354
408
  signal: config.signal
355
409
  };
356
410
  if (config.data && ["POST", "PUT", "PATCH", "DELETE"].includes(config.method)) {
357
- init.body = JSON.stringify(config.data);
411
+ init.body = isFormData ? config.data : JSON.stringify(config.data);
358
412
  }
359
413
  try {
360
414
  const controller = new AbortController();
@@ -378,13 +432,10 @@ class FetchAdapter {
378
432
  if (timeoutId) {
379
433
  clearTimeout(timeoutId);
380
434
  }
381
- const contentType = response.headers.get("content-type");
382
- let data;
383
- if (contentType?.includes("application/json")) {
384
- data = await response.json();
385
- } else {
386
- data = await response.text();
387
- }
435
+ const data = await parseFetchResponseData(
436
+ response,
437
+ config
438
+ );
388
439
  const headers = {};
389
440
  response.headers.forEach((value, key) => {
390
441
  headers[key] = value;
@@ -548,7 +599,8 @@ const SERVICE_PATHS = {
548
599
  DATABASES: "/service/sdk/boltic-tables/v1",
549
600
  WORKFLOW_TEMPORAL: "/service/panel/temporal/v1.0",
550
601
  INTEGRATION: "/service/panel/integration/v1",
551
- SERVERLESS: "/service/panel/serverless/v1.0"
602
+ SERVERLESS: "/service/panel/serverless/v1.0",
603
+ STORAGE: "/service/panel/storage/v1.0"
552
604
  };
553
605
  class BaseApiClient {
554
606
  constructor(apiKey, config = {}, servicePath = SERVICE_PATHS.DATABASES) {
@@ -4075,7 +4127,10 @@ class SqlApiClient extends BaseApiClient {
4075
4127
  const response = await this.httpAdapter.request({
4076
4128
  url,
4077
4129
  method: endpoint.method,
4078
- headers: this.buildHeaders(),
4130
+ headers: {
4131
+ ...this.buildHeaders(),
4132
+ "x-request-source": "sdk"
4133
+ },
4079
4134
  data: request,
4080
4135
  timeout: this.config.timeout
4081
4136
  });
@@ -4106,7 +4161,10 @@ class SqlApiClient extends BaseApiClient {
4106
4161
  const response = await this.httpAdapter.request({
4107
4162
  url,
4108
4163
  method: endpoint.method,
4109
- headers: this.buildHeaders(),
4164
+ headers: {
4165
+ ...this.buildHeaders(),
4166
+ "x-request-source": "sdk"
4167
+ },
4110
4168
  data: request,
4111
4169
  timeout: this.config.timeout
4112
4170
  });
@@ -5476,6 +5534,466 @@ class ServerlessResource extends BaseResource {
5476
5534
  };
5477
5535
  }
5478
5536
  }
5537
+ const DEFAULT_STORAGE_TYPE = "gcs";
5538
+ const MAX_SIGNED_URL_EXPIRE_MINUTES = 7 * 24 * 60;
5539
+ const STORAGE_ENDPOINTS = {
5540
+ list: {
5541
+ path: "/storage/list",
5542
+ method: "GET",
5543
+ authenticated: true
5544
+ },
5545
+ upload: {
5546
+ path: "/storage/upload",
5547
+ method: "POST",
5548
+ authenticated: true
5549
+ },
5550
+ directory: {
5551
+ path: "/storage/directory",
5552
+ method: "POST",
5553
+ authenticated: true
5554
+ },
5555
+ deleteFile: {
5556
+ path: "/storage/file",
5557
+ method: "DELETE",
5558
+ authenticated: true
5559
+ },
5560
+ objectAccess: {
5561
+ path: "/storage/change-object-access",
5562
+ method: "POST",
5563
+ authenticated: true
5564
+ },
5565
+ fileExport: {
5566
+ path: "/storage/file-export",
5567
+ method: "POST",
5568
+ authenticated: true
5569
+ }
5570
+ };
5571
+ function buildStorageEndpointPath(endpoint) {
5572
+ return endpoint.path;
5573
+ }
5574
+ const ERR_PREFIX = "STORAGE";
5575
+ class StorageApiClient extends BaseApiClient {
5576
+ constructor(apiKey, config = {}) {
5577
+ super(apiKey, config, SERVICE_PATHS.STORAGE);
5578
+ }
5579
+ /** Shared try/catch + http — same idea as inlined blocks in serverless/workflow clients, but DRY for storage. */
5580
+ async requestStorage(config) {
5581
+ try {
5582
+ const response = await this.httpAdapter.request(config);
5583
+ return response.data;
5584
+ } catch (error) {
5585
+ return this.formatErrorResponse(error, ERR_PREFIX);
5586
+ }
5587
+ }
5588
+ url(path, query) {
5589
+ const qs = query?.toString();
5590
+ return qs ? `${this.baseURL}${path}?${qs}` : `${this.baseURL}${path}`;
5591
+ }
5592
+ storageTypeQuery(storageType) {
5593
+ const q = new URLSearchParams();
5594
+ q.set("storageType", storageType ?? DEFAULT_STORAGE_TYPE);
5595
+ return q;
5596
+ }
5597
+ listQuery(params) {
5598
+ const q = this.storageTypeQuery(params.storageType);
5599
+ if (params.basePath !== void 0) {
5600
+ q.set("basePath", params.basePath);
5601
+ }
5602
+ if (params.pageSize !== void 0) {
5603
+ q.set("pageSize", String(params.pageSize));
5604
+ }
5605
+ if (params.nextPageToken !== void 0) {
5606
+ q.set("nextPageToken", params.nextPageToken);
5607
+ }
5608
+ return q;
5609
+ }
5610
+ /** `expire_in` is minutes; clamp to max temporary URL lifetime (7 days). */
5611
+ normalizeExpireInMinutes(raw) {
5612
+ const n = typeof raw === "number" ? raw : Number(raw);
5613
+ if (!Number.isFinite(n) || n <= 0) {
5614
+ throw new Error(
5615
+ "expire_in must be a positive number of minutes (max 7 days for temporary signed URLs)"
5616
+ );
5617
+ }
5618
+ const truncated = Math.trunc(n);
5619
+ return Math.min(Math.max(1, truncated), MAX_SIGNED_URL_EXPIRE_MINUTES);
5620
+ }
5621
+ isTruthyFormFlag(v) {
5622
+ if (v === void 0) return false;
5623
+ if (typeof v === "boolean") return v;
5624
+ const s = String(v).toLowerCase();
5625
+ return s === "true" || s === "1" || s === "yes";
5626
+ }
5627
+ /**
5628
+ * `public` shortcut: true + no expire_in → permanent; true + expire_in → temporary signed URL; false → private.
5629
+ * Legacy: omit `public` and pass `is_public` / `is_public_permanent` / `expire_in` as before.
5630
+ */
5631
+ appendUploadVisibility(form, params) {
5632
+ const hasPublic = Object.prototype.hasOwnProperty.call(params, "public");
5633
+ if (hasPublic) {
5634
+ const pub = this.isTruthyFormFlag(params.public);
5635
+ if (!pub) {
5636
+ form.append("is_public", "false");
5637
+ return;
5638
+ }
5639
+ if (params.expire_in !== void 0) {
5640
+ form.append("is_public", "true");
5641
+ form.append(
5642
+ "expire_in",
5643
+ String(this.normalizeExpireInMinutes(params.expire_in))
5644
+ );
5645
+ return;
5646
+ }
5647
+ form.append("is_public", "false");
5648
+ form.append("is_public_permanent", "true");
5649
+ return;
5650
+ }
5651
+ if (params.is_public !== void 0) {
5652
+ form.append("is_public", String(params.is_public));
5653
+ }
5654
+ if (params.expire_in !== void 0) {
5655
+ form.append(
5656
+ "expire_in",
5657
+ String(this.normalizeExpireInMinutes(params.expire_in))
5658
+ );
5659
+ }
5660
+ if (params.is_public_permanent !== void 0) {
5661
+ form.append("is_public_permanent", String(params.is_public_permanent));
5662
+ }
5663
+ }
5664
+ buildUploadForm(params) {
5665
+ const logicalName = params.filename ?? params.file_name;
5666
+ if (logicalName == null || logicalName === "") {
5667
+ throw new Error("upload requires filename or file_name");
5668
+ }
5669
+ const form = new FormData();
5670
+ form.append("file", params.file, logicalName);
5671
+ form.append("filename", logicalName);
5672
+ if (params.filepath !== void 0) {
5673
+ form.append("filepath", params.filepath);
5674
+ }
5675
+ if (params.overwrite !== void 0) {
5676
+ form.append("overwrite", String(params.overwrite));
5677
+ }
5678
+ this.appendUploadVisibility(form, params);
5679
+ return form;
5680
+ }
5681
+ isErrorResult(result) {
5682
+ return typeof result === "object" && result !== null && "error" in result && result.error !== void 0;
5683
+ }
5684
+ isAclErrorResult(result) {
5685
+ return typeof result === "object" && result !== null && "error" in result && result.error !== void 0;
5686
+ }
5687
+ buildObjectAccessSummary(filePath, row, fallbackPublic) {
5688
+ const isPublic = row != null ? Boolean(row.isPublic) : fallbackPublic;
5689
+ return {
5690
+ message: isPublic ? "File has been made publicly accessible." : "File's public access has been revoked, making it private.",
5691
+ name: row?.name ?? row?.fullPath ?? filePath,
5692
+ size: row?.size ?? null,
5693
+ updated: row?.updatedAt ?? null,
5694
+ public: isPublic
5695
+ };
5696
+ }
5697
+ /**
5698
+ * Parses Hawkeye `POST /change-object-access` success JSON
5699
+ * `{ message, name, size, updated, public }`.
5700
+ */
5701
+ parseChangeObjectAccessResponse(raw) {
5702
+ if (typeof raw !== "object" || raw === null) return null;
5703
+ const o = raw;
5704
+ if (typeof o.name !== "string" || typeof o.public !== "boolean") {
5705
+ return null;
5706
+ }
5707
+ const message = typeof o.message === "string" && o.message.length > 0 ? o.message : o.public ? "File has been made publicly accessible." : "File's public access has been revoked, making it private.";
5708
+ const size = o.size === void 0 || o.size === null ? null : String(o.size);
5709
+ const updated = o.updated === void 0 || o.updated === null ? null : String(o.updated);
5710
+ return {
5711
+ message,
5712
+ name: o.name,
5713
+ size,
5714
+ updated,
5715
+ public: o.public
5716
+ };
5717
+ }
5718
+ /** Resolves the list row for a full object path (same folder semantics as download size resolution). */
5719
+ async findFileListItem(filePath, storageType) {
5720
+ const lastSlash = filePath.lastIndexOf("/");
5721
+ const nameOnly = lastSlash === -1 ? filePath : filePath.slice(lastSlash + 1);
5722
+ const folderPrefix = lastSlash === -1 ? "" : filePath.slice(0, lastSlash);
5723
+ const basePath = folderPrefix ? `${folderPrefix}/` : "";
5724
+ const result = await this.list({ basePath, storageType });
5725
+ if (this.isErrorResult(result)) return result;
5726
+ const rows = result.files?.data ?? [];
5727
+ return rows.find(
5728
+ (r) => r.fullPath === filePath || !r.isDirectory && (r.name === nameOnly || r.fullPath === filePath)
5729
+ ) ?? null;
5730
+ }
5731
+ /** Keep only SDK list fields; flatten metadata to `size` / `updatedAt`. */
5732
+ normalizeListItem(raw) {
5733
+ const meta = raw.metadata ?? {};
5734
+ let size;
5735
+ if (meta.size !== void 0 && meta.size !== null) {
5736
+ size = String(meta.size);
5737
+ }
5738
+ const updatedRaw = meta.updated ?? meta.timeUpdated;
5739
+ let updatedAt;
5740
+ if (updatedRaw !== void 0 && updatedRaw !== null) {
5741
+ updatedAt = String(updatedRaw);
5742
+ }
5743
+ const name = raw.name;
5744
+ const parentPath = raw.parentPath;
5745
+ const isDirectory = Boolean(raw.isDirectory);
5746
+ let fullPath;
5747
+ if (!isDirectory && name) {
5748
+ fullPath = parentPath ? `${parentPath}/${name}` : name;
5749
+ }
5750
+ const cdnRaw = raw.cdnUrl;
5751
+ const cdnUrl = cdnRaw === void 0 || cdnRaw === null ? null : cdnRaw;
5752
+ const item = {
5753
+ name,
5754
+ path: raw.path,
5755
+ folderName: raw.folderName,
5756
+ parentPath,
5757
+ isDirectory,
5758
+ isPublic: raw.isPublic,
5759
+ cdnUrl,
5760
+ fullPath
5761
+ };
5762
+ if (size !== void 0) item.size = size;
5763
+ if (updatedAt !== void 0) item.updatedAt = updatedAt;
5764
+ return item;
5765
+ }
5766
+ /** Map wire `shareable_link` to `temporary_sharable_link`. */
5767
+ normalizeUploadData(raw) {
5768
+ const message = String(raw.message ?? "");
5769
+ const path = String(raw.path ?? "");
5770
+ const out = { message, path };
5771
+ const link = raw.temporary_sharable_link ?? raw.shareable_link;
5772
+ if (link !== void 0 && link !== null && String(link) !== "") {
5773
+ out.temporary_sharable_link = String(link);
5774
+ }
5775
+ if (raw.public_url !== void 0 && raw.public_url !== null) {
5776
+ out.public_url = String(raw.public_url);
5777
+ }
5778
+ return out;
5779
+ }
5780
+ normalizeListResponse(data) {
5781
+ const payload = data.files;
5782
+ if (!payload?.data) return data;
5783
+ return {
5784
+ files: {
5785
+ ...payload,
5786
+ data: payload.data.map(
5787
+ (item) => this.normalizeListItem(item)
5788
+ )
5789
+ }
5790
+ };
5791
+ }
5792
+ async list(params = {}) {
5793
+ const endpoint = STORAGE_ENDPOINTS.list;
5794
+ const path = buildStorageEndpointPath(endpoint);
5795
+ const result = await this.requestStorage({
5796
+ url: this.url(path, this.listQuery(params)),
5797
+ method: endpoint.method,
5798
+ headers: this.buildHeaders(),
5799
+ timeout: this.config.timeout
5800
+ });
5801
+ if (this.isErrorResult(result)) return result;
5802
+ return this.normalizeListResponse(result);
5803
+ }
5804
+ async createFolder(body) {
5805
+ const endpoint = STORAGE_ENDPOINTS.directory;
5806
+ const path = buildStorageEndpointPath(endpoint);
5807
+ const q = this.storageTypeQuery(body.storageType);
5808
+ return this.requestStorage({
5809
+ url: this.url(path, q),
5810
+ method: endpoint.method,
5811
+ headers: this.buildHeaders(),
5812
+ data: { folder_path: body.folder_path },
5813
+ timeout: this.config.timeout
5814
+ });
5815
+ }
5816
+ async deleteFile(params) {
5817
+ const endpoint = STORAGE_ENDPOINTS.deleteFile;
5818
+ const path = buildStorageEndpointPath(endpoint);
5819
+ const q = this.storageTypeQuery(params.storageType);
5820
+ q.set("filename", params.filename);
5821
+ const data = {};
5822
+ if (params.filepath !== void 0) {
5823
+ data.filepath = params.filepath;
5824
+ }
5825
+ if (params.totalsize !== void 0) {
5826
+ data.totalsize = params.totalsize;
5827
+ }
5828
+ return this.requestStorage({
5829
+ url: this.url(path, q),
5830
+ method: endpoint.method,
5831
+ headers: this.buildHeaders(),
5832
+ data: Object.keys(data).length ? data : void 0,
5833
+ timeout: this.config.timeout
5834
+ });
5835
+ }
5836
+ /**
5837
+ * `POST /change-object-access` — used by `makePublic` / `makePrivate`.
5838
+ * Preferring API body `{ message, name, size, updated, public }`; otherwise falls back to a parent list.
5839
+ */
5840
+ async setObjectAccess(body) {
5841
+ const endpoint = STORAGE_ENDPOINTS.objectAccess;
5842
+ const path = buildStorageEndpointPath(endpoint);
5843
+ const acl = await this.requestStorage({
5844
+ url: this.url(path),
5845
+ method: endpoint.method,
5846
+ headers: this.buildHeaders(),
5847
+ data: {
5848
+ file_path: body.file_path,
5849
+ public: body.public
5850
+ },
5851
+ timeout: this.config.timeout
5852
+ });
5853
+ if (this.isAclErrorResult(acl)) return acl;
5854
+ const fromApi = this.parseChangeObjectAccessResponse(acl);
5855
+ if (fromApi !== null) {
5856
+ return fromApi;
5857
+ }
5858
+ const row = await this.findFileListItem(body.file_path);
5859
+ if (this.isAclErrorResult(row)) return row;
5860
+ return this.buildObjectAccessSummary(body.file_path, row, body.public);
5861
+ }
5862
+ async upload(params) {
5863
+ const endpoint = STORAGE_ENDPOINTS.upload;
5864
+ const path = buildStorageEndpointPath(endpoint);
5865
+ const q = this.storageTypeQuery(params.storageType);
5866
+ const headers = { ...this.buildHeaders() };
5867
+ delete headers["Content-Type"];
5868
+ const result = await this.requestStorage({
5869
+ url: this.url(path, q),
5870
+ method: endpoint.method,
5871
+ headers,
5872
+ data: this.buildUploadForm(params),
5873
+ timeout: this.config.timeout
5874
+ });
5875
+ if (this.isAclErrorResult(result)) return result;
5876
+ return this.normalizeUploadData(result);
5877
+ }
5878
+ async resolveFileSizeBytes(fileName, storageType) {
5879
+ const lastSlash = fileName.lastIndexOf("/");
5880
+ const nameOnly = lastSlash === -1 ? fileName : fileName.slice(lastSlash + 1);
5881
+ const folderPrefix = lastSlash === -1 ? "" : fileName.slice(0, lastSlash);
5882
+ const basePath = folderPrefix ? `${folderPrefix}/` : "";
5883
+ const result = await this.list({ basePath, storageType });
5884
+ if (this.isErrorResult(result)) {
5885
+ throw new Error(
5886
+ result.error?.message ?? "List failed while resolving file size"
5887
+ );
5888
+ }
5889
+ const rows = result.files?.data ?? [];
5890
+ const hit = rows.find(
5891
+ (r) => r.fullPath === fileName || !r.isDirectory && r.name === nameOnly
5892
+ );
5893
+ const s = hit?.size;
5894
+ if (s === void 0) {
5895
+ throw new Error(
5896
+ `Could not resolve size for "${fileName}". Pass sizeBytes (from list() item size).`
5897
+ );
5898
+ }
5899
+ return Number(s);
5900
+ }
5901
+ /**
5902
+ * Download file bytes via `POST /file-export` (range 0..size-1).
5903
+ */
5904
+ async downloadFile(params) {
5905
+ try {
5906
+ const sizeBytes = params.sizeBytes !== void 0 ? Number(params.sizeBytes) : await this.resolveFileSizeBytes(
5907
+ params.file_name,
5908
+ params.storageType
5909
+ );
5910
+ if (!Number.isFinite(sizeBytes) || sizeBytes <= 0) {
5911
+ throw new Error(
5912
+ "Invalid or unknown file size. Pass sizeBytes from list() item size."
5913
+ );
5914
+ }
5915
+ const endpoint = STORAGE_ENDPOINTS.fileExport;
5916
+ const path = buildStorageEndpointPath(endpoint);
5917
+ const q = this.storageTypeQuery(params.storageType);
5918
+ const response = await this.httpAdapter.request({
5919
+ url: this.url(path, q),
5920
+ method: "POST",
5921
+ headers: { ...this.buildHeaders(), Accept: "*/*" },
5922
+ data: {
5923
+ file_name: params.file_name,
5924
+ start_byte: 0,
5925
+ end_byte: sizeBytes - 1
5926
+ },
5927
+ timeout: this.config.timeout,
5928
+ responseType: "arraybuffer"
5929
+ });
5930
+ if (response.status < 200 || response.status >= 300) {
5931
+ const d = response.data;
5932
+ if (d && typeof d === "object" && d !== null && "error" in d) {
5933
+ return d;
5934
+ }
5935
+ return this.formatErrorResponse(
5936
+ new Error(`Download failed: HTTP ${response.status}`),
5937
+ ERR_PREFIX
5938
+ );
5939
+ }
5940
+ if (!(response.data instanceof ArrayBuffer)) {
5941
+ throw new Error("Expected binary response body");
5942
+ }
5943
+ const headers = response.headers;
5944
+ const ct = headers["content-type"] ?? headers["Content-Type"] ?? void 0;
5945
+ return {
5946
+ bytes: response.data,
5947
+ status: response.status,
5948
+ contentType: ct
5949
+ };
5950
+ } catch (error) {
5951
+ return this.formatErrorResponse(error, ERR_PREFIX);
5952
+ }
5953
+ }
5954
+ }
5955
+ class StorageResource extends BaseResource {
5956
+ constructor(client) {
5957
+ super(client, "/storage");
5958
+ const config = client.getConfig();
5959
+ this.apiClient = new StorageApiClient(config.apiKey, {
5960
+ environment: config.environment,
5961
+ region: config.region,
5962
+ timeout: config.timeout,
5963
+ debug: config.debug,
5964
+ headers: config.headers
5965
+ });
5966
+ }
5967
+ async list(params = {}) {
5968
+ return this.apiClient.list(params);
5969
+ }
5970
+ /** Direct upload — `POST /upload` (multipart); server persists the object. */
5971
+ async upload(params) {
5972
+ return this.apiClient.upload(params);
5973
+ }
5974
+ async createFolder(params) {
5975
+ return this.apiClient.createFolder(params);
5976
+ }
5977
+ async deleteFile(params) {
5978
+ return this.apiClient.deleteFile(params);
5979
+ }
5980
+ async makePublic(filePath) {
5981
+ return this.apiClient.setObjectAccess({
5982
+ file_path: filePath,
5983
+ public: true
5984
+ });
5985
+ }
5986
+ async makePrivate(filePath) {
5987
+ return this.apiClient.setObjectAccess({
5988
+ file_path: filePath,
5989
+ public: false
5990
+ });
5991
+ }
5992
+ /** Download file bytes via `POST /file-export` (full file). */
5993
+ async downloadFile(params) {
5994
+ return this.apiClient.downloadFile(params);
5995
+ }
5996
+ }
5479
5997
  class BolticClient {
5480
5998
  constructor(apiKey, options = {}) {
5481
5999
  this.currentDatabase = null;
@@ -5500,6 +6018,7 @@ class BolticClient {
5500
6018
  this.databaseResource = new DatabaseResource(this.baseClient);
5501
6019
  this.workflowResource = new WorkflowResource(this.baseClient);
5502
6020
  this.serverlessResource = new ServerlessResource(this.baseClient);
6021
+ this.storageResource = new StorageResource(this.baseClient);
5503
6022
  this.currentDatabase = null;
5504
6023
  }
5505
6024
  /**
@@ -5722,6 +6241,17 @@ class BolticClient {
5722
6241
  pollStatus: (appId, options) => this.serverlessResource.pollStatus(appId, options)
5723
6242
  };
5724
6243
  }
6244
+ get storage() {
6245
+ return {
6246
+ list: (params) => this.storageResource.list(params),
6247
+ upload: (params) => this.storageResource.upload(params),
6248
+ createFolder: (params) => this.storageResource.createFolder(params),
6249
+ deleteFile: (params) => this.storageResource.deleteFile(params),
6250
+ makePublic: (filePath) => this.storageResource.makePublic(filePath),
6251
+ makePrivate: (filePath) => this.storageResource.makePrivate(filePath),
6252
+ downloadFile: (params) => this.storageResource.downloadFile(params)
6253
+ };
6254
+ }
5725
6255
  // SQL resource access for testing
5726
6256
  getSqlResource() {
5727
6257
  return this.sqlResource;
@@ -5806,6 +6336,7 @@ class BolticClient {
5806
6336
  this.databaseResource = new DatabaseResource(this.baseClient);
5807
6337
  this.workflowResource = new WorkflowResource(this.baseClient);
5808
6338
  this.serverlessResource = new ServerlessResource(this.baseClient);
6339
+ this.storageResource = new StorageResource(this.baseClient);
5809
6340
  }
5810
6341
  // Security methods to prevent API key exposure
5811
6342
  toString() {
@@ -5836,14 +6367,20 @@ export {
5836
6367
  BolticClient,
5837
6368
  DEFAULT_RESOURCES,
5838
6369
  DEFAULT_SCALING,
6370
+ DEFAULT_STORAGE_TYPE,
6371
+ MAX_SIGNED_URL_EXPIRE_MINUTES,
5839
6372
  MAX_STATUS_POLLING_ATTEMPTS,
5840
6373
  SERVICE_PATHS,
5841
6374
  STATUS_POLLING_INTERVAL_MS,
6375
+ STORAGE_ENDPOINTS,
5842
6376
  ServerlessApiClient,
5843
6377
  ServerlessResource,
6378
+ StorageApiClient,
6379
+ StorageResource,
5844
6380
  TERMINAL_STATUSES,
5845
6381
  VERSION,
5846
6382
  WorkflowResource,
6383
+ buildStorageEndpointPath,
5847
6384
  createClient,
5848
6385
  createErrorWithContext$1 as createErrorWithContext,
5849
6386
  formatError$1 as formatError,