@dcd-sdk/storage-client 0.0.0-stage → 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 +100 -2
- package/dist/builder.d.ts +31 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +94 -0
- package/dist/client.d.ts +38 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +178 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/types.d.ts +67 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/package.json +36 -4
package/README.md
CHANGED
|
@@ -1,3 +1,101 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @dcd-sdk/storage-client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official Node.js & TypeScript Client SDK for DevCloudDesk Storage Engine.
|
|
4
|
+
|
|
5
|
+
High-performance, Cloudinary-style on-the-fly media transformations and ultra-fast direct asset delivery backed by your DevCloudDesk storage clusters.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@dcd-sdk/storage-client)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 📦 Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# npm
|
|
16
|
+
npm install @dcd-sdk/storage-client
|
|
17
|
+
|
|
18
|
+
# pnpm
|
|
19
|
+
pnpm add @dcd-sdk/storage-client
|
|
20
|
+
|
|
21
|
+
# yarn
|
|
22
|
+
yarn add @dcd-sdk/storage-client
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🚀 Quickstart
|
|
28
|
+
|
|
29
|
+
### 1. Initialize the Client
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createBucketClient } from '@dcd-sdk/storage-client';
|
|
33
|
+
|
|
34
|
+
const bucket = createBucketClient({
|
|
35
|
+
endpoint: 'http://YOUR_SERVER_IP:7100', // or your custom domain https://storage.yourdomain.com
|
|
36
|
+
apiKey: process.env.STORAGE_API_KEY, // API Key from DevCloudDesk Dashboard
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Uploading Files (Node.js or Browser)
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// Upload from Buffer, File, or local file path
|
|
44
|
+
const asset = await bucket.upload(fileBuffer, {
|
|
45
|
+
folder: 'avatars',
|
|
46
|
+
filename: 'user-profile.png',
|
|
47
|
+
onProgress: (percent) => {
|
|
48
|
+
console.log(`Upload progress: ${percent}%`);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
console.log('Public ID:', asset.publicId);
|
|
53
|
+
console.log('CDN URL:', asset.url);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 3. Dynamic On-The-Fly Transformations
|
|
57
|
+
|
|
58
|
+
Generate real-time optimized WebP/AVIF images with automatic resizing:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const avatarUrl = bucket
|
|
62
|
+
.url(asset.publicId)
|
|
63
|
+
.resize({ width: 400, height: 400, crop: 'cover' })
|
|
64
|
+
.format('webp')
|
|
65
|
+
.quality(80)
|
|
66
|
+
.build();
|
|
67
|
+
|
|
68
|
+
// Output: http://YOUR_SERVER_IP:7100/v1/image/w_400,h_400,c_cover,f_webp,q_80/avatars/user-profile.png
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 4. Client-Side Uploads via Signed Tokens (Zero Secret Exposure)
|
|
72
|
+
|
|
73
|
+
Keep your master API keys safely on your backend server. Generate short-lived, tamper-proof signed upload tokens for your frontend React/Next.js/Mobile apps:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// Backend endpoint:
|
|
77
|
+
app.post('/api/get-upload-token', async (req, res) => {
|
|
78
|
+
const tokenData = await bucket.createSignedUploadToken({
|
|
79
|
+
folder: 'user-uploads',
|
|
80
|
+
expiresInSeconds: 300, // 5 minutes
|
|
81
|
+
maxBytes: 10 * 1024 * 1024, // 10 MB limit
|
|
82
|
+
});
|
|
83
|
+
res.json(tokenData);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Frontend client:
|
|
87
|
+
const { token } = await res.json();
|
|
88
|
+
await bucket.uploadWithToken(file, token);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 5. Deleting Files
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
await bucket.delete(asset.publicId);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 📄 License
|
|
100
|
+
|
|
101
|
+
MIT © [DevCloudDesk](https://devclouddesk.com)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CropMode, OutputFormat } from './types.js';
|
|
2
|
+
export declare class TransformationBuilder {
|
|
3
|
+
private endpoint;
|
|
4
|
+
private publicId;
|
|
5
|
+
private options;
|
|
6
|
+
constructor(endpoint: string, publicId: string);
|
|
7
|
+
width(w: number): this;
|
|
8
|
+
height(h: number): this;
|
|
9
|
+
resize(params: {
|
|
10
|
+
width?: number;
|
|
11
|
+
height?: number;
|
|
12
|
+
crop?: CropMode;
|
|
13
|
+
}): this;
|
|
14
|
+
crop(mode: CropMode): this;
|
|
15
|
+
format(fmt: OutputFormat): this;
|
|
16
|
+
quality(q: number | 'auto'): this;
|
|
17
|
+
blur(sigma?: number): this;
|
|
18
|
+
grayscale(enable?: boolean): this;
|
|
19
|
+
sharpen(enable?: boolean): this;
|
|
20
|
+
rotation(rot: number | 'auto'): this;
|
|
21
|
+
/**
|
|
22
|
+
* Builds the formatted transformation string (e.g. w_400,h_300,c_fill,f_auto)
|
|
23
|
+
*/
|
|
24
|
+
toParamsString(): string;
|
|
25
|
+
/**
|
|
26
|
+
* Returns the final full URL string
|
|
27
|
+
*/
|
|
28
|
+
build(): string;
|
|
29
|
+
toString(): string;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAyB,MAAM,YAAY,CAAC;AAEhF,qBAAa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAA6B;gBAEhC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAK9C,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAKtB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI;IAKvB,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,IAAI;IAO1E,IAAI,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IAK1B,MAAM,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI;IAK/B,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAKjC,IAAI,CAAC,KAAK,SAAI,GAAG,IAAI;IAKrB,SAAS,CAAC,MAAM,UAAO,GAAG,IAAI;IAK9B,OAAO,CAAC,MAAM,UAAO,GAAG,IAAI;IAK5B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAKpC;;OAEG;IACH,cAAc,IAAI,MAAM;IAgBxB;;OAEG;IACH,KAAK,IAAI,MAAM;IAQf,QAAQ,IAAI,MAAM;CAGnB"}
|
package/dist/builder.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TransformationBuilder = void 0;
|
|
4
|
+
class TransformationBuilder {
|
|
5
|
+
constructor(endpoint, publicId) {
|
|
6
|
+
this.options = {};
|
|
7
|
+
this.endpoint = endpoint.replace(/\/+$/, '');
|
|
8
|
+
this.publicId = publicId.replace(/^\/+/, '');
|
|
9
|
+
}
|
|
10
|
+
width(w) {
|
|
11
|
+
this.options.width = Math.round(w);
|
|
12
|
+
return this;
|
|
13
|
+
}
|
|
14
|
+
height(h) {
|
|
15
|
+
this.options.height = Math.round(h);
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
resize(params) {
|
|
19
|
+
if (params.width)
|
|
20
|
+
this.options.width = Math.round(params.width);
|
|
21
|
+
if (params.height)
|
|
22
|
+
this.options.height = Math.round(params.height);
|
|
23
|
+
if (params.crop)
|
|
24
|
+
this.options.crop = params.crop;
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
crop(mode) {
|
|
28
|
+
this.options.crop = mode;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
format(fmt) {
|
|
32
|
+
this.options.format = fmt;
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
quality(q) {
|
|
36
|
+
this.options.quality = q;
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
blur(sigma = 5) {
|
|
40
|
+
this.options.blur = sigma;
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
grayscale(enable = true) {
|
|
44
|
+
this.options.grayscale = enable;
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
sharpen(enable = true) {
|
|
48
|
+
this.options.sharpen = enable;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
rotation(rot) {
|
|
52
|
+
this.options.rotation = rot;
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Builds the formatted transformation string (e.g. w_400,h_300,c_fill,f_auto)
|
|
57
|
+
*/
|
|
58
|
+
toParamsString() {
|
|
59
|
+
const parts = [];
|
|
60
|
+
if (this.options.width)
|
|
61
|
+
parts.push(`w_${this.options.width}`);
|
|
62
|
+
if (this.options.height)
|
|
63
|
+
parts.push(`h_${this.options.height}`);
|
|
64
|
+
if (this.options.crop)
|
|
65
|
+
parts.push(`c_${this.options.crop}`);
|
|
66
|
+
if (this.options.format)
|
|
67
|
+
parts.push(`f_${this.options.format}`);
|
|
68
|
+
if (this.options.quality)
|
|
69
|
+
parts.push(`q_${this.options.quality}`);
|
|
70
|
+
if (this.options.blur)
|
|
71
|
+
parts.push(`e_blur_${this.options.blur}`);
|
|
72
|
+
if (this.options.grayscale)
|
|
73
|
+
parts.push('e_gray');
|
|
74
|
+
if (this.options.sharpen)
|
|
75
|
+
parts.push('e_sharp');
|
|
76
|
+
if (this.options.rotation)
|
|
77
|
+
parts.push(`r_${this.options.rotation}`);
|
|
78
|
+
return parts.length > 0 ? parts.join(',') : 'original';
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Returns the final full URL string
|
|
82
|
+
*/
|
|
83
|
+
build() {
|
|
84
|
+
const params = this.toParamsString();
|
|
85
|
+
if (params === 'original') {
|
|
86
|
+
return `${this.endpoint}/v1/image/${this.publicId}`;
|
|
87
|
+
}
|
|
88
|
+
return `${this.endpoint}/v1/image/${params}/${this.publicId}`;
|
|
89
|
+
}
|
|
90
|
+
toString() {
|
|
91
|
+
return this.build();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.TransformationBuilder = TransformationBuilder;
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { TransformationBuilder } from './builder.js';
|
|
2
|
+
import type { AssetResult, SignedUploadResult, SignUploadOptions, StorageClientConfig, UploadOptions } from './types.js';
|
|
3
|
+
export declare class StorageClient {
|
|
4
|
+
private endpoint;
|
|
5
|
+
private apiKey;
|
|
6
|
+
constructor(config: StorageClientConfig);
|
|
7
|
+
/**
|
|
8
|
+
* Initializes a fluent URL builder for the specified asset public ID
|
|
9
|
+
*/
|
|
10
|
+
url(publicId: string): TransformationBuilder;
|
|
11
|
+
/**
|
|
12
|
+
* Generates a short-lived cryptographically signed upload token for safe client-side browser/mobile uploads.
|
|
13
|
+
* Keeps master API keys strictly on backend servers.
|
|
14
|
+
*/
|
|
15
|
+
createSignedUploadToken(options?: SignUploadOptions): Promise<SignedUploadResult>;
|
|
16
|
+
/**
|
|
17
|
+
* Directly uploads a file to the storage server.
|
|
18
|
+
* Supports File/Blob in browser environments and Buffer/ReadStream in Node.js environments.
|
|
19
|
+
* Supports both Master API Key and Client Signed Upload Tokens (dcd_sig_...).
|
|
20
|
+
*/
|
|
21
|
+
upload(file: any, options?: UploadOptions): Promise<AssetResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Helper method for frontend clients to upload using only a signed upload token (zero API key)
|
|
24
|
+
*/
|
|
25
|
+
uploadWithToken(file: any, token: string, options?: Omit<UploadOptions, 'token'>): Promise<AssetResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Permanently deletes a storage asset (requires API key with delete role)
|
|
28
|
+
*/
|
|
29
|
+
delete(publicId: string): Promise<boolean>;
|
|
30
|
+
}
|
|
31
|
+
export declare function createStorageClient(config: StorageClientConfig): StorageClient;
|
|
32
|
+
export declare const BucketClient: typeof StorageClient;
|
|
33
|
+
export type BucketClient = StorageClient;
|
|
34
|
+
export declare const createBucketClient: typeof createStorageClient;
|
|
35
|
+
export declare const MediaClient: typeof StorageClient;
|
|
36
|
+
export type MediaClient = StorageClient;
|
|
37
|
+
export declare const createMediaClient: typeof createStorageClient;
|
|
38
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,aAAa,EACd,MAAM,YAAY,CAAC;AAEpB,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,mBAAmB;IAQvC;;OAEG;IACH,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB;IAI5C;;;OAGG;IACG,uBAAuB,CAAC,OAAO,GAAE,iBAAsB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAuB3F;;;;OAIG;IACG,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,WAAW,CAAC;IAqG1E;;OAEG;IACG,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,IAAI,CAAC,aAAa,EAAE,OAAO,CAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAIjH;;OAEG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAajD;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CAE9E;AAGD,eAAO,MAAM,YAAY,sBAAgB,CAAC;AAC1C,MAAM,MAAM,YAAY,GAAG,aAAa,CAAC;AACzC,eAAO,MAAM,kBAAkB,4BAAsB,CAAC;AAGtD,eAAO,MAAM,WAAW,sBAAgB,CAAC;AACzC,MAAM,MAAM,WAAW,GAAG,aAAa,CAAC;AACxC,eAAO,MAAM,iBAAiB,4BAAsB,CAAC"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMediaClient = exports.MediaClient = exports.createBucketClient = exports.BucketClient = exports.StorageClient = void 0;
|
|
4
|
+
exports.createStorageClient = createStorageClient;
|
|
5
|
+
const builder_js_1 = require("./builder.js");
|
|
6
|
+
class StorageClient {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
if (!config.endpoint) {
|
|
9
|
+
throw new Error('StorageClient: endpoint is required');
|
|
10
|
+
}
|
|
11
|
+
this.endpoint = config.endpoint.replace(/\/+$/, '');
|
|
12
|
+
this.apiKey = (config.apiKey || '').trim();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Initializes a fluent URL builder for the specified asset public ID
|
|
16
|
+
*/
|
|
17
|
+
url(publicId) {
|
|
18
|
+
return new builder_js_1.TransformationBuilder(this.endpoint, publicId);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Generates a short-lived cryptographically signed upload token for safe client-side browser/mobile uploads.
|
|
22
|
+
* Keeps master API keys strictly on backend servers.
|
|
23
|
+
*/
|
|
24
|
+
async createSignedUploadToken(options = {}) {
|
|
25
|
+
if (!this.apiKey) {
|
|
26
|
+
throw new Error('StorageClient: API key is required on backend to generate signed upload tokens.');
|
|
27
|
+
}
|
|
28
|
+
const signUrl = `${this.endpoint}/v1/upload/sign`;
|
|
29
|
+
const response = await fetch(signUrl, {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: {
|
|
32
|
+
'Content-Type': 'application/json',
|
|
33
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
34
|
+
},
|
|
35
|
+
body: JSON.stringify(options),
|
|
36
|
+
});
|
|
37
|
+
const data = await response.json();
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(data.error?.message || `Failed to generate signed upload token with status ${response.status}`);
|
|
40
|
+
}
|
|
41
|
+
return data;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Directly uploads a file to the storage server.
|
|
45
|
+
* Supports File/Blob in browser environments and Buffer/ReadStream in Node.js environments.
|
|
46
|
+
* Supports both Master API Key and Client Signed Upload Tokens (dcd_sig_...).
|
|
47
|
+
*/
|
|
48
|
+
async upload(file, options = {}) {
|
|
49
|
+
const uploadUrl = `${this.endpoint}/v1/upload`;
|
|
50
|
+
const formData = new FormData();
|
|
51
|
+
let fileToAppend = file;
|
|
52
|
+
let filename = options.filename;
|
|
53
|
+
if (typeof file === 'string') {
|
|
54
|
+
if (typeof process !== 'undefined' && process.versions?.node) {
|
|
55
|
+
try {
|
|
56
|
+
const fs = await import('node:fs');
|
|
57
|
+
const path = await import('node:path');
|
|
58
|
+
if (fs.existsSync(file)) {
|
|
59
|
+
const buf = fs.readFileSync(file);
|
|
60
|
+
filename = filename || path.basename(file);
|
|
61
|
+
fileToAppend = new Blob([new Uint8Array(buf)]);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// ignore
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(file)) {
|
|
70
|
+
fileToAppend = new Blob([new Uint8Array(file)]);
|
|
71
|
+
filename = filename || 'upload.bin';
|
|
72
|
+
}
|
|
73
|
+
if (filename) {
|
|
74
|
+
formData.append('file', fileToAppend, filename);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
formData.append('file', fileToAppend);
|
|
78
|
+
}
|
|
79
|
+
if (options.folder)
|
|
80
|
+
formData.append('folder', options.folder);
|
|
81
|
+
if (options.publicId)
|
|
82
|
+
formData.append('public_id', options.publicId);
|
|
83
|
+
if (options.overwrite !== undefined)
|
|
84
|
+
formData.append('overwrite', String(options.overwrite));
|
|
85
|
+
if (options.uniqueFilename !== undefined)
|
|
86
|
+
formData.append('unique_filename', String(options.uniqueFilename));
|
|
87
|
+
// Determine auth header: Use options.token if provided, otherwise this.apiKey
|
|
88
|
+
const headers = {};
|
|
89
|
+
if (options.token) {
|
|
90
|
+
headers['X-Upload-Token'] = options.token;
|
|
91
|
+
}
|
|
92
|
+
else if (this.apiKey) {
|
|
93
|
+
headers['Authorization'] = `Bearer ${this.apiKey}`;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
throw new Error('StorageClient: Either apiKey or a signed upload token must be provided to upload.');
|
|
97
|
+
}
|
|
98
|
+
// Browser upload with progress tracking via XMLHttpRequest
|
|
99
|
+
if (typeof XMLHttpRequest !== 'undefined' && options.onProgress) {
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
const xhr = new XMLHttpRequest();
|
|
102
|
+
xhr.open('POST', uploadUrl, true);
|
|
103
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
104
|
+
xhr.setRequestHeader(k, v);
|
|
105
|
+
}
|
|
106
|
+
xhr.upload.onprogress = (e) => {
|
|
107
|
+
if (e.lengthComputable && options.onProgress) {
|
|
108
|
+
const percent = Math.round((e.loaded / e.total) * 100);
|
|
109
|
+
options.onProgress(percent);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
xhr.onload = () => {
|
|
113
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
114
|
+
try {
|
|
115
|
+
const res = JSON.parse(xhr.responseText);
|
|
116
|
+
resolve(res.asset);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
reject(new Error('Invalid response from storage server'));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
try {
|
|
124
|
+
const errData = JSON.parse(xhr.responseText);
|
|
125
|
+
reject(new Error(errData.error?.message || `Upload failed with status ${xhr.status}`));
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
reject(new Error(`Upload failed with status ${xhr.status}`));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
xhr.onerror = () => reject(new Error('Network error during file upload'));
|
|
133
|
+
xhr.send(formData);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// Standard fetch upload (Browser or Node.js)
|
|
137
|
+
const response = await fetch(uploadUrl, {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers,
|
|
140
|
+
body: formData,
|
|
141
|
+
});
|
|
142
|
+
const data = await response.json();
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
throw new Error(data.error?.message || `Upload failed with status ${response.status}`);
|
|
145
|
+
}
|
|
146
|
+
return data.asset;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Helper method for frontend clients to upload using only a signed upload token (zero API key)
|
|
150
|
+
*/
|
|
151
|
+
async uploadWithToken(file, token, options = {}) {
|
|
152
|
+
return this.upload(file, { ...options, token });
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Permanently deletes a storage asset (requires API key with delete role)
|
|
156
|
+
*/
|
|
157
|
+
async delete(publicId) {
|
|
158
|
+
const cleanPublicId = publicId.replace(/^\/+/, '');
|
|
159
|
+
const deleteUrl = `${this.endpoint}/v1/admin/files/${cleanPublicId}`;
|
|
160
|
+
const response = await fetch(deleteUrl, {
|
|
161
|
+
method: 'DELETE',
|
|
162
|
+
headers: {
|
|
163
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
return response.ok;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
exports.StorageClient = StorageClient;
|
|
170
|
+
function createStorageClient(config) {
|
|
171
|
+
return new StorageClient(config);
|
|
172
|
+
}
|
|
173
|
+
// Bucket modern API aliases
|
|
174
|
+
exports.BucketClient = StorageClient;
|
|
175
|
+
exports.createBucketClient = createStorageClient;
|
|
176
|
+
// Backward compatibility aliases
|
|
177
|
+
exports.MediaClient = StorageClient;
|
|
178
|
+
exports.createMediaClient = createStorageClient;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { TransformationBuilder } from './builder.js';
|
|
2
|
+
export { StorageClient, createStorageClient, BucketClient, createBucketClient, MediaClient, createMediaClient, } from './client.js';
|
|
3
|
+
export type { AssetResult, StorageAssetResult, BucketAssetResult, CropMode, OutputFormat, StorageClientConfig, BucketClientConfig, MediaClientConfig, TransformationOptions, UploadOptions, } from './types.js';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,EAClB,iBAAiB,EACjB,qBAAqB,EACrB,aAAa,GACd,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createMediaClient = exports.MediaClient = exports.createBucketClient = exports.BucketClient = exports.createStorageClient = exports.StorageClient = exports.TransformationBuilder = void 0;
|
|
4
|
+
var builder_js_1 = require("./builder.js");
|
|
5
|
+
Object.defineProperty(exports, "TransformationBuilder", { enumerable: true, get: function () { return builder_js_1.TransformationBuilder; } });
|
|
6
|
+
var client_js_1 = require("./client.js");
|
|
7
|
+
Object.defineProperty(exports, "StorageClient", { enumerable: true, get: function () { return client_js_1.StorageClient; } });
|
|
8
|
+
Object.defineProperty(exports, "createStorageClient", { enumerable: true, get: function () { return client_js_1.createStorageClient; } });
|
|
9
|
+
Object.defineProperty(exports, "BucketClient", { enumerable: true, get: function () { return client_js_1.BucketClient; } });
|
|
10
|
+
Object.defineProperty(exports, "createBucketClient", { enumerable: true, get: function () { return client_js_1.createBucketClient; } });
|
|
11
|
+
Object.defineProperty(exports, "MediaClient", { enumerable: true, get: function () { return client_js_1.MediaClient; } });
|
|
12
|
+
Object.defineProperty(exports, "createMediaClient", { enumerable: true, get: function () { return client_js_1.createMediaClient; } });
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type CropMode = 'fill' | 'fit' | 'crop' | 'scale';
|
|
2
|
+
export type OutputFormat = 'auto' | 'webp' | 'avif' | 'jpeg' | 'png';
|
|
3
|
+
export interface StorageClientConfig {
|
|
4
|
+
/** The base URL of the storage engine cluster (e.g. 'https://storage.mycoolapp.com' or 'http://192.168.1.50:7100') */
|
|
5
|
+
endpoint: string;
|
|
6
|
+
/** Public API key (dcd_pk_...), Secret key (dcd_sk_...), or Live key */
|
|
7
|
+
apiKey?: string;
|
|
8
|
+
}
|
|
9
|
+
export type MediaClientConfig = StorageClientConfig;
|
|
10
|
+
export type BucketClientConfig = StorageClientConfig;
|
|
11
|
+
export interface TransformationOptions {
|
|
12
|
+
width?: number;
|
|
13
|
+
height?: number;
|
|
14
|
+
crop?: CropMode;
|
|
15
|
+
format?: OutputFormat;
|
|
16
|
+
quality?: number | 'auto';
|
|
17
|
+
blur?: number;
|
|
18
|
+
grayscale?: boolean;
|
|
19
|
+
sharpen?: boolean;
|
|
20
|
+
rotation?: number | 'auto';
|
|
21
|
+
}
|
|
22
|
+
export interface UploadOptions {
|
|
23
|
+
folder?: string;
|
|
24
|
+
publicId?: string;
|
|
25
|
+
filename?: string;
|
|
26
|
+
overwrite?: boolean;
|
|
27
|
+
uniqueFilename?: boolean;
|
|
28
|
+
/** Optional client signed upload token (dcd_sig_...) to upload without API key */
|
|
29
|
+
token?: string;
|
|
30
|
+
tags?: string[];
|
|
31
|
+
onProgress?: (percent: number) => void;
|
|
32
|
+
}
|
|
33
|
+
export interface SignUploadOptions {
|
|
34
|
+
folder?: string;
|
|
35
|
+
maxBytes?: number;
|
|
36
|
+
expiresInSeconds?: number;
|
|
37
|
+
}
|
|
38
|
+
export interface SignedUploadResult {
|
|
39
|
+
success: boolean;
|
|
40
|
+
token: string;
|
|
41
|
+
uploadUrl: string;
|
|
42
|
+
expiresInSeconds: number;
|
|
43
|
+
folder?: string | null;
|
|
44
|
+
maxBytes?: number | null;
|
|
45
|
+
}
|
|
46
|
+
export interface AssetResult {
|
|
47
|
+
public_id: string;
|
|
48
|
+
publicId: string;
|
|
49
|
+
filename: string;
|
|
50
|
+
format: string;
|
|
51
|
+
resource_type?: string;
|
|
52
|
+
resourceType?: string;
|
|
53
|
+
width?: number;
|
|
54
|
+
height?: number;
|
|
55
|
+
bytes: number;
|
|
56
|
+
storageDriver: string;
|
|
57
|
+
etag: string;
|
|
58
|
+
url: string;
|
|
59
|
+
secure_url: string;
|
|
60
|
+
secureUrl: string;
|
|
61
|
+
rawUrl?: string;
|
|
62
|
+
created_at: number;
|
|
63
|
+
createdAt: number;
|
|
64
|
+
}
|
|
65
|
+
export type StorageAssetResult = AssetResult;
|
|
66
|
+
export type BucketAssetResult = AssetResult;
|
|
67
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AACzD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;AAErE,MAAM,WAAW,mBAAmB;IAClC,sHAAsH;IACtH,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAGD,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AACpD,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,MAAM,kBAAkB,GAAG,WAAW,CAAC;AAC7C,MAAM,MAAM,iBAAiB,GAAG,WAAW,CAAC"}
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dcd-sdk/storage-client",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official Node.js & TypeScript client SDK for DevCloudDesk Storage Engine",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.js",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"test": "vitest run"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.17.19",
|
|
28
|
+
"typescript": "^5.7.3",
|
|
29
|
+
"vitest": "^5.0.5"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/devclouddesk/dcd-sdk.git",
|
|
34
|
+
"directory": "packages/storage-client"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://devclouddesk.com",
|
|
37
|
+
"license": "MIT"
|
|
38
|
+
}
|