@applica-software-guru/didonato-storage-client 1.0.30 → 1.0.32

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.d.mts CHANGED
@@ -1,24 +1,139 @@
1
- interface DidonatoOptions {
1
+ interface SignResponse {
2
+ key: string;
3
+ uploadUrl?: string;
4
+ url?: string;
5
+ fields?: Record<string, string>;
6
+ requiredHeaders?: Record<string, string>;
7
+ expiresIn?: number;
8
+ debug?: {
9
+ canonicalRequest?: string;
10
+ stringToSign?: string;
11
+ signatureHex?: string;
12
+ };
13
+ }
14
+ interface CreateUploadSignatureRequest {
15
+ filename: string;
16
+ contentType: string;
17
+ maxSize: number;
18
+ }
19
+ type CreateUploadSignatureResponse = SignResponse;
20
+ interface FileMetadata {
21
+ _id?: string;
22
+ ownerId: string;
23
+ key: string;
24
+ originalName: string;
25
+ contentType: string;
26
+ size: number;
27
+ createdAt: string;
28
+ }
29
+ interface CompleteResponse {
30
+ insertedId?: string;
31
+ doc?: FileMetadata;
32
+ }
33
+ interface FinalizeUploadRequest {
34
+ key: string;
35
+ originalName: string;
36
+ contentType: string;
37
+ size: number;
38
+ }
39
+ type FinalizeUploadResponse = CompleteResponse;
40
+ interface ListFilesMeta {
41
+ page: number;
42
+ limit: number;
43
+ total: number;
44
+ totalPages: number;
45
+ }
46
+ interface ListFilesResponse {
47
+ files: FileMetadata[];
48
+ meta: ListFilesMeta;
49
+ }
50
+ interface GetFilesRequest {
51
+ q?: string;
52
+ page?: number;
53
+ limit?: number;
54
+ }
55
+ type GetFilesResponse = ListFilesResponse;
56
+ interface GetDownloadUrlRequest {
57
+ id: string;
58
+ }
59
+ interface GetDownloadUrlResponse {
60
+ downloadUrl: string;
61
+ expiresIn?: number;
62
+ }
63
+ interface DownloadFileRequest {
64
+ id: string;
65
+ }
66
+ interface DownloadFileResponse {
67
+ blob: Blob;
68
+ }
69
+ interface DeleteFileRequest {
70
+ id: string;
71
+ }
72
+ interface DeleteFileResponse {
73
+ ok: boolean;
74
+ }
75
+ interface ProgressInfo {
76
+ loaded: number;
77
+ total: number;
78
+ percent?: number;
79
+ }
80
+ interface UploadToSignedUrlRequest {
81
+ uploadUrl: string;
82
+ file: Blob | File;
83
+ requiredHeaders?: Record<string, string>;
84
+ onProgress?: (p: ProgressInfo) => void;
85
+ }
86
+ interface UploadToSignedFormRequest {
87
+ uploadUrl: string;
88
+ fields: Record<string, string>;
89
+ file: Blob | File;
90
+ onProgress?: (p: ProgressInfo) => void;
91
+ }
92
+ interface UploadFileRequest {
93
+ file: Blob | File;
94
+ opts?: {
95
+ filename?: string;
96
+ contentType?: string;
97
+ size?: number;
98
+ };
99
+ onProgress?: (p: ProgressInfo) => void;
100
+ }
101
+ interface UploadFileResponse {
102
+ sig: SignResponse;
103
+ complete: CompleteResponse;
104
+ }
105
+ interface IDidonatoStorageClient {
106
+ createUploadSignature(req: CreateUploadSignatureRequest): Promise<CreateUploadSignatureResponse>;
107
+ finalizeUpload(req: FinalizeUploadRequest): Promise<FinalizeUploadResponse>;
108
+ getFiles(req?: GetFilesRequest): Promise<GetFilesResponse>;
109
+ getDownloadUrl(req: GetDownloadUrlRequest): Promise<GetDownloadUrlResponse>;
110
+ downloadFile(req: DownloadFileRequest): Promise<DownloadFileResponse>;
111
+ deleteFile(req: DeleteFileRequest): Promise<DeleteFileResponse>;
112
+ uploadFile?: (req: UploadFileRequest) => Promise<UploadFileResponse>;
113
+ }
114
+ type TokenProvider = string | (() => string | null | Promise<string | null>) | null;
115
+ interface ClientOptions {
2
116
  baseUrl?: string;
3
- bucket?: string;
4
- credentials?: Record<string, any>;
5
- /**
6
- * A static token string (Bearer) or a function that returns a token string (sync or Promise)
7
- */
8
- token?: string | (() => string | Promise<string>);
9
- }
10
- declare class DidonatoStorageClient {
11
- options: DidonatoOptions;
12
- constructor(opts?: DidonatoOptions);
13
- getToken(): Promise<string | undefined>;
14
- getSignedUrl(key: string, expiresSec?: number): Promise<string>;
15
- createSignedPost(key: string): Promise<{
16
- url: string;
17
- fields: Record<string, string>;
18
- }>;
19
- uploadWithSignedPost(url: string, fields: Record<string, string>, body: Buffer | Blob | string): Promise<boolean>;
20
- delete(key: string): Promise<boolean>;
21
- list(prefix?: string): Promise<string[]>;
117
+ getAuthToken?: TokenProvider;
118
+ token?: TokenProvider;
119
+ }
120
+
121
+ declare class DidonatoStorageClient implements IDidonatoStorageClient {
122
+ private baseUrl;
123
+ private getAuthToken;
124
+ constructor(opts?: ClientOptions);
125
+ private resolveToken;
126
+ private headers;
127
+ private _fetch;
128
+ createUploadSignature(req: CreateUploadSignatureRequest): Promise<CreateUploadSignatureResponse>;
129
+ finalizeUpload(req: FinalizeUploadRequest): Promise<FinalizeUploadResponse>;
130
+ getFiles(req?: GetFilesRequest): Promise<GetFilesResponse>;
131
+ getDownloadUrl(req: GetDownloadUrlRequest): Promise<GetDownloadUrlResponse>;
132
+ downloadFile(req: DownloadFileRequest): Promise<DownloadFileResponse>;
133
+ deleteFile(req: DeleteFileRequest): Promise<DeleteFileResponse>;
134
+ private _uploadToSignedUrl;
135
+ private _uploadToSignedForm;
136
+ uploadFile(req: UploadFileRequest): Promise<UploadFileResponse>;
22
137
  }
23
138
 
24
- export { type DidonatoOptions, DidonatoStorageClient, DidonatoStorageClient as default };
139
+ export { type ClientOptions, type CompleteResponse, type CreateUploadSignatureRequest, type CreateUploadSignatureResponse, type DeleteFileRequest, type DeleteFileResponse, DidonatoStorageClient, type DownloadFileRequest, type DownloadFileResponse, type FileMetadata, type FinalizeUploadRequest, type FinalizeUploadResponse, type GetDownloadUrlRequest, type GetDownloadUrlResponse, type GetFilesRequest, type GetFilesResponse, type IDidonatoStorageClient, type ListFilesMeta, type ListFilesResponse, type ProgressInfo, type SignResponse, type TokenProvider, type UploadFileRequest, type UploadFileResponse, type UploadToSignedFormRequest, type UploadToSignedUrlRequest, DidonatoStorageClient as default };
package/dist/index.d.ts CHANGED
@@ -1,24 +1,139 @@
1
- interface DidonatoOptions {
1
+ interface SignResponse {
2
+ key: string;
3
+ uploadUrl?: string;
4
+ url?: string;
5
+ fields?: Record<string, string>;
6
+ requiredHeaders?: Record<string, string>;
7
+ expiresIn?: number;
8
+ debug?: {
9
+ canonicalRequest?: string;
10
+ stringToSign?: string;
11
+ signatureHex?: string;
12
+ };
13
+ }
14
+ interface CreateUploadSignatureRequest {
15
+ filename: string;
16
+ contentType: string;
17
+ maxSize: number;
18
+ }
19
+ type CreateUploadSignatureResponse = SignResponse;
20
+ interface FileMetadata {
21
+ _id?: string;
22
+ ownerId: string;
23
+ key: string;
24
+ originalName: string;
25
+ contentType: string;
26
+ size: number;
27
+ createdAt: string;
28
+ }
29
+ interface CompleteResponse {
30
+ insertedId?: string;
31
+ doc?: FileMetadata;
32
+ }
33
+ interface FinalizeUploadRequest {
34
+ key: string;
35
+ originalName: string;
36
+ contentType: string;
37
+ size: number;
38
+ }
39
+ type FinalizeUploadResponse = CompleteResponse;
40
+ interface ListFilesMeta {
41
+ page: number;
42
+ limit: number;
43
+ total: number;
44
+ totalPages: number;
45
+ }
46
+ interface ListFilesResponse {
47
+ files: FileMetadata[];
48
+ meta: ListFilesMeta;
49
+ }
50
+ interface GetFilesRequest {
51
+ q?: string;
52
+ page?: number;
53
+ limit?: number;
54
+ }
55
+ type GetFilesResponse = ListFilesResponse;
56
+ interface GetDownloadUrlRequest {
57
+ id: string;
58
+ }
59
+ interface GetDownloadUrlResponse {
60
+ downloadUrl: string;
61
+ expiresIn?: number;
62
+ }
63
+ interface DownloadFileRequest {
64
+ id: string;
65
+ }
66
+ interface DownloadFileResponse {
67
+ blob: Blob;
68
+ }
69
+ interface DeleteFileRequest {
70
+ id: string;
71
+ }
72
+ interface DeleteFileResponse {
73
+ ok: boolean;
74
+ }
75
+ interface ProgressInfo {
76
+ loaded: number;
77
+ total: number;
78
+ percent?: number;
79
+ }
80
+ interface UploadToSignedUrlRequest {
81
+ uploadUrl: string;
82
+ file: Blob | File;
83
+ requiredHeaders?: Record<string, string>;
84
+ onProgress?: (p: ProgressInfo) => void;
85
+ }
86
+ interface UploadToSignedFormRequest {
87
+ uploadUrl: string;
88
+ fields: Record<string, string>;
89
+ file: Blob | File;
90
+ onProgress?: (p: ProgressInfo) => void;
91
+ }
92
+ interface UploadFileRequest {
93
+ file: Blob | File;
94
+ opts?: {
95
+ filename?: string;
96
+ contentType?: string;
97
+ size?: number;
98
+ };
99
+ onProgress?: (p: ProgressInfo) => void;
100
+ }
101
+ interface UploadFileResponse {
102
+ sig: SignResponse;
103
+ complete: CompleteResponse;
104
+ }
105
+ interface IDidonatoStorageClient {
106
+ createUploadSignature(req: CreateUploadSignatureRequest): Promise<CreateUploadSignatureResponse>;
107
+ finalizeUpload(req: FinalizeUploadRequest): Promise<FinalizeUploadResponse>;
108
+ getFiles(req?: GetFilesRequest): Promise<GetFilesResponse>;
109
+ getDownloadUrl(req: GetDownloadUrlRequest): Promise<GetDownloadUrlResponse>;
110
+ downloadFile(req: DownloadFileRequest): Promise<DownloadFileResponse>;
111
+ deleteFile(req: DeleteFileRequest): Promise<DeleteFileResponse>;
112
+ uploadFile?: (req: UploadFileRequest) => Promise<UploadFileResponse>;
113
+ }
114
+ type TokenProvider = string | (() => string | null | Promise<string | null>) | null;
115
+ interface ClientOptions {
2
116
  baseUrl?: string;
3
- bucket?: string;
4
- credentials?: Record<string, any>;
5
- /**
6
- * A static token string (Bearer) or a function that returns a token string (sync or Promise)
7
- */
8
- token?: string | (() => string | Promise<string>);
9
- }
10
- declare class DidonatoStorageClient {
11
- options: DidonatoOptions;
12
- constructor(opts?: DidonatoOptions);
13
- getToken(): Promise<string | undefined>;
14
- getSignedUrl(key: string, expiresSec?: number): Promise<string>;
15
- createSignedPost(key: string): Promise<{
16
- url: string;
17
- fields: Record<string, string>;
18
- }>;
19
- uploadWithSignedPost(url: string, fields: Record<string, string>, body: Buffer | Blob | string): Promise<boolean>;
20
- delete(key: string): Promise<boolean>;
21
- list(prefix?: string): Promise<string[]>;
117
+ getAuthToken?: TokenProvider;
118
+ token?: TokenProvider;
119
+ }
120
+
121
+ declare class DidonatoStorageClient implements IDidonatoStorageClient {
122
+ private baseUrl;
123
+ private getAuthToken;
124
+ constructor(opts?: ClientOptions);
125
+ private resolveToken;
126
+ private headers;
127
+ private _fetch;
128
+ createUploadSignature(req: CreateUploadSignatureRequest): Promise<CreateUploadSignatureResponse>;
129
+ finalizeUpload(req: FinalizeUploadRequest): Promise<FinalizeUploadResponse>;
130
+ getFiles(req?: GetFilesRequest): Promise<GetFilesResponse>;
131
+ getDownloadUrl(req: GetDownloadUrlRequest): Promise<GetDownloadUrlResponse>;
132
+ downloadFile(req: DownloadFileRequest): Promise<DownloadFileResponse>;
133
+ deleteFile(req: DeleteFileRequest): Promise<DeleteFileResponse>;
134
+ private _uploadToSignedUrl;
135
+ private _uploadToSignedForm;
136
+ uploadFile(req: UploadFileRequest): Promise<UploadFileResponse>;
22
137
  }
23
138
 
24
- export { type DidonatoOptions, DidonatoStorageClient, DidonatoStorageClient as default };
139
+ export { type ClientOptions, type CompleteResponse, type CreateUploadSignatureRequest, type CreateUploadSignatureResponse, type DeleteFileRequest, type DeleteFileResponse, DidonatoStorageClient, type DownloadFileRequest, type DownloadFileResponse, type FileMetadata, type FinalizeUploadRequest, type FinalizeUploadResponse, type GetDownloadUrlRequest, type GetDownloadUrlResponse, type GetFilesRequest, type GetFilesResponse, type IDidonatoStorageClient, type ListFilesMeta, type ListFilesResponse, type ProgressInfo, type SignResponse, type TokenProvider, type UploadFileRequest, type UploadFileResponse, type UploadToSignedFormRequest, type UploadToSignedUrlRequest, DidonatoStorageClient as default };
package/dist/index.js CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,81 +15,190 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
31
21
  var index_exports = {};
32
22
  __export(index_exports, {
33
23
  DidonatoStorageClient: () => DidonatoStorageClient,
34
- default: () => DidonatoStorageClient
24
+ default: () => client_default
35
25
  });
36
26
  module.exports = __toCommonJS(index_exports);
37
27
 
38
- // src/DidonatoStorageClient.ts
39
- var import_cross_fetch = __toESM(require("cross-fetch"));
28
+ // src/client.ts
40
29
  var DidonatoStorageClient = class {
41
30
  constructor(opts = {}) {
42
- this.options = opts;
31
+ this.baseUrl = opts.baseUrl || "";
32
+ this.getAuthToken = typeof opts.getAuthToken === "function" ? opts.getAuthToken : (opts.getAuthToken ?? opts.token) || null;
43
33
  }
44
- async getToken() {
45
- const t = this.options.token;
46
- if (!t) return void 0;
47
- if (typeof t === "function") {
34
+ async resolveToken() {
35
+ if (!this.getAuthToken) return null;
36
+ if (typeof this.getAuthToken === "function") {
48
37
  try {
49
- return await t();
50
- } catch (err) {
51
- return void 0;
38
+ const v = this.getAuthToken();
39
+ if (v instanceof Promise) return await v;
40
+ return v;
41
+ } catch {
42
+ return null;
52
43
  }
53
44
  }
54
- return String(t);
55
- }
56
- async getSignedUrl(key, expiresSec = 3600) {
57
- return `${this.options.baseUrl || "https://storage.example.com"}/signed/${encodeURIComponent(
58
- key
59
- )}?exp=${expiresSec}`;
45
+ return this.getAuthToken;
60
46
  }
61
- async createSignedPost(key) {
62
- const url = `${this.options.baseUrl || ""}/signed-post/${encodeURIComponent(key)}`;
63
- const res = await (0, import_cross_fetch.default)(url);
64
- if (!res.ok) throw new Error("failed to get signed post");
65
- const json = await res.json();
66
- return json;
47
+ async headers(extra = {}) {
48
+ const h = { ...extra };
49
+ const token = await this.resolveToken();
50
+ if (token) h["Authorization"] = `Bearer ${token}`;
51
+ return h;
67
52
  }
68
- async uploadWithSignedPost(url, fields, body) {
69
- if (typeof FormData !== "undefined") {
70
- const fd = new FormData();
71
- for (const k in fields) fd.append(k, fields[k]);
72
- fd.append("file", body);
73
- const res = await (0, import_cross_fetch.default)(url, { method: "POST", body: fd });
74
- return res.ok;
53
+ async _fetch(path, opts = {}) {
54
+ const url = (this.baseUrl || "") + path;
55
+ const resolvedHeaders = await this.headers();
56
+ const headers = { ...resolvedHeaders, ...opts.headers || {} };
57
+ const fetchOpts = { ...opts, headers };
58
+ const res = await fetch(url, fetchOpts);
59
+ if (!res.ok) {
60
+ let body = null;
61
+ try {
62
+ body = await res.json();
63
+ } catch (e) {
64
+ try {
65
+ body = await res.text();
66
+ } catch {
67
+ body = null;
68
+ }
69
+ }
70
+ const err = new Error("Request failed");
71
+ err.status = res.status;
72
+ err.body = body;
73
+ throw err;
74
+ }
75
+ const ct = res.headers && typeof res.headers.get === "function" ? res.headers.get("content-type") || "" : "";
76
+ if (ct.includes("application/json")) return await res.json();
77
+ try {
78
+ if (typeof res.text === "function") return await res.text();
79
+ } catch {
75
80
  }
76
- throw new Error("FormData not available; use form-data package in Node tests");
81
+ return null;
82
+ }
83
+ async createUploadSignature(req) {
84
+ return this._fetch("/api/sign-post", {
85
+ method: "POST",
86
+ headers: { "Content-Type": "application/json" },
87
+ body: JSON.stringify({
88
+ filename: req.filename,
89
+ contentType: req.contentType,
90
+ maxSize: req.maxSize,
91
+ expiresIn: 300
92
+ })
93
+ });
94
+ }
95
+ async finalizeUpload(req) {
96
+ return this._fetch("/api/complete-upload", {
97
+ method: "POST",
98
+ headers: { "Content-Type": "application/json" },
99
+ body: JSON.stringify(req)
100
+ });
101
+ }
102
+ async getFiles(req = {}) {
103
+ const qs = [];
104
+ if (req.q) qs.push("q=" + encodeURIComponent(req.q));
105
+ if (req.page) qs.push("page=" + encodeURIComponent(String(req.page)));
106
+ if (req.limit) qs.push("limit=" + encodeURIComponent(String(req.limit)));
107
+ const path = "/api/files" + (qs.length ? "?" + qs.join("&") : "");
108
+ return this._fetch(path, { method: "GET" });
109
+ }
110
+ async getDownloadUrl(req) {
111
+ return this._fetch(`/api/files/${encodeURIComponent(req.id)}/download`, { method: "GET" });
112
+ }
113
+ async downloadFile(req) {
114
+ const res = await this.getDownloadUrl({ id: req.id });
115
+ if (!res || !res.downloadUrl) throw new Error("No download URL");
116
+ const headers = await this.headers();
117
+ const r = await fetch(res.downloadUrl, { headers });
118
+ if (!r.ok) throw new Error("Download failed");
119
+ const blob = await r.blob();
120
+ return { blob };
77
121
  }
78
- async delete(key) {
79
- const url = `${this.options.baseUrl || ""}/objects/${encodeURIComponent(key)}`;
80
- const token = await this.getToken();
81
- const headers = token ? { Authorization: `Bearer ${token}` } : void 0;
82
- const res = await (0, import_cross_fetch.default)(url, { method: "DELETE", headers });
83
- return res.ok;
122
+ async deleteFile(req) {
123
+ await this._fetch(`/api/files/${encodeURIComponent(req.id)}`, { method: "DELETE" });
124
+ return { ok: true };
84
125
  }
85
- async list(prefix = "") {
86
- const url = `${this.options.baseUrl || ""}/objects?prefix=${encodeURIComponent(prefix)}`;
87
- const token = await this.getToken();
88
- const headers = token ? { Authorization: `Bearer ${token}` } : void 0;
89
- const res = await (0, import_cross_fetch.default)(url, { headers });
90
- if (!res.ok) return [];
91
- const json = await res.json();
92
- return Array.isArray(json) ? json : [];
126
+ _uploadToSignedUrl(req) {
127
+ return new Promise(async (resolve, reject) => {
128
+ try {
129
+ const authHeaders = await this.headers();
130
+ const xhr = new XMLHttpRequest();
131
+ xhr.open("PUT", req.uploadUrl);
132
+ Object.keys(authHeaders || {}).forEach((k) => {
133
+ try {
134
+ xhr.setRequestHeader(k, authHeaders[k]);
135
+ } catch {
136
+ }
137
+ });
138
+ Object.keys(req.requiredHeaders || {}).forEach((k) => {
139
+ try {
140
+ xhr.setRequestHeader(k, (req.requiredHeaders || {})[k]);
141
+ } catch {
142
+ }
143
+ });
144
+ xhr.upload.onprogress = (ev) => {
145
+ if (!ev.lengthComputable) return req.onProgress && req.onProgress({ loaded: ev.loaded, total: ev.total });
146
+ req.onProgress && req.onProgress({ loaded: ev.loaded, total: ev.total, percent: ev.loaded / ev.total * 100 });
147
+ };
148
+ xhr.onload = () => {
149
+ if (xhr.status >= 200 && xhr.status < 300) resolve({ status: xhr.status });
150
+ else reject(new Error("Upload failed: " + xhr.status));
151
+ };
152
+ xhr.onerror = () => reject(new Error("Upload error"));
153
+ xhr.send(req.file);
154
+ } catch (err) {
155
+ reject(err);
156
+ }
157
+ });
158
+ }
159
+ _uploadToSignedForm(req) {
160
+ return new Promise(async (resolve, reject) => {
161
+ try {
162
+ const xhr = new XMLHttpRequest();
163
+ xhr.open("POST", req.uploadUrl);
164
+ const form = new FormData();
165
+ Object.entries(req.fields || {}).forEach(([k, v]) => form.append(k, v));
166
+ form.append("file", req.file);
167
+ xhr.upload.onprogress = (ev) => {
168
+ if (!ev.lengthComputable) return req.onProgress && req.onProgress({ loaded: ev.loaded, total: ev.total });
169
+ req.onProgress && req.onProgress({ loaded: ev.loaded, total: ev.total, percent: ev.loaded / ev.total * 100 });
170
+ };
171
+ xhr.onload = () => {
172
+ if (xhr.status >= 200 && xhr.status < 300) resolve({ status: xhr.status });
173
+ else reject(new Error("Upload failed: " + xhr.status));
174
+ };
175
+ xhr.onerror = () => reject(new Error("Upload error"));
176
+ xhr.send(form);
177
+ } catch (err) {
178
+ reject(err);
179
+ }
180
+ });
181
+ }
182
+ async uploadFile(req) {
183
+ const file = req.file;
184
+ const opts = req.opts || {};
185
+ const onProgress = req.onProgress;
186
+ const filename = opts.filename ?? (file instanceof File ? file.name : "file.bin");
187
+ const contentType = opts.contentType ?? (file instanceof File ? file.type || "application/octet-stream" : "application/octet-stream");
188
+ const size = typeof opts.size === "number" ? opts.size : file instanceof File ? file.size : 0;
189
+ const sig = await this.createUploadSignature({ filename, contentType, maxSize: size });
190
+ if (!sig) throw new Error("No signature from server");
191
+ if (sig.url || sig.uploadUrl) {
192
+ const postUrl = sig.url || sig.uploadUrl;
193
+ await this._uploadToSignedForm({ uploadUrl: postUrl, fields: sig.fields || {}, file, onProgress });
194
+ } else {
195
+ throw new Error("No usable upload info from server");
196
+ }
197
+ const complete = await this.finalizeUpload({ key: sig.key, originalName: filename, contentType, size });
198
+ return { sig, complete };
93
199
  }
94
200
  };
201
+ var client_default = DidonatoStorageClient;
95
202
  // Annotate the CommonJS export names for ESM import in node:
96
203
  0 && (module.exports = {
97
204
  DidonatoStorageClient
package/dist/index.mjs CHANGED
@@ -1,61 +1,178 @@
1
- // src/DidonatoStorageClient.ts
2
- import fetch from "cross-fetch";
1
+ // src/client.ts
3
2
  var DidonatoStorageClient = class {
4
3
  constructor(opts = {}) {
5
- this.options = opts;
4
+ this.baseUrl = opts.baseUrl || "";
5
+ this.getAuthToken = typeof opts.getAuthToken === "function" ? opts.getAuthToken : (opts.getAuthToken ?? opts.token) || null;
6
6
  }
7
- async getToken() {
8
- const t = this.options.token;
9
- if (!t) return void 0;
10
- if (typeof t === "function") {
7
+ async resolveToken() {
8
+ if (!this.getAuthToken) return null;
9
+ if (typeof this.getAuthToken === "function") {
11
10
  try {
12
- return await t();
13
- } catch (err) {
14
- return void 0;
11
+ const v = this.getAuthToken();
12
+ if (v instanceof Promise) return await v;
13
+ return v;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+ return this.getAuthToken;
19
+ }
20
+ async headers(extra = {}) {
21
+ const h = { ...extra };
22
+ const token = await this.resolveToken();
23
+ if (token) h["Authorization"] = `Bearer ${token}`;
24
+ return h;
25
+ }
26
+ async _fetch(path, opts = {}) {
27
+ const url = (this.baseUrl || "") + path;
28
+ const resolvedHeaders = await this.headers();
29
+ const headers = { ...resolvedHeaders, ...opts.headers || {} };
30
+ const fetchOpts = { ...opts, headers };
31
+ const res = await fetch(url, fetchOpts);
32
+ if (!res.ok) {
33
+ let body = null;
34
+ try {
35
+ body = await res.json();
36
+ } catch (e) {
37
+ try {
38
+ body = await res.text();
39
+ } catch {
40
+ body = null;
41
+ }
15
42
  }
43
+ const err = new Error("Request failed");
44
+ err.status = res.status;
45
+ err.body = body;
46
+ throw err;
16
47
  }
17
- return String(t);
18
- }
19
- async getSignedUrl(key, expiresSec = 3600) {
20
- return `${this.options.baseUrl || "https://storage.example.com"}/signed/${encodeURIComponent(
21
- key
22
- )}?exp=${expiresSec}`;
23
- }
24
- async createSignedPost(key) {
25
- const url = `${this.options.baseUrl || ""}/signed-post/${encodeURIComponent(key)}`;
26
- const res = await fetch(url);
27
- if (!res.ok) throw new Error("failed to get signed post");
28
- const json = await res.json();
29
- return json;
30
- }
31
- async uploadWithSignedPost(url, fields, body) {
32
- if (typeof FormData !== "undefined") {
33
- const fd = new FormData();
34
- for (const k in fields) fd.append(k, fields[k]);
35
- fd.append("file", body);
36
- const res = await fetch(url, { method: "POST", body: fd });
37
- return res.ok;
48
+ const ct = res.headers && typeof res.headers.get === "function" ? res.headers.get("content-type") || "" : "";
49
+ if (ct.includes("application/json")) return await res.json();
50
+ try {
51
+ if (typeof res.text === "function") return await res.text();
52
+ } catch {
53
+ }
54
+ return null;
55
+ }
56
+ async createUploadSignature(req) {
57
+ return this._fetch("/api/sign-post", {
58
+ method: "POST",
59
+ headers: { "Content-Type": "application/json" },
60
+ body: JSON.stringify({
61
+ filename: req.filename,
62
+ contentType: req.contentType,
63
+ maxSize: req.maxSize,
64
+ expiresIn: 300
65
+ })
66
+ });
67
+ }
68
+ async finalizeUpload(req) {
69
+ return this._fetch("/api/complete-upload", {
70
+ method: "POST",
71
+ headers: { "Content-Type": "application/json" },
72
+ body: JSON.stringify(req)
73
+ });
74
+ }
75
+ async getFiles(req = {}) {
76
+ const qs = [];
77
+ if (req.q) qs.push("q=" + encodeURIComponent(req.q));
78
+ if (req.page) qs.push("page=" + encodeURIComponent(String(req.page)));
79
+ if (req.limit) qs.push("limit=" + encodeURIComponent(String(req.limit)));
80
+ const path = "/api/files" + (qs.length ? "?" + qs.join("&") : "");
81
+ return this._fetch(path, { method: "GET" });
82
+ }
83
+ async getDownloadUrl(req) {
84
+ return this._fetch(`/api/files/${encodeURIComponent(req.id)}/download`, { method: "GET" });
85
+ }
86
+ async downloadFile(req) {
87
+ const res = await this.getDownloadUrl({ id: req.id });
88
+ if (!res || !res.downloadUrl) throw new Error("No download URL");
89
+ const headers = await this.headers();
90
+ const r = await fetch(res.downloadUrl, { headers });
91
+ if (!r.ok) throw new Error("Download failed");
92
+ const blob = await r.blob();
93
+ return { blob };
94
+ }
95
+ async deleteFile(req) {
96
+ await this._fetch(`/api/files/${encodeURIComponent(req.id)}`, { method: "DELETE" });
97
+ return { ok: true };
98
+ }
99
+ _uploadToSignedUrl(req) {
100
+ return new Promise(async (resolve, reject) => {
101
+ try {
102
+ const authHeaders = await this.headers();
103
+ const xhr = new XMLHttpRequest();
104
+ xhr.open("PUT", req.uploadUrl);
105
+ Object.keys(authHeaders || {}).forEach((k) => {
106
+ try {
107
+ xhr.setRequestHeader(k, authHeaders[k]);
108
+ } catch {
109
+ }
110
+ });
111
+ Object.keys(req.requiredHeaders || {}).forEach((k) => {
112
+ try {
113
+ xhr.setRequestHeader(k, (req.requiredHeaders || {})[k]);
114
+ } catch {
115
+ }
116
+ });
117
+ xhr.upload.onprogress = (ev) => {
118
+ if (!ev.lengthComputable) return req.onProgress && req.onProgress({ loaded: ev.loaded, total: ev.total });
119
+ req.onProgress && req.onProgress({ loaded: ev.loaded, total: ev.total, percent: ev.loaded / ev.total * 100 });
120
+ };
121
+ xhr.onload = () => {
122
+ if (xhr.status >= 200 && xhr.status < 300) resolve({ status: xhr.status });
123
+ else reject(new Error("Upload failed: " + xhr.status));
124
+ };
125
+ xhr.onerror = () => reject(new Error("Upload error"));
126
+ xhr.send(req.file);
127
+ } catch (err) {
128
+ reject(err);
129
+ }
130
+ });
131
+ }
132
+ _uploadToSignedForm(req) {
133
+ return new Promise(async (resolve, reject) => {
134
+ try {
135
+ const xhr = new XMLHttpRequest();
136
+ xhr.open("POST", req.uploadUrl);
137
+ const form = new FormData();
138
+ Object.entries(req.fields || {}).forEach(([k, v]) => form.append(k, v));
139
+ form.append("file", req.file);
140
+ xhr.upload.onprogress = (ev) => {
141
+ if (!ev.lengthComputable) return req.onProgress && req.onProgress({ loaded: ev.loaded, total: ev.total });
142
+ req.onProgress && req.onProgress({ loaded: ev.loaded, total: ev.total, percent: ev.loaded / ev.total * 100 });
143
+ };
144
+ xhr.onload = () => {
145
+ if (xhr.status >= 200 && xhr.status < 300) resolve({ status: xhr.status });
146
+ else reject(new Error("Upload failed: " + xhr.status));
147
+ };
148
+ xhr.onerror = () => reject(new Error("Upload error"));
149
+ xhr.send(form);
150
+ } catch (err) {
151
+ reject(err);
152
+ }
153
+ });
154
+ }
155
+ async uploadFile(req) {
156
+ const file = req.file;
157
+ const opts = req.opts || {};
158
+ const onProgress = req.onProgress;
159
+ const filename = opts.filename ?? (file instanceof File ? file.name : "file.bin");
160
+ const contentType = opts.contentType ?? (file instanceof File ? file.type || "application/octet-stream" : "application/octet-stream");
161
+ const size = typeof opts.size === "number" ? opts.size : file instanceof File ? file.size : 0;
162
+ const sig = await this.createUploadSignature({ filename, contentType, maxSize: size });
163
+ if (!sig) throw new Error("No signature from server");
164
+ if (sig.url || sig.uploadUrl) {
165
+ const postUrl = sig.url || sig.uploadUrl;
166
+ await this._uploadToSignedForm({ uploadUrl: postUrl, fields: sig.fields || {}, file, onProgress });
167
+ } else {
168
+ throw new Error("No usable upload info from server");
38
169
  }
39
- throw new Error("FormData not available; use form-data package in Node tests");
40
- }
41
- async delete(key) {
42
- const url = `${this.options.baseUrl || ""}/objects/${encodeURIComponent(key)}`;
43
- const token = await this.getToken();
44
- const headers = token ? { Authorization: `Bearer ${token}` } : void 0;
45
- const res = await fetch(url, { method: "DELETE", headers });
46
- return res.ok;
47
- }
48
- async list(prefix = "") {
49
- const url = `${this.options.baseUrl || ""}/objects?prefix=${encodeURIComponent(prefix)}`;
50
- const token = await this.getToken();
51
- const headers = token ? { Authorization: `Bearer ${token}` } : void 0;
52
- const res = await fetch(url, { headers });
53
- if (!res.ok) return [];
54
- const json = await res.json();
55
- return Array.isArray(json) ? json : [];
170
+ const complete = await this.finalizeUpload({ key: sig.key, originalName: filename, contentType, size });
171
+ return { sig, complete };
56
172
  }
57
173
  };
174
+ var client_default = DidonatoStorageClient;
58
175
  export {
59
176
  DidonatoStorageClient,
60
- DidonatoStorageClient as default
177
+ client_default as default
61
178
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applica-software-guru/didonato-storage-client",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
4
4
  "description": "Client per Didonato Storage (Node + Browser)",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",