@kmlckj/licos-platform-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 +3 -0
- package/package.json +28 -0
- package/src/index.d.ts +64 -0
- package/src/index.js +178 -0
package/README.md
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kmlckj/licos-platform-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "LICOS platform SDK for storage upload, download, and preview",
|
|
5
|
+
"author": "kmlckj",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./src/index.js",
|
|
8
|
+
"types": "./src/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./src/index.d.ts",
|
|
12
|
+
"import": "./src/index.js",
|
|
13
|
+
"default": "./src/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "node --test"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public",
|
|
26
|
+
"registry": "https://registry.npmjs.org"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export interface UploadResult {
|
|
2
|
+
bucket: string;
|
|
3
|
+
object_key: string;
|
|
4
|
+
file_name: string;
|
|
5
|
+
content_type: string;
|
|
6
|
+
size_bytes: number;
|
|
7
|
+
preview_url: string;
|
|
8
|
+
download_url: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface StorageClientOptions {
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
token?: string;
|
|
14
|
+
userId?: string;
|
|
15
|
+
projectId?: string;
|
|
16
|
+
fetch?: typeof fetch;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UploadProjectOptions extends StorageClientOptions {
|
|
20
|
+
userId?: string;
|
|
21
|
+
projectId?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class PlatformSdkError extends Error {
|
|
25
|
+
status?: number;
|
|
26
|
+
code?: number;
|
|
27
|
+
details?: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ConfigurationError extends PlatformSdkError {}
|
|
31
|
+
export class ApiError extends PlatformSdkError {}
|
|
32
|
+
|
|
33
|
+
export class StorageClient {
|
|
34
|
+
constructor(options?: StorageClientOptions);
|
|
35
|
+
previewUrl(bucket: string, objectKey: string): string;
|
|
36
|
+
downloadUrl(bucket: string, objectKey: string): string;
|
|
37
|
+
uploadUser(filePath: string): Promise<UploadResult>;
|
|
38
|
+
uploadProject(filePath: string, options?: UploadProjectOptions): Promise<UploadResult>;
|
|
39
|
+
download(url: string, outputPath: string): Promise<string>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function previewUrl(
|
|
43
|
+
bucket: string,
|
|
44
|
+
objectKey: string,
|
|
45
|
+
options?: StorageClientOptions,
|
|
46
|
+
): string;
|
|
47
|
+
export function downloadUrl(
|
|
48
|
+
bucket: string,
|
|
49
|
+
objectKey: string,
|
|
50
|
+
options?: StorageClientOptions,
|
|
51
|
+
): string;
|
|
52
|
+
export function uploadUser(
|
|
53
|
+
filePath: string,
|
|
54
|
+
options?: StorageClientOptions,
|
|
55
|
+
): Promise<UploadResult>;
|
|
56
|
+
export function uploadProject(
|
|
57
|
+
filePath: string,
|
|
58
|
+
options?: UploadProjectOptions,
|
|
59
|
+
): Promise<UploadResult>;
|
|
60
|
+
export function download(
|
|
61
|
+
url: string,
|
|
62
|
+
outputPath: string,
|
|
63
|
+
options?: StorageClientOptions,
|
|
64
|
+
): Promise<string>;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export class PlatformSdkError extends Error {
|
|
5
|
+
constructor(message, options = {}) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = new.target.name;
|
|
8
|
+
this.status = options.status;
|
|
9
|
+
this.code = options.code;
|
|
10
|
+
this.details = options.details;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ConfigurationError extends PlatformSdkError {}
|
|
15
|
+
export class ApiError extends PlatformSdkError {}
|
|
16
|
+
|
|
17
|
+
function requireEnv(name) {
|
|
18
|
+
const value = process.env[name];
|
|
19
|
+
if (value) {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
throw new ConfigurationError(`Missing required environment variable: ${name}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeBaseUrl(baseUrl) {
|
|
26
|
+
return (baseUrl ?? requireEnv('LICOS_API_BASE_URL')).replace(/\/+$/, '');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveToken(token) {
|
|
30
|
+
return token ?? requireEnv('LICOS_USER_TOKEN');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function resolveUserId(userId) {
|
|
34
|
+
return userId ?? requireEnv('LICOS_USER_ID');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveProjectId(projectId) {
|
|
38
|
+
return projectId ?? requireEnv('LICOS_PROJECT_ID');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function encodeStorageSegment(value) {
|
|
42
|
+
return encodeURIComponent(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function encodeObjectKey(value) {
|
|
46
|
+
return value.split('/').map(encodeStorageSegment).join('/');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function guessContentType(filePath) {
|
|
50
|
+
switch (path.extname(filePath).toLowerCase()) {
|
|
51
|
+
case '.txt':
|
|
52
|
+
return 'text/plain';
|
|
53
|
+
case '.json':
|
|
54
|
+
return 'application/json';
|
|
55
|
+
case '.pdf':
|
|
56
|
+
return 'application/pdf';
|
|
57
|
+
case '.png':
|
|
58
|
+
return 'image/png';
|
|
59
|
+
case '.jpg':
|
|
60
|
+
case '.jpeg':
|
|
61
|
+
return 'image/jpeg';
|
|
62
|
+
default:
|
|
63
|
+
return 'application/octet-stream';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function readJsonEnvelope(response) {
|
|
68
|
+
let payload;
|
|
69
|
+
try {
|
|
70
|
+
payload = await response.json();
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new ApiError('Platform API returned non-JSON response', {
|
|
73
|
+
status: response.status,
|
|
74
|
+
details: error instanceof Error ? error.message : String(error),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!response.ok || payload?.code !== 0) {
|
|
79
|
+
throw new ApiError(payload?.message ?? `Platform API failed: HTTP ${response.status}`, {
|
|
80
|
+
status: response.status,
|
|
81
|
+
code: payload?.code,
|
|
82
|
+
details: payload?.data ?? payload,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return payload.data;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class StorageClient {
|
|
89
|
+
constructor(options = {}) {
|
|
90
|
+
this.baseUrl = options.baseUrl;
|
|
91
|
+
this.token = options.token;
|
|
92
|
+
this.userId = options.userId;
|
|
93
|
+
this.projectId = options.projectId;
|
|
94
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
previewUrl(bucket, objectKey) {
|
|
98
|
+
const baseUrl = normalizeBaseUrl(this.baseUrl);
|
|
99
|
+
return `${baseUrl}/api/v1/public/storage/${encodeStorageSegment(bucket)}/${encodeObjectKey(objectKey)}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
downloadUrl(bucket, objectKey) {
|
|
103
|
+
return `${this.previewUrl(bucket, objectKey)}?download=1`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async uploadUser(filePath) {
|
|
107
|
+
const baseUrl = normalizeBaseUrl(this.baseUrl);
|
|
108
|
+
return this.#upload(`${baseUrl}/api/v1/storage/upload/user`, filePath);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async uploadProject(filePath, options = {}) {
|
|
112
|
+
const baseUrl = normalizeBaseUrl(this.baseUrl);
|
|
113
|
+
const userId = resolveUserId(options.userId ?? this.userId);
|
|
114
|
+
const projectId = resolveProjectId(options.projectId ?? this.projectId);
|
|
115
|
+
return this.#upload(
|
|
116
|
+
`${baseUrl}/api/v1/storage/upload/project/${encodeStorageSegment(userId)}/${encodeStorageSegment(projectId)}`,
|
|
117
|
+
filePath,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async download(url, outputPath) {
|
|
122
|
+
const response = await this.fetchImpl(url, { method: 'GET' });
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
throw new ApiError(`Platform download failed: HTTP ${response.status}`, {
|
|
125
|
+
status: response.status,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
129
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
130
|
+
await fs.writeFile(outputPath, buffer);
|
|
131
|
+
return outputPath;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async #upload(url, filePath) {
|
|
135
|
+
const token = resolveToken(this.token);
|
|
136
|
+
const fileBuffer = await fs.readFile(filePath);
|
|
137
|
+
const fileName = path.basename(filePath);
|
|
138
|
+
const formData = new FormData();
|
|
139
|
+
formData.append(
|
|
140
|
+
'file',
|
|
141
|
+
new Blob([fileBuffer], { type: guessContentType(filePath) }),
|
|
142
|
+
fileName,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const response = await this.fetchImpl(url, {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
headers: {
|
|
148
|
+
Authorization: `Bearer ${token}`,
|
|
149
|
+
},
|
|
150
|
+
body: formData,
|
|
151
|
+
});
|
|
152
|
+
return readJsonEnvelope(response);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function defaultClient(options = {}) {
|
|
157
|
+
return new StorageClient(options);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function previewUrl(bucket, objectKey, options = {}) {
|
|
161
|
+
return defaultClient(options).previewUrl(bucket, objectKey);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function downloadUrl(bucket, objectKey, options = {}) {
|
|
165
|
+
return defaultClient(options).downloadUrl(bucket, objectKey);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function uploadUser(filePath, options = {}) {
|
|
169
|
+
return defaultClient(options).uploadUser(filePath);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export async function uploadProject(filePath, options = {}) {
|
|
173
|
+
return defaultClient(options).uploadProject(filePath, options);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function download(url, outputPath, options = {}) {
|
|
177
|
+
return defaultClient(options).download(url, outputPath);
|
|
178
|
+
}
|