@signskart/uploader 1.0.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/README.md +0 -0
- package/dist/index.d.mts +90 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +248 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +218 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +38 -0
package/README.md
ADDED
|
File without changes
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
type UploadProvider = 's3' | 'cloudinary';
|
|
2
|
+
interface UploadOptions {
|
|
3
|
+
file: File;
|
|
4
|
+
folder: string;
|
|
5
|
+
fileName?: string;
|
|
6
|
+
metadata?: Record<string, any>;
|
|
7
|
+
}
|
|
8
|
+
interface UploadResponse {
|
|
9
|
+
url: string;
|
|
10
|
+
provider: UploadProvider;
|
|
11
|
+
}
|
|
12
|
+
type UploadStatus = 'queued' | 'uploading' | 'success' | 'error' | 'cancelled';
|
|
13
|
+
interface UploadTaskState {
|
|
14
|
+
id: string;
|
|
15
|
+
progress: number;
|
|
16
|
+
status: UploadStatus;
|
|
17
|
+
error?: string;
|
|
18
|
+
response?: UploadResponse;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare abstract class BaseUploader {
|
|
22
|
+
abstract upload(options: UploadOptions, onProgress: (percent: number) => void, signal?: AbortSignal): Promise<UploadResponse>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type Listener<T> = (payload: T) => void;
|
|
26
|
+
declare class EventEmitter<T> {
|
|
27
|
+
private listeners;
|
|
28
|
+
subscribe(listener: Listener<T>): () => void;
|
|
29
|
+
emit(payload: T): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare class UploadTask {
|
|
33
|
+
private uploader;
|
|
34
|
+
private options;
|
|
35
|
+
private maxRetries;
|
|
36
|
+
private retryDelay;
|
|
37
|
+
state: UploadTaskState;
|
|
38
|
+
events: EventEmitter<UploadTaskState>;
|
|
39
|
+
private abortController;
|
|
40
|
+
private retries;
|
|
41
|
+
constructor(uploader: BaseUploader, options: UploadOptions, maxRetries?: number, retryDelay?: number);
|
|
42
|
+
/**
|
|
43
|
+
* Starts the upload process
|
|
44
|
+
*/
|
|
45
|
+
start(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Cancels the upload
|
|
48
|
+
*/
|
|
49
|
+
cancel(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Internal state updater
|
|
52
|
+
*/
|
|
53
|
+
private update;
|
|
54
|
+
/**
|
|
55
|
+
* Utility sleep helper
|
|
56
|
+
*/
|
|
57
|
+
private sleep;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
declare class UploadManager {
|
|
61
|
+
private uploader;
|
|
62
|
+
private concurrency;
|
|
63
|
+
private queue;
|
|
64
|
+
private active;
|
|
65
|
+
constructor(uploader: BaseUploader, concurrency?: number);
|
|
66
|
+
add(options: UploadOptions): UploadTask;
|
|
67
|
+
private process;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface S3Config {
|
|
71
|
+
apiBaseUrl: string;
|
|
72
|
+
publicUrl: string;
|
|
73
|
+
}
|
|
74
|
+
declare class S3Uploader extends BaseUploader {
|
|
75
|
+
private config;
|
|
76
|
+
constructor(config: S3Config);
|
|
77
|
+
upload(options: UploadOptions, onProgress: (percent: number) => void, signal?: AbortSignal): Promise<UploadResponse>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface CloudinaryConfig {
|
|
81
|
+
cloudName: string;
|
|
82
|
+
uploadPreset: string;
|
|
83
|
+
}
|
|
84
|
+
declare class CloudinaryUploader extends BaseUploader {
|
|
85
|
+
private config;
|
|
86
|
+
constructor(config: CloudinaryConfig);
|
|
87
|
+
upload(options: UploadOptions, onProgress: (percent: number) => void): Promise<UploadResponse>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { CloudinaryUploader, S3Uploader, UploadManager, type UploadOptions, type UploadProvider, type UploadResponse, type UploadStatus, UploadTask, type UploadTaskState };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
type UploadProvider = 's3' | 'cloudinary';
|
|
2
|
+
interface UploadOptions {
|
|
3
|
+
file: File;
|
|
4
|
+
folder: string;
|
|
5
|
+
fileName?: string;
|
|
6
|
+
metadata?: Record<string, any>;
|
|
7
|
+
}
|
|
8
|
+
interface UploadResponse {
|
|
9
|
+
url: string;
|
|
10
|
+
provider: UploadProvider;
|
|
11
|
+
}
|
|
12
|
+
type UploadStatus = 'queued' | 'uploading' | 'success' | 'error' | 'cancelled';
|
|
13
|
+
interface UploadTaskState {
|
|
14
|
+
id: string;
|
|
15
|
+
progress: number;
|
|
16
|
+
status: UploadStatus;
|
|
17
|
+
error?: string;
|
|
18
|
+
response?: UploadResponse;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
declare abstract class BaseUploader {
|
|
22
|
+
abstract upload(options: UploadOptions, onProgress: (percent: number) => void, signal?: AbortSignal): Promise<UploadResponse>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type Listener<T> = (payload: T) => void;
|
|
26
|
+
declare class EventEmitter<T> {
|
|
27
|
+
private listeners;
|
|
28
|
+
subscribe(listener: Listener<T>): () => void;
|
|
29
|
+
emit(payload: T): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare class UploadTask {
|
|
33
|
+
private uploader;
|
|
34
|
+
private options;
|
|
35
|
+
private maxRetries;
|
|
36
|
+
private retryDelay;
|
|
37
|
+
state: UploadTaskState;
|
|
38
|
+
events: EventEmitter<UploadTaskState>;
|
|
39
|
+
private abortController;
|
|
40
|
+
private retries;
|
|
41
|
+
constructor(uploader: BaseUploader, options: UploadOptions, maxRetries?: number, retryDelay?: number);
|
|
42
|
+
/**
|
|
43
|
+
* Starts the upload process
|
|
44
|
+
*/
|
|
45
|
+
start(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Cancels the upload
|
|
48
|
+
*/
|
|
49
|
+
cancel(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Internal state updater
|
|
52
|
+
*/
|
|
53
|
+
private update;
|
|
54
|
+
/**
|
|
55
|
+
* Utility sleep helper
|
|
56
|
+
*/
|
|
57
|
+
private sleep;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
declare class UploadManager {
|
|
61
|
+
private uploader;
|
|
62
|
+
private concurrency;
|
|
63
|
+
private queue;
|
|
64
|
+
private active;
|
|
65
|
+
constructor(uploader: BaseUploader, concurrency?: number);
|
|
66
|
+
add(options: UploadOptions): UploadTask;
|
|
67
|
+
private process;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface S3Config {
|
|
71
|
+
apiBaseUrl: string;
|
|
72
|
+
publicUrl: string;
|
|
73
|
+
}
|
|
74
|
+
declare class S3Uploader extends BaseUploader {
|
|
75
|
+
private config;
|
|
76
|
+
constructor(config: S3Config);
|
|
77
|
+
upload(options: UploadOptions, onProgress: (percent: number) => void, signal?: AbortSignal): Promise<UploadResponse>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface CloudinaryConfig {
|
|
81
|
+
cloudName: string;
|
|
82
|
+
uploadPreset: string;
|
|
83
|
+
}
|
|
84
|
+
declare class CloudinaryUploader extends BaseUploader {
|
|
85
|
+
private config;
|
|
86
|
+
constructor(config: CloudinaryConfig);
|
|
87
|
+
upload(options: UploadOptions, onProgress: (percent: number) => void): Promise<UploadResponse>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { CloudinaryUploader, S3Uploader, UploadManager, type UploadOptions, type UploadProvider, type UploadResponse, type UploadStatus, UploadTask, type UploadTaskState };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
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
|
+
CloudinaryUploader: () => CloudinaryUploader,
|
|
24
|
+
S3Uploader: () => S3Uploader,
|
|
25
|
+
UploadManager: () => UploadManager,
|
|
26
|
+
UploadTask: () => UploadTask
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/core/EventEmitter.ts
|
|
31
|
+
var EventEmitter = class {
|
|
32
|
+
constructor() {
|
|
33
|
+
this.listeners = [];
|
|
34
|
+
}
|
|
35
|
+
subscribe(listener) {
|
|
36
|
+
this.listeners.push(listener);
|
|
37
|
+
return () => {
|
|
38
|
+
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
emit(payload) {
|
|
42
|
+
this.listeners.forEach((listener) => listener(payload));
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/core/UploadTask.ts
|
|
47
|
+
var UploadTask = class {
|
|
48
|
+
constructor(uploader, options, maxRetries = 2, retryDelay = 500) {
|
|
49
|
+
this.uploader = uploader;
|
|
50
|
+
this.options = options;
|
|
51
|
+
this.maxRetries = maxRetries;
|
|
52
|
+
this.retryDelay = retryDelay;
|
|
53
|
+
this.events = new EventEmitter();
|
|
54
|
+
this.abortController = new AbortController();
|
|
55
|
+
this.retries = 0;
|
|
56
|
+
this.state = {
|
|
57
|
+
id: crypto.randomUUID(),
|
|
58
|
+
progress: 0,
|
|
59
|
+
status: "queued"
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Starts the upload process
|
|
64
|
+
*/
|
|
65
|
+
async start() {
|
|
66
|
+
this.update({ status: "uploading" });
|
|
67
|
+
while (this.retries <= this.maxRetries) {
|
|
68
|
+
try {
|
|
69
|
+
const response = await this.uploader.upload(
|
|
70
|
+
this.options,
|
|
71
|
+
(progress) => {
|
|
72
|
+
this.update({ progress });
|
|
73
|
+
},
|
|
74
|
+
this.abortController.signal
|
|
75
|
+
);
|
|
76
|
+
this.update({
|
|
77
|
+
status: "success",
|
|
78
|
+
progress: 100,
|
|
79
|
+
response
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
if (this.abortController.signal.aborted) {
|
|
84
|
+
this.update({ status: "cancelled" });
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const message = error instanceof Error ? error.message : "Unknown upload error";
|
|
88
|
+
if (this.retries >= this.maxRetries) {
|
|
89
|
+
this.update({
|
|
90
|
+
status: "error",
|
|
91
|
+
error: message
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
this.retries++;
|
|
96
|
+
const delay = this.retryDelay * Math.pow(2, this.retries - 1);
|
|
97
|
+
await this.sleep(delay);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Cancels the upload
|
|
103
|
+
*/
|
|
104
|
+
cancel() {
|
|
105
|
+
this.abortController.abort();
|
|
106
|
+
this.update({ status: "cancelled" });
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Internal state updater
|
|
110
|
+
*/
|
|
111
|
+
update(update) {
|
|
112
|
+
this.state = { ...this.state, ...update };
|
|
113
|
+
this.events.emit(this.state);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Utility sleep helper
|
|
117
|
+
*/
|
|
118
|
+
sleep(ms) {
|
|
119
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// src/core/UploadManager.ts
|
|
124
|
+
var UploadManager = class {
|
|
125
|
+
constructor(uploader, concurrency = 3) {
|
|
126
|
+
this.uploader = uploader;
|
|
127
|
+
this.concurrency = concurrency;
|
|
128
|
+
this.queue = [];
|
|
129
|
+
this.active = 0;
|
|
130
|
+
}
|
|
131
|
+
add(options) {
|
|
132
|
+
const task = new UploadTask(this.uploader, options);
|
|
133
|
+
this.queue.push(task);
|
|
134
|
+
this.process();
|
|
135
|
+
return task;
|
|
136
|
+
}
|
|
137
|
+
async process() {
|
|
138
|
+
if (this.active >= this.concurrency) return;
|
|
139
|
+
const next = this.queue.find((t) => t.state.status === "queued");
|
|
140
|
+
if (!next) return;
|
|
141
|
+
this.active++;
|
|
142
|
+
await next.start();
|
|
143
|
+
this.active--;
|
|
144
|
+
this.process();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// src/core/BaseUploader.ts
|
|
149
|
+
var BaseUploader = class {
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/providers/S3Uploader.ts
|
|
153
|
+
var S3Uploader = class extends BaseUploader {
|
|
154
|
+
constructor(config) {
|
|
155
|
+
super();
|
|
156
|
+
this.config = config;
|
|
157
|
+
}
|
|
158
|
+
async upload(options, onProgress, signal) {
|
|
159
|
+
const presignRes = await fetch(
|
|
160
|
+
`${this.config.apiBaseUrl}/s3/presign-upload`,
|
|
161
|
+
{
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: { "Content-Type": "application/json" },
|
|
164
|
+
body: JSON.stringify({
|
|
165
|
+
fileName: options.fileName || options.file.name,
|
|
166
|
+
folder: options.folder,
|
|
167
|
+
contentType: options.file.type
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
const { uploadUrl, key } = await presignRes.json();
|
|
172
|
+
return new Promise((resolve, reject) => {
|
|
173
|
+
const xhr = new XMLHttpRequest();
|
|
174
|
+
xhr.upload.onprogress = (event) => {
|
|
175
|
+
if (event.lengthComputable) {
|
|
176
|
+
const percent = event.loaded / event.total * 100;
|
|
177
|
+
onProgress(Math.round(percent));
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
xhr.onload = () => {
|
|
181
|
+
if (xhr.status === 200) {
|
|
182
|
+
resolve({
|
|
183
|
+
url: `${this.config.publicUrl}/${key}`,
|
|
184
|
+
provider: "s3"
|
|
185
|
+
});
|
|
186
|
+
} else {
|
|
187
|
+
reject(new Error("S3 upload failed"));
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
xhr.onerror = () => reject(new Error("S3 upload failed"));
|
|
191
|
+
signal?.addEventListener("abort", () => {
|
|
192
|
+
xhr.abort();
|
|
193
|
+
reject(new Error("Upload cancelled"));
|
|
194
|
+
});
|
|
195
|
+
xhr.open("PUT", uploadUrl);
|
|
196
|
+
xhr.setRequestHeader("Content-Type", options.file.type);
|
|
197
|
+
xhr.send(options.file);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// src/providers/CloudinaryUploader.ts
|
|
203
|
+
var CloudinaryUploader = class extends BaseUploader {
|
|
204
|
+
constructor(config) {
|
|
205
|
+
super();
|
|
206
|
+
this.config = config;
|
|
207
|
+
}
|
|
208
|
+
async upload(options, onProgress) {
|
|
209
|
+
const formData = new FormData();
|
|
210
|
+
formData.append("file", options.file);
|
|
211
|
+
formData.append("upload_preset", this.config.uploadPreset);
|
|
212
|
+
formData.append("folder", options.folder);
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
const xhr = new XMLHttpRequest();
|
|
215
|
+
xhr.upload.onprogress = (event) => {
|
|
216
|
+
if (event.lengthComputable) {
|
|
217
|
+
const percent = event.loaded / event.total * 100;
|
|
218
|
+
onProgress(Math.round(percent));
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
xhr.onload = () => {
|
|
222
|
+
const data = JSON.parse(xhr.responseText);
|
|
223
|
+
if (xhr.status === 200 && data.secure_url) {
|
|
224
|
+
resolve({
|
|
225
|
+
url: data.secure_url,
|
|
226
|
+
provider: "cloudinary"
|
|
227
|
+
});
|
|
228
|
+
} else {
|
|
229
|
+
reject(new Error("Cloudinary upload failed"));
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
xhr.onerror = () => reject(new Error("Cloudinary upload failed"));
|
|
233
|
+
xhr.open(
|
|
234
|
+
"POST",
|
|
235
|
+
`https://api.cloudinary.com/v1_1/${this.config.cloudName}/upload`
|
|
236
|
+
);
|
|
237
|
+
xhr.send(formData);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
242
|
+
0 && (module.exports = {
|
|
243
|
+
CloudinaryUploader,
|
|
244
|
+
S3Uploader,
|
|
245
|
+
UploadManager,
|
|
246
|
+
UploadTask
|
|
247
|
+
});
|
|
248
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/EventEmitter.ts","../src/core/UploadTask.ts","../src/core/UploadManager.ts","../src/core/BaseUploader.ts","../src/providers/S3Uploader.ts","../src/providers/CloudinaryUploader.ts"],"sourcesContent":["export * from './core/types';\nexport * from './core/UploadManager';\nexport * from './core/UploadTask';\nexport * from './providers/S3Uploader';\nexport * from './providers/CloudinaryUploader';","type Listener<T> = (payload: T) => void;\n\nexport class EventEmitter<T> {\n private listeners: Listener<T>[] = [];\n\n subscribe(listener: Listener<T>) {\n this.listeners.push(listener);\n return () => {\n this.listeners = this.listeners.filter(l => l !== listener);\n };\n }\n\n emit(payload: T) {\n this.listeners.forEach(listener => listener(payload));\n }\n}","import { BaseUploader } from './BaseUploader';\nimport { UploadOptions, UploadTaskState } from './types';\nimport { EventEmitter } from './EventEmitter';\n\nexport class UploadTask {\n public state: UploadTaskState;\n public events = new EventEmitter<UploadTaskState>();\n\n private abortController = new AbortController();\n private retries = 0;\n\n constructor(\n private uploader: BaseUploader,\n private options: UploadOptions,\n private maxRetries = 2,\n private retryDelay = 500 // base delay in ms\n ) {\n this.state = {\n id: crypto.randomUUID(),\n progress: 0,\n status: 'queued',\n };\n }\n\n /**\n * Starts the upload process\n */\n async start(): Promise<void> {\n this.update({ status: 'uploading' });\n\n while (this.retries <= this.maxRetries) {\n try {\n const response = await this.uploader.upload(\n this.options,\n (progress: number) => {\n this.update({ progress });\n },\n this.abortController.signal\n );\n\n this.update({\n status: 'success',\n progress: 100,\n response,\n });\n\n return;\n } catch (error: unknown) {\n if (this.abortController.signal.aborted) {\n this.update({ status: 'cancelled' });\n return;\n }\n\n const message =\n error instanceof Error\n ? error.message\n : 'Unknown upload error';\n\n if (this.retries >= this.maxRetries) {\n this.update({\n status: 'error',\n error: message,\n });\n return;\n }\n\n this.retries++;\n\n // Exponential backoff delay\n const delay = this.retryDelay * Math.pow(2, this.retries - 1);\n await this.sleep(delay);\n }\n }\n }\n\n /**\n * Cancels the upload\n */\n cancel(): void {\n this.abortController.abort();\n this.update({ status: 'cancelled' });\n }\n\n /**\n * Internal state updater\n */\n private update(update: Partial<UploadTaskState>): void {\n this.state = { ...this.state, ...update };\n this.events.emit(this.state);\n }\n\n /**\n * Utility sleep helper\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}","import { BaseUploader } from './BaseUploader';\nimport { UploadOptions } from './types';\nimport { UploadTask } from './UploadTask';\n\nexport class UploadManager {\n private queue: UploadTask[] = [];\n private active = 0;\n\n constructor(\n private uploader: BaseUploader,\n private concurrency = 3\n ) { }\n\n add(options: UploadOptions) {\n const task = new UploadTask(this.uploader, options);\n this.queue.push(task);\n this.process();\n return task;\n }\n\n private async process() {\n if (this.active >= this.concurrency) return;\n\n const next = this.queue.find(t => t.state.status === 'queued');\n if (!next) return;\n\n this.active++;\n await next.start();\n this.active--;\n\n this.process();\n }\n}","import { UploadOptions, UploadResponse } from './types';\n\nexport abstract class BaseUploader {\n abstract upload(options: UploadOptions, onProgress: (percent: number) => void, signal?: AbortSignal): Promise<UploadResponse>;\n}","import { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\ninterface S3Config {\n apiBaseUrl: string;\n publicUrl: string;\n}\n\nexport class S3Uploader extends BaseUploader {\n constructor(private config: S3Config) {\n super();\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void,\n signal?: AbortSignal\n ): Promise<UploadResponse> {\n const presignRes = await fetch(\n `${this.config.apiBaseUrl}/s3/presign-upload`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n fileName: options.fileName || options.file.name,\n folder: options.folder,\n contentType: options.file.type,\n }),\n }\n );\n\n const { uploadUrl, key } = await presignRes.json();\n\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n const percent = (event.loaded / event.total) * 100;\n onProgress(Math.round(percent));\n }\n };\n\n xhr.onload = () => {\n if (xhr.status === 200) {\n resolve({\n url: `${this.config.publicUrl}/${key}`,\n provider: 's3',\n });\n } else {\n reject(new Error('S3 upload failed'));\n }\n };\n\n xhr.onerror = () => reject(new Error('S3 upload failed'));\n\n signal?.addEventListener('abort', () => {\n xhr.abort();\n reject(new Error('Upload cancelled'));\n });\n\n xhr.open('PUT', uploadUrl);\n xhr.setRequestHeader('Content-Type', options.file.type);\n xhr.send(options.file);\n });\n }\n}","import { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\ninterface CloudinaryConfig {\n cloudName: string;\n uploadPreset: string;\n}\n\nexport class CloudinaryUploader extends BaseUploader {\n constructor(private config: CloudinaryConfig) {\n super();\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void\n ): Promise<UploadResponse> {\n const formData = new FormData();\n formData.append('file', options.file);\n formData.append('upload_preset', this.config.uploadPreset);\n formData.append('folder', options.folder);\n\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n const percent = (event.loaded / event.total) * 100;\n onProgress(Math.round(percent));\n }\n };\n\n xhr.onload = () => {\n const data = JSON.parse(xhr.responseText);\n if (xhr.status === 200 && data.secure_url) {\n resolve({\n url: data.secure_url,\n provider: 'cloudinary',\n });\n } else {\n reject(new Error('Cloudinary upload failed'));\n }\n };\n\n xhr.onerror = () => reject(new Error('Cloudinary upload failed'));\n\n xhr.open(\n 'POST',\n `https://api.cloudinary.com/v1_1/${this.config.cloudName}/upload`\n );\n\n xhr.send(formData);\n });\n }\n}"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// src/core/EventEmitter.ts
|
|
2
|
+
var EventEmitter = class {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.listeners = [];
|
|
5
|
+
}
|
|
6
|
+
subscribe(listener) {
|
|
7
|
+
this.listeners.push(listener);
|
|
8
|
+
return () => {
|
|
9
|
+
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
emit(payload) {
|
|
13
|
+
this.listeners.forEach((listener) => listener(payload));
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/core/UploadTask.ts
|
|
18
|
+
var UploadTask = class {
|
|
19
|
+
constructor(uploader, options, maxRetries = 2, retryDelay = 500) {
|
|
20
|
+
this.uploader = uploader;
|
|
21
|
+
this.options = options;
|
|
22
|
+
this.maxRetries = maxRetries;
|
|
23
|
+
this.retryDelay = retryDelay;
|
|
24
|
+
this.events = new EventEmitter();
|
|
25
|
+
this.abortController = new AbortController();
|
|
26
|
+
this.retries = 0;
|
|
27
|
+
this.state = {
|
|
28
|
+
id: crypto.randomUUID(),
|
|
29
|
+
progress: 0,
|
|
30
|
+
status: "queued"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Starts the upload process
|
|
35
|
+
*/
|
|
36
|
+
async start() {
|
|
37
|
+
this.update({ status: "uploading" });
|
|
38
|
+
while (this.retries <= this.maxRetries) {
|
|
39
|
+
try {
|
|
40
|
+
const response = await this.uploader.upload(
|
|
41
|
+
this.options,
|
|
42
|
+
(progress) => {
|
|
43
|
+
this.update({ progress });
|
|
44
|
+
},
|
|
45
|
+
this.abortController.signal
|
|
46
|
+
);
|
|
47
|
+
this.update({
|
|
48
|
+
status: "success",
|
|
49
|
+
progress: 100,
|
|
50
|
+
response
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (this.abortController.signal.aborted) {
|
|
55
|
+
this.update({ status: "cancelled" });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const message = error instanceof Error ? error.message : "Unknown upload error";
|
|
59
|
+
if (this.retries >= this.maxRetries) {
|
|
60
|
+
this.update({
|
|
61
|
+
status: "error",
|
|
62
|
+
error: message
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.retries++;
|
|
67
|
+
const delay = this.retryDelay * Math.pow(2, this.retries - 1);
|
|
68
|
+
await this.sleep(delay);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Cancels the upload
|
|
74
|
+
*/
|
|
75
|
+
cancel() {
|
|
76
|
+
this.abortController.abort();
|
|
77
|
+
this.update({ status: "cancelled" });
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Internal state updater
|
|
81
|
+
*/
|
|
82
|
+
update(update) {
|
|
83
|
+
this.state = { ...this.state, ...update };
|
|
84
|
+
this.events.emit(this.state);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Utility sleep helper
|
|
88
|
+
*/
|
|
89
|
+
sleep(ms) {
|
|
90
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// src/core/UploadManager.ts
|
|
95
|
+
var UploadManager = class {
|
|
96
|
+
constructor(uploader, concurrency = 3) {
|
|
97
|
+
this.uploader = uploader;
|
|
98
|
+
this.concurrency = concurrency;
|
|
99
|
+
this.queue = [];
|
|
100
|
+
this.active = 0;
|
|
101
|
+
}
|
|
102
|
+
add(options) {
|
|
103
|
+
const task = new UploadTask(this.uploader, options);
|
|
104
|
+
this.queue.push(task);
|
|
105
|
+
this.process();
|
|
106
|
+
return task;
|
|
107
|
+
}
|
|
108
|
+
async process() {
|
|
109
|
+
if (this.active >= this.concurrency) return;
|
|
110
|
+
const next = this.queue.find((t) => t.state.status === "queued");
|
|
111
|
+
if (!next) return;
|
|
112
|
+
this.active++;
|
|
113
|
+
await next.start();
|
|
114
|
+
this.active--;
|
|
115
|
+
this.process();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
// src/core/BaseUploader.ts
|
|
120
|
+
var BaseUploader = class {
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// src/providers/S3Uploader.ts
|
|
124
|
+
var S3Uploader = class extends BaseUploader {
|
|
125
|
+
constructor(config) {
|
|
126
|
+
super();
|
|
127
|
+
this.config = config;
|
|
128
|
+
}
|
|
129
|
+
async upload(options, onProgress, signal) {
|
|
130
|
+
const presignRes = await fetch(
|
|
131
|
+
`${this.config.apiBaseUrl}/s3/presign-upload`,
|
|
132
|
+
{
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: { "Content-Type": "application/json" },
|
|
135
|
+
body: JSON.stringify({
|
|
136
|
+
fileName: options.fileName || options.file.name,
|
|
137
|
+
folder: options.folder,
|
|
138
|
+
contentType: options.file.type
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
const { uploadUrl, key } = await presignRes.json();
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const xhr = new XMLHttpRequest();
|
|
145
|
+
xhr.upload.onprogress = (event) => {
|
|
146
|
+
if (event.lengthComputable) {
|
|
147
|
+
const percent = event.loaded / event.total * 100;
|
|
148
|
+
onProgress(Math.round(percent));
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
xhr.onload = () => {
|
|
152
|
+
if (xhr.status === 200) {
|
|
153
|
+
resolve({
|
|
154
|
+
url: `${this.config.publicUrl}/${key}`,
|
|
155
|
+
provider: "s3"
|
|
156
|
+
});
|
|
157
|
+
} else {
|
|
158
|
+
reject(new Error("S3 upload failed"));
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
xhr.onerror = () => reject(new Error("S3 upload failed"));
|
|
162
|
+
signal?.addEventListener("abort", () => {
|
|
163
|
+
xhr.abort();
|
|
164
|
+
reject(new Error("Upload cancelled"));
|
|
165
|
+
});
|
|
166
|
+
xhr.open("PUT", uploadUrl);
|
|
167
|
+
xhr.setRequestHeader("Content-Type", options.file.type);
|
|
168
|
+
xhr.send(options.file);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// src/providers/CloudinaryUploader.ts
|
|
174
|
+
var CloudinaryUploader = class extends BaseUploader {
|
|
175
|
+
constructor(config) {
|
|
176
|
+
super();
|
|
177
|
+
this.config = config;
|
|
178
|
+
}
|
|
179
|
+
async upload(options, onProgress) {
|
|
180
|
+
const formData = new FormData();
|
|
181
|
+
formData.append("file", options.file);
|
|
182
|
+
formData.append("upload_preset", this.config.uploadPreset);
|
|
183
|
+
formData.append("folder", options.folder);
|
|
184
|
+
return new Promise((resolve, reject) => {
|
|
185
|
+
const xhr = new XMLHttpRequest();
|
|
186
|
+
xhr.upload.onprogress = (event) => {
|
|
187
|
+
if (event.lengthComputable) {
|
|
188
|
+
const percent = event.loaded / event.total * 100;
|
|
189
|
+
onProgress(Math.round(percent));
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
xhr.onload = () => {
|
|
193
|
+
const data = JSON.parse(xhr.responseText);
|
|
194
|
+
if (xhr.status === 200 && data.secure_url) {
|
|
195
|
+
resolve({
|
|
196
|
+
url: data.secure_url,
|
|
197
|
+
provider: "cloudinary"
|
|
198
|
+
});
|
|
199
|
+
} else {
|
|
200
|
+
reject(new Error("Cloudinary upload failed"));
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
xhr.onerror = () => reject(new Error("Cloudinary upload failed"));
|
|
204
|
+
xhr.open(
|
|
205
|
+
"POST",
|
|
206
|
+
`https://api.cloudinary.com/v1_1/${this.config.cloudName}/upload`
|
|
207
|
+
);
|
|
208
|
+
xhr.send(formData);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
export {
|
|
213
|
+
CloudinaryUploader,
|
|
214
|
+
S3Uploader,
|
|
215
|
+
UploadManager,
|
|
216
|
+
UploadTask
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/EventEmitter.ts","../src/core/UploadTask.ts","../src/core/UploadManager.ts","../src/core/BaseUploader.ts","../src/providers/S3Uploader.ts","../src/providers/CloudinaryUploader.ts"],"sourcesContent":["type Listener<T> = (payload: T) => void;\n\nexport class EventEmitter<T> {\n private listeners: Listener<T>[] = [];\n\n subscribe(listener: Listener<T>) {\n this.listeners.push(listener);\n return () => {\n this.listeners = this.listeners.filter(l => l !== listener);\n };\n }\n\n emit(payload: T) {\n this.listeners.forEach(listener => listener(payload));\n }\n}","import { BaseUploader } from './BaseUploader';\nimport { UploadOptions, UploadTaskState } from './types';\nimport { EventEmitter } from './EventEmitter';\n\nexport class UploadTask {\n public state: UploadTaskState;\n public events = new EventEmitter<UploadTaskState>();\n\n private abortController = new AbortController();\n private retries = 0;\n\n constructor(\n private uploader: BaseUploader,\n private options: UploadOptions,\n private maxRetries = 2,\n private retryDelay = 500 // base delay in ms\n ) {\n this.state = {\n id: crypto.randomUUID(),\n progress: 0,\n status: 'queued',\n };\n }\n\n /**\n * Starts the upload process\n */\n async start(): Promise<void> {\n this.update({ status: 'uploading' });\n\n while (this.retries <= this.maxRetries) {\n try {\n const response = await this.uploader.upload(\n this.options,\n (progress: number) => {\n this.update({ progress });\n },\n this.abortController.signal\n );\n\n this.update({\n status: 'success',\n progress: 100,\n response,\n });\n\n return;\n } catch (error: unknown) {\n if (this.abortController.signal.aborted) {\n this.update({ status: 'cancelled' });\n return;\n }\n\n const message =\n error instanceof Error\n ? error.message\n : 'Unknown upload error';\n\n if (this.retries >= this.maxRetries) {\n this.update({\n status: 'error',\n error: message,\n });\n return;\n }\n\n this.retries++;\n\n // Exponential backoff delay\n const delay = this.retryDelay * Math.pow(2, this.retries - 1);\n await this.sleep(delay);\n }\n }\n }\n\n /**\n * Cancels the upload\n */\n cancel(): void {\n this.abortController.abort();\n this.update({ status: 'cancelled' });\n }\n\n /**\n * Internal state updater\n */\n private update(update: Partial<UploadTaskState>): void {\n this.state = { ...this.state, ...update };\n this.events.emit(this.state);\n }\n\n /**\n * Utility sleep helper\n */\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}","import { BaseUploader } from './BaseUploader';\nimport { UploadOptions } from './types';\nimport { UploadTask } from './UploadTask';\n\nexport class UploadManager {\n private queue: UploadTask[] = [];\n private active = 0;\n\n constructor(\n private uploader: BaseUploader,\n private concurrency = 3\n ) { }\n\n add(options: UploadOptions) {\n const task = new UploadTask(this.uploader, options);\n this.queue.push(task);\n this.process();\n return task;\n }\n\n private async process() {\n if (this.active >= this.concurrency) return;\n\n const next = this.queue.find(t => t.state.status === 'queued');\n if (!next) return;\n\n this.active++;\n await next.start();\n this.active--;\n\n this.process();\n }\n}","import { UploadOptions, UploadResponse } from './types';\n\nexport abstract class BaseUploader {\n abstract upload(options: UploadOptions, onProgress: (percent: number) => void, signal?: AbortSignal): Promise<UploadResponse>;\n}","import { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\ninterface S3Config {\n apiBaseUrl: string;\n publicUrl: string;\n}\n\nexport class S3Uploader extends BaseUploader {\n constructor(private config: S3Config) {\n super();\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void,\n signal?: AbortSignal\n ): Promise<UploadResponse> {\n const presignRes = await fetch(\n `${this.config.apiBaseUrl}/s3/presign-upload`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n fileName: options.fileName || options.file.name,\n folder: options.folder,\n contentType: options.file.type,\n }),\n }\n );\n\n const { uploadUrl, key } = await presignRes.json();\n\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n const percent = (event.loaded / event.total) * 100;\n onProgress(Math.round(percent));\n }\n };\n\n xhr.onload = () => {\n if (xhr.status === 200) {\n resolve({\n url: `${this.config.publicUrl}/${key}`,\n provider: 's3',\n });\n } else {\n reject(new Error('S3 upload failed'));\n }\n };\n\n xhr.onerror = () => reject(new Error('S3 upload failed'));\n\n signal?.addEventListener('abort', () => {\n xhr.abort();\n reject(new Error('Upload cancelled'));\n });\n\n xhr.open('PUT', uploadUrl);\n xhr.setRequestHeader('Content-Type', options.file.type);\n xhr.send(options.file);\n });\n }\n}","import { BaseUploader } from '../core/BaseUploader';\nimport { UploadOptions, UploadResponse } from '../core/types';\n\ninterface CloudinaryConfig {\n cloudName: string;\n uploadPreset: string;\n}\n\nexport class CloudinaryUploader extends BaseUploader {\n constructor(private config: CloudinaryConfig) {\n super();\n }\n\n async upload(\n options: UploadOptions,\n onProgress: (percent: number) => void\n ): Promise<UploadResponse> {\n const formData = new FormData();\n formData.append('file', options.file);\n formData.append('upload_preset', this.config.uploadPreset);\n formData.append('folder', options.folder);\n\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n\n xhr.upload.onprogress = (event) => {\n if (event.lengthComputable) {\n const percent = (event.loaded / event.total) * 100;\n onProgress(Math.round(percent));\n }\n };\n\n xhr.onload = () => {\n const data = JSON.parse(xhr.responseText);\n if (xhr.status === 200 && data.secure_url) {\n resolve({\n url: data.secure_url,\n provider: 'cloudinary',\n });\n } else {\n reject(new Error('Cloudinary upload failed'));\n }\n };\n\n xhr.onerror = () => reject(new Error('Cloudinary upload failed'));\n\n xhr.open(\n 'POST',\n `https://api.cloudinary.com/v1_1/${this.config.cloudName}/upload`\n );\n\n xhr.send(formData);\n });\n }\n}"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@signskart/uploader",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-grade upload manager SDK with queue, progress tracking, retry logic, and multi-provider support (S3, Cloudinary).",
|
|
5
|
+
"author": "Signskart",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.cjs",
|
|
8
|
+
"module": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"require": "./dist/index.cjs",
|
|
17
|
+
"types": "./dist/index.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"prepublishOnly": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"signskart",
|
|
26
|
+
"upload",
|
|
27
|
+
"s3",
|
|
28
|
+
"cloudinary",
|
|
29
|
+
"upload-manager",
|
|
30
|
+
"typescript",
|
|
31
|
+
"file-upload",
|
|
32
|
+
"sdk"
|
|
33
|
+
],
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"tsup": "^8.0.0",
|
|
36
|
+
"typescript": "^5.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|