@alphayard/unibox-sdk 0.1.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 ADDED
@@ -0,0 +1,73 @@
1
+ # `@alphayard/unibox-sdk`
2
+
3
+ UniBox client SDK for file storage in Boundary and other UniApps clients.
4
+
5
+ ## What it does
6
+
7
+ - Upload files
8
+ - List files
9
+ - List folders
10
+ - Create folders
11
+ - Load circle folders
12
+ - Read folder breadcrumb paths
13
+ - Fetch file details
14
+ - Update file metadata
15
+ - Delete files
16
+ - Search files
17
+ - Read quota and analytics
18
+ - Build public or proxied file URLs
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install @alphayard/unibox-sdk
24
+ ```
25
+
26
+ For local development in this monorepo, `boundary-app` uses:
27
+
28
+ ```json
29
+ "@alphayard/unibox-sdk": "file:../packages/unibox-sdk"
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ```ts
35
+ import { createUniBoxClient } from '@alphayard/unibox-sdk';
36
+
37
+ const unibox = createUniBoxClient({
38
+ baseURL: 'http://localhost:4000/api/v1',
39
+ appId: 'your-app-id',
40
+ getAccessToken: async () => 'jwt-token',
41
+ });
42
+
43
+ const result = await unibox.uploadFile({
44
+ file,
45
+ description: 'Profile image',
46
+ isPublic: true,
47
+ });
48
+
49
+ const folders = await unibox.getCircleFolders('circle-id');
50
+
51
+ const createdFolder = await unibox.createCircleFolder('circle-id', {
52
+ name: 'Invoices',
53
+ parentId: 'optional-parent-folder-id',
54
+ });
55
+ ```
56
+
57
+ ## Publish
58
+
59
+ For local Boundary app development, Metro resolves the package from `src/index.ts`.
60
+
61
+ Before publishing to npm, build the package so `dist/` is up to date:
62
+
63
+ ```bash
64
+ cd packages/unibox-sdk
65
+ npm run build
66
+ ```
67
+
68
+ Publishing still requires an authenticated npm session:
69
+
70
+ ```bash
71
+ cd packages/unibox-sdk
72
+ npm publish
73
+ ```
@@ -0,0 +1,106 @@
1
+ export type UniBoxFileType = 'image' | 'video' | 'audio' | 'document' | 'other';
2
+ export interface UniBoxFile {
3
+ id: string;
4
+ userId: string;
5
+ applicationId?: string;
6
+ circleId: string;
7
+ fileName: string;
8
+ originalName: string;
9
+ fileSize: number;
10
+ mimeType: string;
11
+ fileType: UniBoxFileType;
12
+ filePath: string;
13
+ url: string;
14
+ thumbnailPath?: string;
15
+ metadata?: Record<string, unknown>;
16
+ isPublic: boolean;
17
+ tags: string[];
18
+ description?: string;
19
+ createdAt: string;
20
+ updatedAt: string;
21
+ }
22
+ export interface UniBoxFolder {
23
+ id: string;
24
+ name: string;
25
+ description?: string;
26
+ parentId?: string;
27
+ ownerId: string;
28
+ circleId?: string;
29
+ color?: string;
30
+ icon?: string;
31
+ isFavorite: boolean;
32
+ isPinned: boolean;
33
+ sortOrder: number;
34
+ itemCount: number;
35
+ totalSize: number;
36
+ createdAt: string;
37
+ updatedAt: string;
38
+ }
39
+ export interface UniBoxUploadRequest {
40
+ file: unknown;
41
+ description?: string;
42
+ folderId?: string;
43
+ isPublic?: boolean;
44
+ tags?: string[];
45
+ }
46
+ export interface UniBoxClientOptions {
47
+ baseURL: string;
48
+ appId?: string;
49
+ appSlug?: string;
50
+ getAccessToken?: () => Promise<string | null> | string | null;
51
+ }
52
+ export interface UniBoxListResult {
53
+ success: boolean;
54
+ files: UniBoxFile[];
55
+ total: number;
56
+ pagination?: unknown;
57
+ }
58
+ export interface UniBoxFolderListResult {
59
+ success: boolean;
60
+ folders: UniBoxFolder[];
61
+ total: number;
62
+ pagination?: unknown;
63
+ }
64
+ export interface UniBoxClient {
65
+ uploadFile(data: UniBoxUploadRequest): Promise<{
66
+ success: boolean;
67
+ file: UniBoxFile;
68
+ }>;
69
+ getFiles(params?: Record<string, string | number | boolean>): Promise<UniBoxListResult>;
70
+ getCircleFiles(circleId: string, params?: Record<string, string | number | boolean>): Promise<UniBoxListResult>;
71
+ getFile(fileId: string): Promise<{
72
+ success: boolean;
73
+ file: UniBoxFile;
74
+ }>;
75
+ updateFile(fileId: string, data: Record<string, unknown>): Promise<{
76
+ success: boolean;
77
+ file: UniBoxFile;
78
+ }>;
79
+ deleteFile(fileId: string): Promise<{
80
+ success: boolean;
81
+ message: string;
82
+ }>;
83
+ getFolders(params?: Record<string, string | number | boolean>): Promise<UniBoxFolderListResult>;
84
+ getCircleFolders(circleId: string, params?: Record<string, string | number | boolean>): Promise<UniBoxFolderListResult>;
85
+ createFolder(data: Record<string, unknown>): Promise<{
86
+ success: boolean;
87
+ folder: UniBoxFolder;
88
+ }>;
89
+ createCircleFolder(circleId: string, data: Record<string, unknown>): Promise<{
90
+ success: boolean;
91
+ folder: UniBoxFolder;
92
+ }>;
93
+ getFolderPath(folderId: string): Promise<{
94
+ success: boolean;
95
+ path: UniBoxFolder[];
96
+ }>;
97
+ getQuota(): Promise<unknown>;
98
+ getCircleQuota(circleId: string): Promise<unknown>;
99
+ getAnalytics(): Promise<unknown>;
100
+ searchFiles(query: string, params?: Record<string, string | number | boolean>): Promise<UniBoxListResult>;
101
+ getFileUrl(path: string): string;
102
+ }
103
+ export declare function normalizeFileType(fileType?: string): UniBoxFileType;
104
+ export declare function mapUniBoxFile(baseURL: string, file: any): UniBoxFile;
105
+ export declare function mapUniBoxFolder(folder: any): UniBoxFolder;
106
+ export declare function createUniBoxClient(options: UniBoxClientOptions): UniBoxClient;
package/dist/index.js ADDED
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeFileType = normalizeFileType;
4
+ exports.mapUniBoxFile = mapUniBoxFile;
5
+ exports.mapUniBoxFolder = mapUniBoxFolder;
6
+ exports.createUniBoxClient = createUniBoxClient;
7
+ function normalizeBaseURL(baseURL) {
8
+ const trimmed = String(baseURL || '').replace(/\/$/, '');
9
+ return trimmed.endsWith('/api/v1') ? trimmed : `${trimmed}/api/v1`;
10
+ }
11
+ function normalizeFileType(fileType) {
12
+ if (fileType === 'image' || fileType === 'video' || fileType === 'audio') {
13
+ return fileType;
14
+ }
15
+ if (fileType === 'pdf' || fileType === 'document') {
16
+ return 'document';
17
+ }
18
+ return 'other';
19
+ }
20
+ function normalizeFileURL(baseURL, path) {
21
+ if (!path) {
22
+ return '';
23
+ }
24
+ if (/^https?:\/\//i.test(path) || path.startsWith('data:')) {
25
+ return path;
26
+ }
27
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(path)) {
28
+ return `${baseURL.replace(/\/api\/v1$/, '')}/api/v1/storage/proxy/${path}`;
29
+ }
30
+ const origin = baseURL.replace(/\/api\/v1$/, '');
31
+ const normalizedPath = path.startsWith('/') ? path : `/api/uploads/${path}`;
32
+ return `${origin}${normalizedPath}`;
33
+ }
34
+ function mapUniBoxFile(baseURL, file) {
35
+ var _a, _b, _c;
36
+ return {
37
+ id: (file === null || file === void 0 ? void 0 : file.id) || '',
38
+ userId: (file === null || file === void 0 ? void 0 : file.uploadedBy) || (file === null || file === void 0 ? void 0 : file.userId) || '',
39
+ applicationId: file === null || file === void 0 ? void 0 : file.applicationId,
40
+ circleId: (file === null || file === void 0 ? void 0 : file.circleId) || '',
41
+ fileName: (file === null || file === void 0 ? void 0 : file.fileName) || (file === null || file === void 0 ? void 0 : file.filename) || (file === null || file === void 0 ? void 0 : file.originalName) || '',
42
+ originalName: (file === null || file === void 0 ? void 0 : file.originalName) || (file === null || file === void 0 ? void 0 : file.filename) || (file === null || file === void 0 ? void 0 : file.fileName) || '',
43
+ fileSize: Number((_b = (_a = file === null || file === void 0 ? void 0 : file.size) !== null && _a !== void 0 ? _a : file === null || file === void 0 ? void 0 : file.fileSize) !== null && _b !== void 0 ? _b : 0),
44
+ mimeType: (file === null || file === void 0 ? void 0 : file.mimeType) || 'application/octet-stream',
45
+ fileType: normalizeFileType(file === null || file === void 0 ? void 0 : file.fileType),
46
+ filePath: (file === null || file === void 0 ? void 0 : file.filePath) || (file === null || file === void 0 ? void 0 : file.storagePath) || '',
47
+ url: normalizeFileURL(baseURL, (file === null || file === void 0 ? void 0 : file.url) || (file === null || file === void 0 ? void 0 : file.filePath) || (file === null || file === void 0 ? void 0 : file.storagePath) || (file === null || file === void 0 ? void 0 : file.id) || ''),
48
+ thumbnailPath: (file === null || file === void 0 ? void 0 : file.thumbnailUrl) || (file === null || file === void 0 ? void 0 : file.thumbnailPath),
49
+ metadata: file === null || file === void 0 ? void 0 : file.metadata,
50
+ isPublic: (_c = file === null || file === void 0 ? void 0 : file.isPublic) !== null && _c !== void 0 ? _c : true,
51
+ tags: Array.isArray(file === null || file === void 0 ? void 0 : file.tags)
52
+ ? file.tags.map((tag) => (typeof tag === 'string' ? tag : tag === null || tag === void 0 ? void 0 : tag.name)).filter(Boolean)
53
+ : [],
54
+ description: file === null || file === void 0 ? void 0 : file.description,
55
+ createdAt: (file === null || file === void 0 ? void 0 : file.createdAt) || new Date().toISOString(),
56
+ updatedAt: (file === null || file === void 0 ? void 0 : file.updatedAt) || (file === null || file === void 0 ? void 0 : file.createdAt) || new Date().toISOString(),
57
+ };
58
+ }
59
+ function mapUniBoxFolder(folder) {
60
+ return {
61
+ id: (folder === null || folder === void 0 ? void 0 : folder.id) || '',
62
+ name: (folder === null || folder === void 0 ? void 0 : folder.name) || '',
63
+ description: folder === null || folder === void 0 ? void 0 : folder.description,
64
+ parentId: folder === null || folder === void 0 ? void 0 : folder.parentId,
65
+ ownerId: (folder === null || folder === void 0 ? void 0 : folder.ownerId) || (folder === null || folder === void 0 ? void 0 : folder.userId) || '',
66
+ circleId: folder === null || folder === void 0 ? void 0 : folder.circleId,
67
+ color: folder === null || folder === void 0 ? void 0 : folder.color,
68
+ icon: folder === null || folder === void 0 ? void 0 : folder.icon,
69
+ isFavorite: Boolean(folder === null || folder === void 0 ? void 0 : folder.isFavorite),
70
+ isPinned: Boolean(folder === null || folder === void 0 ? void 0 : folder.isPinned),
71
+ sortOrder: Number((folder === null || folder === void 0 ? void 0 : folder.sortOrder) || 0),
72
+ itemCount: Number((folder === null || folder === void 0 ? void 0 : folder.itemCount) || 0),
73
+ totalSize: Number((folder === null || folder === void 0 ? void 0 : folder.totalSize) || 0),
74
+ createdAt: (folder === null || folder === void 0 ? void 0 : folder.createdAt) || new Date().toISOString(),
75
+ updatedAt: (folder === null || folder === void 0 ? void 0 : folder.updatedAt) || (folder === null || folder === void 0 ? void 0 : folder.createdAt) || new Date().toISOString(),
76
+ };
77
+ }
78
+ function createUniBoxClient(options) {
79
+ const baseURL = normalizeBaseURL(options.baseURL);
80
+ async function buildHeaders(extraHeaders) {
81
+ const accessToken = options.getAccessToken ? await options.getAccessToken() : null;
82
+ const headers = { ...(extraHeaders || {}) };
83
+ if (options.appId) {
84
+ headers['X-App-ID'] = options.appId;
85
+ }
86
+ else if (options.appSlug) {
87
+ headers['X-App-Slug'] = options.appSlug;
88
+ }
89
+ if (accessToken) {
90
+ headers.Authorization = `Bearer ${accessToken}`;
91
+ }
92
+ return headers;
93
+ }
94
+ async function request(path, init) {
95
+ const response = await fetch(`${baseURL}${path}`, init);
96
+ if (!response.ok) {
97
+ let message = `Request failed: HTTP ${response.status}`;
98
+ try {
99
+ const payload = await response.json();
100
+ if ((payload === null || payload === void 0 ? void 0 : payload.message) || (payload === null || payload === void 0 ? void 0 : payload.error)) {
101
+ message = payload.message || payload.error;
102
+ }
103
+ }
104
+ catch {
105
+ try {
106
+ const text = await response.text();
107
+ if (text) {
108
+ message = text;
109
+ }
110
+ }
111
+ catch {
112
+ // Ignore text parsing error.
113
+ }
114
+ }
115
+ throw new Error(message);
116
+ }
117
+ return response.json();
118
+ }
119
+ function buildQuery(params) {
120
+ const searchParams = new URLSearchParams();
121
+ Object.entries(params || {}).forEach(([key, value]) => {
122
+ if (value !== undefined && value !== null && value !== '') {
123
+ searchParams.set(key, String(value));
124
+ }
125
+ });
126
+ const query = searchParams.toString();
127
+ return query ? `?${query}` : '';
128
+ }
129
+ return {
130
+ uploadFile: async (data) => {
131
+ var _a;
132
+ const formData = new FormData();
133
+ formData.append('file', data.file);
134
+ if (data.description) {
135
+ formData.append('description', data.description);
136
+ }
137
+ if (data.folderId) {
138
+ formData.append('folderId', data.folderId);
139
+ }
140
+ if (data.isPublic !== undefined) {
141
+ formData.append('isPublic', String(data.isPublic));
142
+ }
143
+ if (Array.isArray(data.tags) && data.tags.length > 0) {
144
+ formData.append('tags', JSON.stringify(data.tags));
145
+ }
146
+ const response = await request('/files/files/upload', {
147
+ method: 'POST',
148
+ headers: await buildHeaders(),
149
+ body: formData,
150
+ });
151
+ return {
152
+ success: Boolean(response === null || response === void 0 ? void 0 : response.success),
153
+ file: mapUniBoxFile(baseURL, (response === null || response === void 0 ? void 0 : response.file) || ((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.file) || (response === null || response === void 0 ? void 0 : response.data)),
154
+ };
155
+ },
156
+ getFiles: async (params) => {
157
+ const response = await request(`/files/files${buildQuery(params)}`, {
158
+ method: 'GET',
159
+ headers: await buildHeaders(),
160
+ });
161
+ const files = Array.isArray(response === null || response === void 0 ? void 0 : response.files) ? response.files : [];
162
+ return {
163
+ success: Boolean(response === null || response === void 0 ? void 0 : response.success),
164
+ files: files.map((file) => mapUniBoxFile(baseURL, file)),
165
+ total: Number((response === null || response === void 0 ? void 0 : response.total) || files.length || 0),
166
+ pagination: response === null || response === void 0 ? void 0 : response.pagination,
167
+ };
168
+ },
169
+ getCircleFiles: async (circleId, params) => {
170
+ const response = await request(`/files/circles/${encodeURIComponent(circleId)}/files${buildQuery(params)}`, {
171
+ method: 'GET',
172
+ headers: await buildHeaders(),
173
+ });
174
+ const files = Array.isArray(response === null || response === void 0 ? void 0 : response.files) ? response.files : [];
175
+ return {
176
+ success: Boolean(response === null || response === void 0 ? void 0 : response.success),
177
+ files: files.map((file) => mapUniBoxFile(baseURL, file)),
178
+ total: Number((response === null || response === void 0 ? void 0 : response.total) || files.length || 0),
179
+ pagination: response === null || response === void 0 ? void 0 : response.pagination,
180
+ };
181
+ },
182
+ getFile: async (fileId) => {
183
+ var _a;
184
+ const response = await request(`/files/files/${encodeURIComponent(fileId)}`, {
185
+ method: 'GET',
186
+ headers: await buildHeaders(),
187
+ });
188
+ return {
189
+ success: Boolean(response === null || response === void 0 ? void 0 : response.success),
190
+ file: mapUniBoxFile(baseURL, (response === null || response === void 0 ? void 0 : response.file) || ((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.file) || (response === null || response === void 0 ? void 0 : response.data)),
191
+ };
192
+ },
193
+ updateFile: async (fileId, data) => {
194
+ var _a;
195
+ const response = await request(`/files/files/${encodeURIComponent(fileId)}`, {
196
+ method: 'PUT',
197
+ headers: await buildHeaders({ 'Content-Type': 'application/json' }),
198
+ body: JSON.stringify(data || {}),
199
+ });
200
+ return {
201
+ success: Boolean(response === null || response === void 0 ? void 0 : response.success),
202
+ file: mapUniBoxFile(baseURL, (response === null || response === void 0 ? void 0 : response.file) || ((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.file) || (response === null || response === void 0 ? void 0 : response.data)),
203
+ };
204
+ },
205
+ deleteFile: async (fileId) => {
206
+ const response = await request(`/files/files/${encodeURIComponent(fileId)}`, {
207
+ method: 'DELETE',
208
+ headers: await buildHeaders(),
209
+ });
210
+ return {
211
+ success: Boolean((response === null || response === void 0 ? void 0 : response.success) !== false),
212
+ message: (response === null || response === void 0 ? void 0 : response.message) || (response === null || response === void 0 ? void 0 : response.error) || 'File deleted',
213
+ };
214
+ },
215
+ getFolders: async (params) => {
216
+ const response = await request(`/files/folders${buildQuery(params)}`, {
217
+ method: 'GET',
218
+ headers: await buildHeaders(),
219
+ });
220
+ const folders = Array.isArray(response === null || response === void 0 ? void 0 : response.folders) ? response.folders : [];
221
+ return {
222
+ success: Boolean(response === null || response === void 0 ? void 0 : response.success),
223
+ folders: folders.map((folder) => mapUniBoxFolder(folder)),
224
+ total: Number((response === null || response === void 0 ? void 0 : response.total) || folders.length || 0),
225
+ pagination: response === null || response === void 0 ? void 0 : response.pagination,
226
+ };
227
+ },
228
+ getCircleFolders: async (circleId, params) => {
229
+ const response = await request(`/files/circles/${encodeURIComponent(circleId)}/folders${buildQuery(params)}`, {
230
+ method: 'GET',
231
+ headers: await buildHeaders(),
232
+ });
233
+ const folders = Array.isArray(response === null || response === void 0 ? void 0 : response.folders) ? response.folders : [];
234
+ return {
235
+ success: Boolean(response === null || response === void 0 ? void 0 : response.success),
236
+ folders: folders.map((folder) => mapUniBoxFolder(folder)),
237
+ total: Number((response === null || response === void 0 ? void 0 : response.total) || folders.length || 0),
238
+ pagination: response === null || response === void 0 ? void 0 : response.pagination,
239
+ };
240
+ },
241
+ createFolder: async (data) => {
242
+ var _a;
243
+ const response = await request('/files/folders', {
244
+ method: 'POST',
245
+ headers: await buildHeaders({ 'Content-Type': 'application/json' }),
246
+ body: JSON.stringify(data || {}),
247
+ });
248
+ return {
249
+ success: Boolean(response === null || response === void 0 ? void 0 : response.success),
250
+ folder: mapUniBoxFolder((response === null || response === void 0 ? void 0 : response.folder) || ((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.folder) || (response === null || response === void 0 ? void 0 : response.data)),
251
+ };
252
+ },
253
+ createCircleFolder: async (circleId, data) => {
254
+ var _a;
255
+ const response = await request(`/files/circles/${encodeURIComponent(circleId)}/folders`, {
256
+ method: 'POST',
257
+ headers: await buildHeaders({ 'Content-Type': 'application/json' }),
258
+ body: JSON.stringify(data || {}),
259
+ });
260
+ return {
261
+ success: Boolean(response === null || response === void 0 ? void 0 : response.success),
262
+ folder: mapUniBoxFolder((response === null || response === void 0 ? void 0 : response.folder) || ((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.folder) || (response === null || response === void 0 ? void 0 : response.data)),
263
+ };
264
+ },
265
+ getFolderPath: async (folderId) => {
266
+ const response = await request(`/files/folders/${encodeURIComponent(folderId)}/path`, {
267
+ method: 'GET',
268
+ headers: await buildHeaders(),
269
+ });
270
+ const path = Array.isArray(response === null || response === void 0 ? void 0 : response.path) ? response.path : [];
271
+ return {
272
+ success: Boolean(response === null || response === void 0 ? void 0 : response.success),
273
+ path: path.map((folder) => mapUniBoxFolder(folder)),
274
+ };
275
+ },
276
+ getQuota: async () => request('/files/quota', {
277
+ method: 'GET',
278
+ headers: await buildHeaders(),
279
+ }),
280
+ getCircleQuota: async (circleId) => request(`/files/circles/${encodeURIComponent(circleId)}/quota`, {
281
+ method: 'GET',
282
+ headers: await buildHeaders(),
283
+ }),
284
+ getAnalytics: async () => request('/files/analytics', {
285
+ method: 'GET',
286
+ headers: await buildHeaders(),
287
+ }),
288
+ searchFiles: async (query, params) => {
289
+ const response = await request(`/files/search${buildQuery({
290
+ query,
291
+ ...(params || {}),
292
+ })}`, {
293
+ method: 'GET',
294
+ headers: await buildHeaders(),
295
+ });
296
+ const files = Array.isArray(response === null || response === void 0 ? void 0 : response.files) ? response.files : [];
297
+ return {
298
+ success: Boolean(response === null || response === void 0 ? void 0 : response.success),
299
+ files: files.map((file) => mapUniBoxFile(baseURL, file)),
300
+ total: Number((response === null || response === void 0 ? void 0 : response.total) || files.length || 0),
301
+ pagination: response === null || response === void 0 ? void 0 : response.pagination,
302
+ };
303
+ },
304
+ getFileUrl: (path) => normalizeFileURL(baseURL, path),
305
+ };
306
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@alphayard/unibox-sdk",
3
+ "version": "0.1.0",
4
+ "description": "UniBox storage SDK for Boundary and other UniApps clients",
5
+ "main": "dist/index.js",
6
+ "types": "src/index.ts",
7
+ "react-native": "src/index.ts",
8
+ "source": "src/index.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./src/index.ts",
12
+ "react-native": "./src/index.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsc -p tsconfig.json",
18
+ "type-check": "tsc --noEmit -p tsconfig.json",
19
+ "prepublishOnly": "npm run type-check && npm run build"
20
+ },
21
+ "files": [
22
+ "src",
23
+ "dist",
24
+ "README.md"
25
+ ],
26
+ "sideEffects": false,
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "keywords": [
31
+ "unibox",
32
+ "storage",
33
+ "sdk",
34
+ "boundary",
35
+ "uniapps"
36
+ ],
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "license": "UNLICENSED"
41
+ }
package/src/index.ts ADDED
@@ -0,0 +1,447 @@
1
+ export type UniBoxFileType = 'image' | 'video' | 'audio' | 'document' | 'other';
2
+
3
+ export interface UniBoxFile {
4
+ id: string;
5
+ userId: string;
6
+ applicationId?: string;
7
+ circleId: string;
8
+ fileName: string;
9
+ originalName: string;
10
+ fileSize: number;
11
+ mimeType: string;
12
+ fileType: UniBoxFileType;
13
+ filePath: string;
14
+ url: string;
15
+ thumbnailPath?: string;
16
+ metadata?: Record<string, unknown>;
17
+ isPublic: boolean;
18
+ tags: string[];
19
+ description?: string;
20
+ createdAt: string;
21
+ updatedAt: string;
22
+ }
23
+
24
+ export interface UniBoxFolder {
25
+ id: string;
26
+ name: string;
27
+ description?: string;
28
+ parentId?: string;
29
+ ownerId: string;
30
+ circleId?: string;
31
+ color?: string;
32
+ icon?: string;
33
+ isFavorite: boolean;
34
+ isPinned: boolean;
35
+ sortOrder: number;
36
+ itemCount: number;
37
+ totalSize: number;
38
+ createdAt: string;
39
+ updatedAt: string;
40
+ }
41
+
42
+ export interface UniBoxUploadRequest {
43
+ file: unknown;
44
+ description?: string;
45
+ folderId?: string;
46
+ isPublic?: boolean;
47
+ tags?: string[];
48
+ }
49
+
50
+ export interface UniBoxClientOptions {
51
+ baseURL: string;
52
+ appId?: string;
53
+ appSlug?: string;
54
+ getAccessToken?: () => Promise<string | null> | string | null;
55
+ }
56
+
57
+ export interface UniBoxListResult {
58
+ success: boolean;
59
+ files: UniBoxFile[];
60
+ total: number;
61
+ pagination?: unknown;
62
+ }
63
+
64
+ export interface UniBoxFolderListResult {
65
+ success: boolean;
66
+ folders: UniBoxFolder[];
67
+ total: number;
68
+ pagination?: unknown;
69
+ }
70
+
71
+ export interface UniBoxClient {
72
+ uploadFile(data: UniBoxUploadRequest): Promise<{ success: boolean; file: UniBoxFile }>;
73
+ getFiles(params?: Record<string, string | number | boolean>): Promise<UniBoxListResult>;
74
+ getCircleFiles(circleId: string, params?: Record<string, string | number | boolean>): Promise<UniBoxListResult>;
75
+ getFile(fileId: string): Promise<{ success: boolean; file: UniBoxFile }>;
76
+ updateFile(fileId: string, data: Record<string, unknown>): Promise<{ success: boolean; file: UniBoxFile }>;
77
+ deleteFile(fileId: string): Promise<{ success: boolean; message: string }>;
78
+ getFolders(params?: Record<string, string | number | boolean>): Promise<UniBoxFolderListResult>;
79
+ getCircleFolders(circleId: string, params?: Record<string, string | number | boolean>): Promise<UniBoxFolderListResult>;
80
+ createFolder(data: Record<string, unknown>): Promise<{ success: boolean; folder: UniBoxFolder }>;
81
+ createCircleFolder(circleId: string, data: Record<string, unknown>): Promise<{ success: boolean; folder: UniBoxFolder }>;
82
+ getFolderPath(folderId: string): Promise<{ success: boolean; path: UniBoxFolder[] }>;
83
+ getQuota(): Promise<unknown>;
84
+ getCircleQuota(circleId: string): Promise<unknown>;
85
+ getAnalytics(): Promise<unknown>;
86
+ searchFiles(query: string, params?: Record<string, string | number | boolean>): Promise<UniBoxListResult>;
87
+ getFileUrl(path: string): string;
88
+ }
89
+
90
+ function normalizeBaseURL(baseURL: string): string {
91
+ const trimmed = String(baseURL || '').replace(/\/$/, '');
92
+ return trimmed.endsWith('/api/v1') ? trimmed : `${trimmed}/api/v1`;
93
+ }
94
+
95
+ export function normalizeFileType(fileType?: string): UniBoxFileType {
96
+ if (fileType === 'image' || fileType === 'video' || fileType === 'audio') {
97
+ return fileType;
98
+ }
99
+
100
+ if (fileType === 'pdf' || fileType === 'document') {
101
+ return 'document';
102
+ }
103
+
104
+ return 'other';
105
+ }
106
+
107
+ function normalizeFileURL(baseURL: string, path: string): string {
108
+ if (!path) {
109
+ return '';
110
+ }
111
+
112
+ if (/^https?:\/\//i.test(path) || path.startsWith('data:')) {
113
+ return path;
114
+ }
115
+
116
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(path)) {
117
+ return `${baseURL.replace(/\/api\/v1$/, '')}/api/v1/storage/proxy/${path}`;
118
+ }
119
+
120
+ const origin = baseURL.replace(/\/api\/v1$/, '');
121
+ const normalizedPath = path.startsWith('/') ? path : `/api/uploads/${path}`;
122
+ return `${origin}${normalizedPath}`;
123
+ }
124
+
125
+ export function mapUniBoxFile(baseURL: string, file: any): UniBoxFile {
126
+ return {
127
+ id: file?.id || '',
128
+ userId: file?.uploadedBy || file?.userId || '',
129
+ applicationId: file?.applicationId,
130
+ circleId: file?.circleId || '',
131
+ fileName: file?.fileName || file?.filename || file?.originalName || '',
132
+ originalName: file?.originalName || file?.filename || file?.fileName || '',
133
+ fileSize: Number(file?.size ?? file?.fileSize ?? 0),
134
+ mimeType: file?.mimeType || 'application/octet-stream',
135
+ fileType: normalizeFileType(file?.fileType),
136
+ filePath: file?.filePath || file?.storagePath || '',
137
+ url: normalizeFileURL(baseURL, file?.url || file?.filePath || file?.storagePath || file?.id || ''),
138
+ thumbnailPath: file?.thumbnailUrl || file?.thumbnailPath,
139
+ metadata: file?.metadata,
140
+ isPublic: file?.isPublic ?? true,
141
+ tags: Array.isArray(file?.tags)
142
+ ? file.tags.map((tag: any) => (typeof tag === 'string' ? tag : tag?.name)).filter(Boolean)
143
+ : [],
144
+ description: file?.description,
145
+ createdAt: file?.createdAt || new Date().toISOString(),
146
+ updatedAt: file?.updatedAt || file?.createdAt || new Date().toISOString(),
147
+ };
148
+ }
149
+
150
+ export function mapUniBoxFolder(folder: any): UniBoxFolder {
151
+ return {
152
+ id: folder?.id || '',
153
+ name: folder?.name || '',
154
+ description: folder?.description,
155
+ parentId: folder?.parentId,
156
+ ownerId: folder?.ownerId || folder?.userId || '',
157
+ circleId: folder?.circleId,
158
+ color: folder?.color,
159
+ icon: folder?.icon,
160
+ isFavorite: Boolean(folder?.isFavorite),
161
+ isPinned: Boolean(folder?.isPinned),
162
+ sortOrder: Number(folder?.sortOrder || 0),
163
+ itemCount: Number(folder?.itemCount || 0),
164
+ totalSize: Number(folder?.totalSize || 0),
165
+ createdAt: folder?.createdAt || new Date().toISOString(),
166
+ updatedAt: folder?.updatedAt || folder?.createdAt || new Date().toISOString(),
167
+ };
168
+ }
169
+
170
+ export function createUniBoxClient(options: UniBoxClientOptions): UniBoxClient {
171
+ const baseURL = normalizeBaseURL(options.baseURL);
172
+
173
+ async function buildHeaders(extraHeaders?: Record<string, string>): Promise<Record<string, string>> {
174
+ const accessToken = options.getAccessToken ? await options.getAccessToken() : null;
175
+ const headers: Record<string, string> = { ...(extraHeaders || {}) };
176
+
177
+ if (options.appId) {
178
+ headers['X-App-ID'] = options.appId;
179
+ } else if (options.appSlug) {
180
+ headers['X-App-Slug'] = options.appSlug;
181
+ }
182
+
183
+ if (accessToken) {
184
+ headers.Authorization = `Bearer ${accessToken}`;
185
+ }
186
+
187
+ return headers;
188
+ }
189
+
190
+ async function request(path: string, init: RequestInit): Promise<any> {
191
+ const response = await fetch(`${baseURL}${path}`, init);
192
+
193
+ if (!response.ok) {
194
+ let message = `Request failed: HTTP ${response.status}`;
195
+
196
+ try {
197
+ const payload = await response.json();
198
+ if (payload?.message || payload?.error) {
199
+ message = payload.message || payload.error;
200
+ }
201
+ } catch {
202
+ try {
203
+ const text = await response.text();
204
+ if (text) {
205
+ message = text;
206
+ }
207
+ } catch {
208
+ // Ignore text parsing error.
209
+ }
210
+ }
211
+
212
+ throw new Error(message);
213
+ }
214
+
215
+ return response.json();
216
+ }
217
+
218
+ function buildQuery(params?: Record<string, string | number | boolean>): string {
219
+ const searchParams = new URLSearchParams();
220
+
221
+ Object.entries(params || {}).forEach(([key, value]) => {
222
+ if (value !== undefined && value !== null && value !== '') {
223
+ searchParams.set(key, String(value));
224
+ }
225
+ });
226
+
227
+ const query = searchParams.toString();
228
+ return query ? `?${query}` : '';
229
+ }
230
+
231
+ return {
232
+ uploadFile: async (data) => {
233
+ const formData = new FormData();
234
+ formData.append('file', data.file as any);
235
+
236
+ if (data.description) {
237
+ formData.append('description', data.description);
238
+ }
239
+
240
+ if (data.folderId) {
241
+ formData.append('folderId', data.folderId);
242
+ }
243
+
244
+ if (data.isPublic !== undefined) {
245
+ formData.append('isPublic', String(data.isPublic));
246
+ }
247
+
248
+ if (Array.isArray(data.tags) && data.tags.length > 0) {
249
+ formData.append('tags', JSON.stringify(data.tags));
250
+ }
251
+
252
+ const response = await request('/files/files/upload', {
253
+ method: 'POST',
254
+ headers: await buildHeaders(),
255
+ body: formData,
256
+ });
257
+
258
+ return {
259
+ success: Boolean(response?.success),
260
+ file: mapUniBoxFile(baseURL, response?.file || response?.data?.file || response?.data),
261
+ };
262
+ },
263
+
264
+ getFiles: async (params) => {
265
+ const response = await request(`/files/files${buildQuery(params)}`, {
266
+ method: 'GET',
267
+ headers: await buildHeaders(),
268
+ });
269
+
270
+ const files = Array.isArray(response?.files) ? response.files : [];
271
+
272
+ return {
273
+ success: Boolean(response?.success),
274
+ files: files.map((file: any) => mapUniBoxFile(baseURL, file)),
275
+ total: Number(response?.total || files.length || 0),
276
+ pagination: response?.pagination,
277
+ };
278
+ },
279
+
280
+ getCircleFiles: async (circleId, params) => {
281
+ const response = await request(`/files/circles/${encodeURIComponent(circleId)}/files${buildQuery(params)}`, {
282
+ method: 'GET',
283
+ headers: await buildHeaders(),
284
+ });
285
+
286
+ const files = Array.isArray(response?.files) ? response.files : [];
287
+
288
+ return {
289
+ success: Boolean(response?.success),
290
+ files: files.map((file: any) => mapUniBoxFile(baseURL, file)),
291
+ total: Number(response?.total || files.length || 0),
292
+ pagination: response?.pagination,
293
+ };
294
+ },
295
+
296
+ getFile: async (fileId) => {
297
+ const response = await request(`/files/files/${encodeURIComponent(fileId)}`, {
298
+ method: 'GET',
299
+ headers: await buildHeaders(),
300
+ });
301
+
302
+ return {
303
+ success: Boolean(response?.success),
304
+ file: mapUniBoxFile(baseURL, response?.file || response?.data?.file || response?.data),
305
+ };
306
+ },
307
+
308
+ updateFile: async (fileId, data) => {
309
+ const response = await request(`/files/files/${encodeURIComponent(fileId)}`, {
310
+ method: 'PUT',
311
+ headers: await buildHeaders({ 'Content-Type': 'application/json' }),
312
+ body: JSON.stringify(data || {}),
313
+ });
314
+
315
+ return {
316
+ success: Boolean(response?.success),
317
+ file: mapUniBoxFile(baseURL, response?.file || response?.data?.file || response?.data),
318
+ };
319
+ },
320
+
321
+ deleteFile: async (fileId) => {
322
+ const response = await request(`/files/files/${encodeURIComponent(fileId)}`, {
323
+ method: 'DELETE',
324
+ headers: await buildHeaders(),
325
+ });
326
+
327
+ return {
328
+ success: Boolean(response?.success !== false),
329
+ message: response?.message || response?.error || 'File deleted',
330
+ };
331
+ },
332
+
333
+ getFolders: async (params) => {
334
+ const response = await request(`/files/folders${buildQuery(params)}`, {
335
+ method: 'GET',
336
+ headers: await buildHeaders(),
337
+ });
338
+
339
+ const folders = Array.isArray(response?.folders) ? response.folders : [];
340
+
341
+ return {
342
+ success: Boolean(response?.success),
343
+ folders: folders.map((folder: any) => mapUniBoxFolder(folder)),
344
+ total: Number(response?.total || folders.length || 0),
345
+ pagination: response?.pagination,
346
+ };
347
+ },
348
+
349
+ getCircleFolders: async (circleId, params) => {
350
+ const response = await request(`/files/circles/${encodeURIComponent(circleId)}/folders${buildQuery(params)}`, {
351
+ method: 'GET',
352
+ headers: await buildHeaders(),
353
+ });
354
+
355
+ const folders = Array.isArray(response?.folders) ? response.folders : [];
356
+
357
+ return {
358
+ success: Boolean(response?.success),
359
+ folders: folders.map((folder: any) => mapUniBoxFolder(folder)),
360
+ total: Number(response?.total || folders.length || 0),
361
+ pagination: response?.pagination,
362
+ };
363
+ },
364
+
365
+ createFolder: async (data) => {
366
+ const response = await request('/files/folders', {
367
+ method: 'POST',
368
+ headers: await buildHeaders({ 'Content-Type': 'application/json' }),
369
+ body: JSON.stringify(data || {}),
370
+ });
371
+
372
+ return {
373
+ success: Boolean(response?.success),
374
+ folder: mapUniBoxFolder(response?.folder || response?.data?.folder || response?.data),
375
+ };
376
+ },
377
+
378
+ createCircleFolder: async (circleId, data) => {
379
+ const response = await request(`/files/circles/${encodeURIComponent(circleId)}/folders`, {
380
+ method: 'POST',
381
+ headers: await buildHeaders({ 'Content-Type': 'application/json' }),
382
+ body: JSON.stringify(data || {}),
383
+ });
384
+
385
+ return {
386
+ success: Boolean(response?.success),
387
+ folder: mapUniBoxFolder(response?.folder || response?.data?.folder || response?.data),
388
+ };
389
+ },
390
+
391
+ getFolderPath: async (folderId) => {
392
+ const response = await request(`/files/folders/${encodeURIComponent(folderId)}/path`, {
393
+ method: 'GET',
394
+ headers: await buildHeaders(),
395
+ });
396
+
397
+ const path = Array.isArray(response?.path) ? response.path : [];
398
+
399
+ return {
400
+ success: Boolean(response?.success),
401
+ path: path.map((folder: any) => mapUniBoxFolder(folder)),
402
+ };
403
+ },
404
+
405
+ getQuota: async () =>
406
+ request('/files/quota', {
407
+ method: 'GET',
408
+ headers: await buildHeaders(),
409
+ }),
410
+
411
+ getCircleQuota: async (circleId) =>
412
+ request(`/files/circles/${encodeURIComponent(circleId)}/quota`, {
413
+ method: 'GET',
414
+ headers: await buildHeaders(),
415
+ }),
416
+
417
+ getAnalytics: async () =>
418
+ request('/files/analytics', {
419
+ method: 'GET',
420
+ headers: await buildHeaders(),
421
+ }),
422
+
423
+ searchFiles: async (query, params) => {
424
+ const response = await request(
425
+ `/files/search${buildQuery({
426
+ query,
427
+ ...(params || {}),
428
+ })}`,
429
+ {
430
+ method: 'GET',
431
+ headers: await buildHeaders(),
432
+ }
433
+ );
434
+
435
+ const files = Array.isArray(response?.files) ? response.files : [];
436
+
437
+ return {
438
+ success: Boolean(response?.success),
439
+ files: files.map((file: any) => mapUniBoxFile(baseURL, file)),
440
+ total: Number(response?.total || files.length || 0),
441
+ pagination: response?.pagination,
442
+ };
443
+ },
444
+
445
+ getFileUrl: (path) => normalizeFileURL(baseURL, path),
446
+ };
447
+ }