@dofe/file-sdk-web 0.1.1

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 ADDED
@@ -0,0 +1,68 @@
1
+ # @dofe/file-sdk-web
2
+
3
+ DofeAI unified file uploader SDK for **browsers** — direct upload, multipart upload with progress, via `sso.dofe.ai`.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @dofe/file-sdk-web
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { FileUploader } from '@dofe/file-sdk-web';
15
+
16
+ const uploader = new FileUploader({
17
+ apiBase: '/api/proxy/sso', // via Next.js proxy
18
+ });
19
+
20
+ // Small files: direct upload
21
+ const { fileId, cdnUrl } = await uploader.upload(file, {
22
+ scope: 'avatar',
23
+ onProgress: ({ percent }) => console.log(`${percent}%`),
24
+ });
25
+
26
+ // Large files: multipart upload with concurrency
27
+ const { fileId, cdnUrl } = await uploader.uploadMultipart(bigFile, {
28
+ scope: 'media-asset',
29
+ partSize: 5 * 1024 * 1024, // 5MB parts
30
+ concurrency: 3, // 3 parallel parts
31
+ onProgress: ({ percent, loaded, total }) => {
32
+ console.log(`${percent}% (${loaded}/${total})`);
33
+ },
34
+ });
35
+ ```
36
+
37
+ ## API Reference
38
+
39
+ ### `new FileUploader(config)`
40
+
41
+ | Option | Type | Required | Default | Description |
42
+ |--------|------|:---:|---------|-------------|
43
+ | `apiBase` | `string` | ✅ | — | API base URL (e.g., `/api/proxy/sso` or `https://sso.dofe.ai`) |
44
+ | `timeout` | `number` | ❌ | `30000` | Request timeout in ms |
45
+
46
+ ### Methods
47
+
48
+ | Method | Returns | Description |
49
+ |--------|---------|-------------|
50
+ | `upload(file, options)` | `Promise<UploadResult>` | Auto-chooses direct or multipart based on file size (>5MB → multipart) |
51
+ | `uploadDirect(file, options)` | `Promise<UploadResult>` | Direct upload for small files |
52
+ | `uploadMultipart(file, options)` | `Promise<UploadResult>` | Multipart upload with configurable part size and concurrency |
53
+
54
+ ### UploadResult
55
+
56
+ ```typescript
57
+ interface UploadResult {
58
+ fileId: string; // SSO FileSource UUID
59
+ key: string; // Object storage key
60
+ url: string; // Upload URL / token
61
+ cdnUrl: string | null; // CDN URL (null for private files)
62
+ bucket: string; // Storage bucket name
63
+ }
64
+ ```
65
+
66
+ ## License
67
+
68
+ MIT
@@ -0,0 +1,2 @@
1
+ export { FileUploader } from './uploader';
2
+ export type { FileUploaderConfig, FileScope, UploadOptions, MultipartUploadOptions, UploadProgress, UploadResult, } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FileUploader = void 0;
4
+ var uploader_1 = require("./uploader");
5
+ Object.defineProperty(exports, "FileUploader", { enumerable: true, get: function () { return uploader_1.FileUploader; } });
@@ -0,0 +1,42 @@
1
+ /** File scope types */
2
+ export type FileScope = 'avatar' | 'media_asset' | 'knowledge' | 'general' | 'system';
3
+ /** Upload progress callback */
4
+ export interface UploadProgress {
5
+ /** Percentage complete (0-100) */
6
+ percent: number;
7
+ /** Bytes uploaded so far */
8
+ loaded: number;
9
+ /** Total bytes to upload */
10
+ total: number;
11
+ }
12
+ /** Upload options */
13
+ export interface UploadOptions {
14
+ /** File scope / category */
15
+ scope: FileScope;
16
+ /** Progress callback */
17
+ onProgress?: (progress: UploadProgress) => void;
18
+ /** Tenant ID (optional, derived from auth context if not provided) */
19
+ tenantId?: string;
20
+ }
21
+ /** Multipart upload options */
22
+ export interface MultipartUploadOptions extends UploadOptions {
23
+ /** Part size in bytes (default: 5MB) */
24
+ partSize?: number;
25
+ /** Max concurrent parts (default: 3) */
26
+ concurrency?: number;
27
+ }
28
+ /** Upload result */
29
+ export interface UploadResult {
30
+ fileId: string;
31
+ key: string;
32
+ url: string;
33
+ cdnUrl: string | null;
34
+ bucket: string;
35
+ }
36
+ /** FileUploader configuration */
37
+ export interface FileUploaderConfig {
38
+ /** API base URL for upload token requests (e.g., "/api/proxy/sso" or "https://sso.dofe.ai") */
39
+ apiBase: string;
40
+ /** Timeout per request in ms (default: 30000) */
41
+ timeout?: number;
42
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,37 @@
1
+ import { FileUploaderConfig, UploadOptions, MultipartUploadOptions, UploadResult } from './types';
2
+ export declare class FileUploader {
3
+ private readonly apiBase;
4
+ private readonly timeout;
5
+ constructor(config: FileUploaderConfig);
6
+ /**
7
+ * Upload a file. Automatically chooses between direct upload (small files)
8
+ * and multipart upload (large files > 5MB).
9
+ */
10
+ upload(file: File, options: UploadOptions): Promise<UploadResult>;
11
+ /**
12
+ * Direct upload for small files.
13
+ * 1. Get upload token from SSO API
14
+ * 2. PUT file directly to presigned URL
15
+ */
16
+ uploadDirect(file: File, options: UploadOptions): Promise<UploadResult>;
17
+ /**
18
+ * Multipart upload for large files.
19
+ * 1. Init multipart upload → get uploadId + first part token
20
+ * 2. Upload first part
21
+ * 3. Get tokens for remaining parts and upload in parallel
22
+ * 4. Complete multipart upload
23
+ */
24
+ uploadMultipart(file: File, options: MultipartUploadOptions): Promise<UploadResult>;
25
+ /**
26
+ * Upload a single part in a multipart upload.
27
+ */
28
+ private uploadPart;
29
+ /**
30
+ * PUT file/blob directly to a presigned URL.
31
+ */
32
+ private uploadToPresignedUrl;
33
+ /**
34
+ * Fetch JSON from API, extracting the data field from the standard response wrapper.
35
+ */
36
+ private fetchJson;
37
+ }
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FileUploader = void 0;
4
+ /** Default part size for multipart uploads: 5MB */
5
+ const DEFAULT_PART_SIZE = 5 * 1024 * 1024;
6
+ class FileUploader {
7
+ constructor(config) {
8
+ var _a;
9
+ this.apiBase = config.apiBase.replace(/\/$/, '');
10
+ this.timeout = (_a = config.timeout) !== null && _a !== void 0 ? _a : 30000;
11
+ }
12
+ /**
13
+ * Upload a file. Automatically chooses between direct upload (small files)
14
+ * and multipart upload (large files > 5MB).
15
+ */
16
+ async upload(file, options) {
17
+ if (file.size > DEFAULT_PART_SIZE) {
18
+ return this.uploadMultipart(file, options);
19
+ }
20
+ return this.uploadDirect(file, options);
21
+ }
22
+ /**
23
+ * Direct upload for small files.
24
+ * 1. Get upload token from SSO API
25
+ * 2. PUT file directly to presigned URL
26
+ */
27
+ async uploadDirect(file, options) {
28
+ var _a, _b, _c, _d;
29
+ // Step 1: Get upload token
30
+ const tokenRes = await this.fetchJson(`${this.apiBase}/api/uploader/token/private`, {
31
+ method: 'POST',
32
+ body: JSON.stringify({
33
+ filename: file.name,
34
+ fsize: file.size,
35
+ mimeType: file.type || 'application/octet-stream',
36
+ scope: options.scope,
37
+ signature: 'browser-upload', // Will be validated differently for browser uploads
38
+ tenantId: options.tenantId,
39
+ }),
40
+ });
41
+ const { token, fileId, key, cdnUrl, bucket } = (_a = tokenRes.data) !== null && _a !== void 0 ? _a : tokenRes;
42
+ // Step 2: PUT file to presigned URL
43
+ await this.uploadToPresignedUrl(token, file);
44
+ (_b = options.onProgress) === null || _b === void 0 ? void 0 : _b.call(options, { percent: 100, loaded: file.size, total: file.size });
45
+ return { fileId, key, url: (_d = (_c = tokenRes.data) === null || _c === void 0 ? void 0 : _c.url) !== null && _d !== void 0 ? _d : token, cdnUrl, bucket };
46
+ }
47
+ /**
48
+ * Multipart upload for large files.
49
+ * 1. Init multipart upload → get uploadId + first part token
50
+ * 2. Upload first part
51
+ * 3. Get tokens for remaining parts and upload in parallel
52
+ * 4. Complete multipart upload
53
+ */
54
+ async uploadMultipart(file, options) {
55
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
56
+ const partSize = (_a = options.partSize) !== null && _a !== void 0 ? _a : DEFAULT_PART_SIZE;
57
+ const concurrency = (_b = options.concurrency) !== null && _b !== void 0 ? _b : 3;
58
+ const totalParts = Math.ceil(file.size / partSize);
59
+ // Step 1: Init multipart upload
60
+ const initRes = await this.fetchJson(`${this.apiBase}/api/uploader/init/multipart`, {
61
+ method: 'POST',
62
+ body: JSON.stringify({
63
+ filename: file.name,
64
+ fsize: file.size,
65
+ mimeType: file.type || 'application/octet-stream',
66
+ scope: options.scope,
67
+ signature: 'browser-upload',
68
+ tenantId: options.tenantId,
69
+ }),
70
+ });
71
+ const { token, fileId, key, bucket, url: uploadId } = (_c = initRes.data) !== null && _c !== void 0 ? _c : initRes;
72
+ // Step 2: Upload first part
73
+ const part1 = file.slice(0, partSize);
74
+ await this.uploadToPresignedUrl(token, part1);
75
+ const parts = [{ partNumber: 1, etag: 'uploaded' }];
76
+ let loaded = partSize;
77
+ (_d = options.onProgress) === null || _d === void 0 ? void 0 : _d.call(options, { percent: Math.round((loaded / file.size) * 100), loaded, total: file.size });
78
+ // Step 3: Upload remaining parts in parallel batches
79
+ for (let i = 2; i <= totalParts; i += concurrency) {
80
+ const batch = [];
81
+ const batchEnd = Math.min(i + concurrency - 1, totalParts);
82
+ for (let partNumber = i; partNumber <= batchEnd; partNumber++) {
83
+ batch.push(this.uploadPart(partNumber, file, partSize, uploadId, fileId, options));
84
+ }
85
+ const results = await Promise.all(batch);
86
+ parts.push(...results.filter(Boolean));
87
+ loaded += (batchEnd - i + 1) * partSize;
88
+ const actualLoaded = Math.min(loaded, file.size);
89
+ (_e = options.onProgress) === null || _e === void 0 ? void 0 : _e.call(options, { percent: Math.round((actualLoaded / file.size) * 100), loaded: actualLoaded, total: file.size });
90
+ }
91
+ // Step 4: Complete multipart upload
92
+ const completeRes = await this.fetchJson(`${this.apiBase}/api/uploader/complete`, {
93
+ method: 'POST',
94
+ body: JSON.stringify({
95
+ fileId,
96
+ signature: 'browser-upload',
97
+ parts,
98
+ uploadId,
99
+ }),
100
+ });
101
+ return {
102
+ fileId,
103
+ key,
104
+ url: (_g = (_f = completeRes.data) === null || _f === void 0 ? void 0 : _f.url) !== null && _g !== void 0 ? _g : '',
105
+ cdnUrl: (_j = (_h = completeRes.data) === null || _h === void 0 ? void 0 : _h.cdnUrl) !== null && _j !== void 0 ? _j : null,
106
+ bucket,
107
+ };
108
+ }
109
+ /**
110
+ * Upload a single part in a multipart upload.
111
+ */
112
+ async uploadPart(partNumber, file, partSize, uploadId, fileId, options) {
113
+ var _a, _b;
114
+ try {
115
+ const tokenRes = await this.fetchJson(`${this.apiBase}/api/uploader/token/multipart`, {
116
+ method: 'POST',
117
+ body: JSON.stringify({
118
+ fileId,
119
+ uploadId,
120
+ partNumber,
121
+ signature: 'browser-upload',
122
+ }),
123
+ });
124
+ const start = (partNumber - 1) * partSize;
125
+ const end = Math.min(start + partSize, file.size);
126
+ const chunk = file.slice(start, end);
127
+ await this.uploadToPresignedUrl((_b = (_a = tokenRes.data) === null || _a === void 0 ? void 0 : _a.token) !== null && _b !== void 0 ? _b : tokenRes.token, chunk);
128
+ return { partNumber, etag: `etag-${partNumber}` };
129
+ }
130
+ catch (err) {
131
+ // Part upload failed; caller can retry
132
+ throw err;
133
+ }
134
+ }
135
+ /**
136
+ * PUT file/blob directly to a presigned URL.
137
+ */
138
+ async uploadToPresignedUrl(presignedUrl, data) {
139
+ const controller = new AbortController();
140
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
141
+ try {
142
+ const res = await fetch(presignedUrl, {
143
+ method: 'PUT',
144
+ body: data,
145
+ signal: controller.signal,
146
+ headers: {
147
+ 'Content-Type': data.type || 'application/octet-stream',
148
+ },
149
+ });
150
+ if (!res.ok) {
151
+ throw new Error(`Upload to storage failed: HTTP ${res.status}`);
152
+ }
153
+ }
154
+ finally {
155
+ clearTimeout(timeoutId);
156
+ }
157
+ }
158
+ /**
159
+ * Fetch JSON from API, extracting the data field from the standard response wrapper.
160
+ */
161
+ async fetchJson(url, options) {
162
+ var _a;
163
+ const controller = new AbortController();
164
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
165
+ try {
166
+ const res = await fetch(url, Object.assign(Object.assign({}, options), { signal: controller.signal, headers: Object.assign({ 'Content-Type': 'application/json' }, ((_a = options === null || options === void 0 ? void 0 : options.headers) !== null && _a !== void 0 ? _a : {})), credentials: 'include' }));
167
+ if (!res.ok) {
168
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
169
+ }
170
+ const json = await res.json();
171
+ // Unwrap standard { code, msg, data } envelope
172
+ if (json && typeof json === 'object' && 'data' in json) {
173
+ return json;
174
+ }
175
+ return json;
176
+ }
177
+ finally {
178
+ clearTimeout(timeoutId);
179
+ }
180
+ }
181
+ }
182
+ exports.FileUploader = FileUploader;
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@dofe/file-sdk-web",
3
+ "version": "0.1.1",
4
+ "description": "DofeAI unified file uploader SDK for browsers — direct upload, multipart upload with progress, via sso.dofe.ai",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "scripts": {
14
+ "build": "tsc --project tsconfig.build.json",
15
+ "clean": "rm -rf dist",
16
+ "typecheck": "tsc --project tsconfig.build.json --noEmit",
17
+ "prepublishOnly": "pnpm run clean && pnpm run build"
18
+ },
19
+ "devDependencies": {
20
+ "typescript": "^6.0.3"
21
+ },
22
+ "type": "commonjs",
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md"
29
+ ],
30
+ "keywords": [
31
+ "dofe",
32
+ "file",
33
+ "upload",
34
+ "browser",
35
+ "multipart",
36
+ "sso"
37
+ ],
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/dofe-ai/sso.dofe.ai.git"
42
+ },
43
+ "author": "Techwu <wumin.itea@gmail.com>"
44
+ }