@js4cytoscape/ndex-client 0.5.0-alpha.9 → 0.6.0-alpha.2
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 +339 -13
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.d.mts +1906 -0
- package/dist/index.d.ts +1906 -0
- package/dist/index.global.js +9 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +80 -21
- package/src/CyNDEx.js.bak +178 -0
- package/src/NDEx.js +66 -13
- package/src/NDEx.js.bak +1107 -0
- package/src/index.ts +230 -0
- package/src/models/CX2Network.ts +423 -0
- package/src/services/AdminService.ts +10 -0
- package/src/services/CyNDExService.ts +338 -0
- package/src/services/FilesService.ts +234 -0
- package/src/services/HTTPService.ts +442 -0
- package/src/services/NetworkServiceV2.ts +427 -0
- package/src/services/NetworkServiceV3.ts +288 -0
- package/src/services/UnifiedNetworkService.ts +641 -0
- package/src/services/UserService.ts +233 -0
- package/src/services/WorkspaceService.ts +159 -0
- package/src/types/cytoscape.ts +81 -0
- package/src/types/index.ts +524 -0
- package/dist/ndexClient.common.js +0 -1057
- package/dist/ndexClient.js +0 -538
- package/src/index.js +0 -2
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
|
|
2
|
+
import {
|
|
3
|
+
NDExClientConfig,
|
|
4
|
+
APIResponse,
|
|
5
|
+
APIError,
|
|
6
|
+
BasicAuth,
|
|
7
|
+
OAuthAuth,
|
|
8
|
+
NDExError,
|
|
9
|
+
NDExNetworkError,
|
|
10
|
+
NDExAuthError,
|
|
11
|
+
NDExNotFoundError,
|
|
12
|
+
NDExValidationError,
|
|
13
|
+
NDExServerError
|
|
14
|
+
} from '../types';
|
|
15
|
+
import { version } from '../../package.json';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* HTTPService - Core HTTP client for NDEx API communication
|
|
19
|
+
* Handles v2/v3 endpoint routing, authentication, and error handling
|
|
20
|
+
*
|
|
21
|
+
* @note User-Agent header is set to 'NDEx-JS-Client/${version}' but only works in Node.js.
|
|
22
|
+
* Browsers will ignore custom User-Agent headers for security reasons.
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export class HTTPService {
|
|
26
|
+
private axiosInstance: AxiosInstance;
|
|
27
|
+
private config: NDExClientConfig;
|
|
28
|
+
|
|
29
|
+
constructor(config: NDExClientConfig = {}) {
|
|
30
|
+
this.config = {
|
|
31
|
+
baseURL: 'https://www.ndexbio.org',
|
|
32
|
+
timeout: 30000,
|
|
33
|
+
retries: 3,
|
|
34
|
+
retryDelay: 1000,
|
|
35
|
+
debug: false,
|
|
36
|
+
...config,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Create headers object
|
|
40
|
+
const headers: Record<string, string> = {
|
|
41
|
+
'Content-Type': 'application/json',
|
|
42
|
+
...this.config.headers,
|
|
43
|
+
...this.getAuthHeaders(),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Only set User-Agent in Node.js environments (not in browsers)
|
|
47
|
+
if (typeof window === 'undefined') {
|
|
48
|
+
headers['User-Agent'] = `NDEx-JS-Client/${version}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.axiosInstance = axios.create({
|
|
52
|
+
baseURL: this.config.baseURL,
|
|
53
|
+
timeout: this.config.timeout,
|
|
54
|
+
headers,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
this.setupInterceptors();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get authentication headers based on config.auth
|
|
62
|
+
*/
|
|
63
|
+
private getAuthHeaders(): Record<string, string> {
|
|
64
|
+
if (!this.config.auth) {
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (this.config.auth.type === 'basic') {
|
|
69
|
+
// Basic authentication: encode username:password in base64
|
|
70
|
+
const credentials = btoa(`${this.config.auth.username}:${this.config.auth.password}`);
|
|
71
|
+
return {
|
|
72
|
+
'Authorization': `Basic ${credentials}`,
|
|
73
|
+
};
|
|
74
|
+
} else if (this.config.auth.type === 'oauth') {
|
|
75
|
+
// OAuth Bearer token
|
|
76
|
+
return {
|
|
77
|
+
'Authorization': `Bearer ${this.config.auth.idToken}`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Build API endpoint URL with version routing
|
|
86
|
+
*/
|
|
87
|
+
private buildUrl(endpoint: string, version: 'v2' | 'v3' = 'v2'): string {
|
|
88
|
+
const cleanEndpoint = endpoint.startsWith('/') ? endpoint.slice(1) : endpoint;
|
|
89
|
+
return `/${version}/${cleanEndpoint}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Generic GET request with intelligent version routing
|
|
94
|
+
*/
|
|
95
|
+
async get<T = any>(
|
|
96
|
+
endpoint: string,
|
|
97
|
+
config?: AxiosRequestConfig & { version?: 'v2' | 'v3' }
|
|
98
|
+
): Promise<T> {
|
|
99
|
+
const { version, ...axiosConfig } = config || {};
|
|
100
|
+
const url = this.buildUrl(endpoint, version);
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const response = await this.axiosInstance.get<APIResponse<T>>(url, axiosConfig);
|
|
104
|
+
return this.handleResponse<T>(response);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
this.handleError(error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Generic POST request with intelligent version routing
|
|
112
|
+
*/
|
|
113
|
+
async post<T = any>(
|
|
114
|
+
endpoint: string,
|
|
115
|
+
data?: any,
|
|
116
|
+
config?: AxiosRequestConfig & { version?: 'v2' | 'v3' }
|
|
117
|
+
): Promise<T> {
|
|
118
|
+
const { version, ...axiosConfig } = config || {};
|
|
119
|
+
const url = this.buildUrl(endpoint, version);
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const response = await this.axiosInstance.post<APIResponse<T>>(url, data, axiosConfig);
|
|
123
|
+
return this.handleResponse<T>(response);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
this.handleError(error);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Generic PUT request with intelligent version routing
|
|
131
|
+
*/
|
|
132
|
+
async put<T = any>(
|
|
133
|
+
endpoint: string,
|
|
134
|
+
data?: any,
|
|
135
|
+
config?: AxiosRequestConfig & { version?: 'v2' | 'v3' }
|
|
136
|
+
): Promise<T> {
|
|
137
|
+
const { version, ...axiosConfig } = config || {};
|
|
138
|
+
const url = this.buildUrl(endpoint, version);
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const response = await this.axiosInstance.put<APIResponse<T>>(url, data, axiosConfig);
|
|
142
|
+
return this.handleResponse<T>(response);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
this.handleError(error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Generic DELETE request with intelligent version routing
|
|
150
|
+
*/
|
|
151
|
+
async delete<T = any>(
|
|
152
|
+
endpoint: string,
|
|
153
|
+
config?: AxiosRequestConfig & { version?: 'v2' | 'v3' }
|
|
154
|
+
): Promise<T> {
|
|
155
|
+
const { version, ...axiosConfig } = config || {};
|
|
156
|
+
const url = this.buildUrl(endpoint, version);
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const response = await this.axiosInstance.delete<APIResponse<T>>(url, axiosConfig);
|
|
160
|
+
return this.handleResponse<T>(response);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
this.handleError(error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
/*
|
|
168
|
+
* Upload a file to the NDEx API with progress tracking support
|
|
169
|
+
*
|
|
170
|
+
* This method handles file uploads using multipart/form-data encoding and supports
|
|
171
|
+
* various input types for maximum flexibility across different environments.
|
|
172
|
+
*
|
|
173
|
+
* @template T - The expected response type from the API
|
|
174
|
+
* @param endpoint - API endpoint path (e.g., 'networks' for network upload)
|
|
175
|
+
* @param file - File data to upload. Supports multiple input types:
|
|
176
|
+
* - `File` (browser): HTML5 File object from file input or drag-and-drop
|
|
177
|
+
* - `Blob` (browser): Binary data as Blob object
|
|
178
|
+
* - `Buffer` (Node.js): Binary data as Buffer for server-side uploads
|
|
179
|
+
* - `string`: Text content that will be converted to Blob (useful for CX/CX2 JSON)
|
|
180
|
+
* @param options - Upload configuration options
|
|
181
|
+
*
|
|
182
|
+
* @param options.contentType - MIME type for string content (default: 'application/json')
|
|
183
|
+
* @param options.onProgress - Progress callback that receives percentage (0-100)
|
|
184
|
+
* Called periodically during upload with current progress
|
|
185
|
+
* @param options.version - API version to use ('v2' or 'v3', defaults to 'v2')
|
|
186
|
+
*
|
|
187
|
+
* @returns Promise resolving to upload result
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* // Upload a File object from file input (browser)
|
|
192
|
+
* const fileInput = document.getElementById('file') as HTMLInputElement;
|
|
193
|
+
* const file = fileInput.files[0];
|
|
194
|
+
* const result = await httpService.uploadFile('networks', file, {
|
|
195
|
+
* filename: 'my-network.cx2',
|
|
196
|
+
* onProgress: (progress) => console.log(`Upload: ${progress}%`),
|
|
197
|
+
* version: 'v3'
|
|
198
|
+
* });
|
|
199
|
+
*
|
|
200
|
+
* // Upload CX2 network data as string
|
|
201
|
+
* const cx2Data = JSON.stringify({ CXVersion: '2.0', networks: [...] });
|
|
202
|
+
* const result = await httpService.uploadFile('networks', cx2Data, {
|
|
203
|
+
* filename: 'network.cx2',
|
|
204
|
+
* contentType: 'application/json',
|
|
205
|
+
* version: 'v3'
|
|
206
|
+
* });
|
|
207
|
+
*
|
|
208
|
+
* // Upload with progress tracking
|
|
209
|
+
* const result = await httpService.uploadFile('networks', file, {
|
|
210
|
+
* onProgress: (progress) => {
|
|
211
|
+
* progressBar.style.width = `${progress}%`;
|
|
212
|
+
* console.log(`Uploading: ${progress}%`);
|
|
213
|
+
* }
|
|
214
|
+
* });
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* @throws Will throw NDExError on network errors,
|
|
218
|
+
* server errors, or invalid file data
|
|
219
|
+
*
|
|
220
|
+
* @note The Content-Type header is automatically set to 'multipart/form-data'
|
|
221
|
+
* and Axios will handle the boundary parameter automatically
|
|
222
|
+
*/
|
|
223
|
+
async uploadFile<T = any>(
|
|
224
|
+
endpoint: string,
|
|
225
|
+
file: File | Blob | Buffer | string,
|
|
226
|
+
options: {
|
|
227
|
+
contentType?: string;
|
|
228
|
+
onProgress?: (progress: number) => void;
|
|
229
|
+
version?: 'v2' | 'v3';
|
|
230
|
+
} = {}
|
|
231
|
+
): Promise<T> {
|
|
232
|
+
const { version, onProgress, contentType, ...config } = options;
|
|
233
|
+
const url = this.buildUrl(endpoint, version);
|
|
234
|
+
|
|
235
|
+
const formData = new FormData();
|
|
236
|
+
|
|
237
|
+
if (file instanceof File) {
|
|
238
|
+
formData.append('file', file, file.name);
|
|
239
|
+
} else if (file instanceof Blob) {
|
|
240
|
+
formData.append('file', file, 'file.cx2');
|
|
241
|
+
} else if (typeof file === 'string') {
|
|
242
|
+
const blob = new Blob([file], { type: contentType || 'application/json' });
|
|
243
|
+
formData.append('file', blob,'file.cx2');
|
|
244
|
+
} else {
|
|
245
|
+
// Buffer (Node.js environment)
|
|
246
|
+
formData.append('file', file as any, 'file.cx2');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
const response = await this.axiosInstance.post<APIResponse<T>>(url, formData, {
|
|
251
|
+
headers: {
|
|
252
|
+
'Content-Type': 'multipart/form-data',
|
|
253
|
+
},
|
|
254
|
+
onUploadProgress: onProgress ? (progressEvent) => {
|
|
255
|
+
const progress = Math.round((progressEvent.loaded * 100) / (progressEvent.total || 1));
|
|
256
|
+
onProgress(progress);
|
|
257
|
+
} : undefined,
|
|
258
|
+
...config,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
return this.handleResponse<T>(response);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
this.handleError(error);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Setup axios interceptors for debugging and retry logic
|
|
269
|
+
*/
|
|
270
|
+
private setupInterceptors(): void {
|
|
271
|
+
// Request interceptor for debugging
|
|
272
|
+
this.axiosInstance.interceptors.request.use(
|
|
273
|
+
(config) => {
|
|
274
|
+
if (this.config.debug) {
|
|
275
|
+
console.log('NDEx API Request:', {
|
|
276
|
+
method: config.method?.toUpperCase(),
|
|
277
|
+
url: config.url,
|
|
278
|
+
headers: config.headers,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
return config;
|
|
282
|
+
},
|
|
283
|
+
(error) => {
|
|
284
|
+
if (this.config.debug) {
|
|
285
|
+
console.error('NDEx API Request Error:', error);
|
|
286
|
+
}
|
|
287
|
+
return Promise.reject(error);
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
// Response interceptor for debugging
|
|
292
|
+
this.axiosInstance.interceptors.response.use(
|
|
293
|
+
(response) => {
|
|
294
|
+
if (this.config.debug) {
|
|
295
|
+
console.log('NDEx API Response:', {
|
|
296
|
+
status: response.status,
|
|
297
|
+
url: response.config.url,
|
|
298
|
+
data: response.data,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return response;
|
|
302
|
+
},
|
|
303
|
+
(error) => {
|
|
304
|
+
if (this.config.debug) {
|
|
305
|
+
console.error('NDEx API Response Error:', error.response?.data || error.message);
|
|
306
|
+
}
|
|
307
|
+
return Promise.reject(error);
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Handle successful API responses
|
|
314
|
+
*/
|
|
315
|
+
private handleResponse<T>(response: AxiosResponse<any>): T {
|
|
316
|
+
// Handle different response formats from NDEx API
|
|
317
|
+
if (response.data && typeof response.data === 'object') {
|
|
318
|
+
if ('errorCode' in response.data) {
|
|
319
|
+
// NDEx error format - throw appropriate error
|
|
320
|
+
this.throwNDExError(
|
|
321
|
+
response.data.message || 'Unknown error occurred',
|
|
322
|
+
response.status,
|
|
323
|
+
response.data.errorCode || 'UNKNOWN_ERROR',
|
|
324
|
+
response.data.description
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Handle wrapped vs direct responses
|
|
330
|
+
const data = response.data && typeof response.data === 'object' && 'data' in response.data
|
|
331
|
+
? response.data.data
|
|
332
|
+
: response.data;
|
|
333
|
+
|
|
334
|
+
return data as T;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Handle API errors by throwing appropriate NDEx error types
|
|
339
|
+
*/
|
|
340
|
+
private handleError(error: any): never {
|
|
341
|
+
if (error.response) {
|
|
342
|
+
// Server responded with error status
|
|
343
|
+
const message = error.response.data?.message || error.message;
|
|
344
|
+
const errorCode = error.response.data?.errorCode || `HTTP_${error.response.status}`;
|
|
345
|
+
const description = error.response.data?.description;
|
|
346
|
+
|
|
347
|
+
this.throwNDExError(message, error.response.status, errorCode, description);
|
|
348
|
+
} else if (error.request) {
|
|
349
|
+
// Request made but no response received
|
|
350
|
+
throw new NDExNetworkError('Network error - no response received', error);
|
|
351
|
+
} else {
|
|
352
|
+
// Something else happened
|
|
353
|
+
throw new NDExError(error.message || 'Unknown error occurred', undefined, 'CLIENT_ERROR', error.toString());
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Throw appropriate NDEx error based on status code
|
|
359
|
+
*/
|
|
360
|
+
private throwNDExError(message: string, statusCode: number, errorCode: string, description?: string): never {
|
|
361
|
+
switch (statusCode) {
|
|
362
|
+
case 400:
|
|
363
|
+
throw new NDExValidationError(message, statusCode);
|
|
364
|
+
case 401:
|
|
365
|
+
case 403:
|
|
366
|
+
throw new NDExAuthError(message, statusCode);
|
|
367
|
+
case 404:
|
|
368
|
+
throw new NDExNotFoundError(message, statusCode);
|
|
369
|
+
case 500:
|
|
370
|
+
case 502:
|
|
371
|
+
case 503:
|
|
372
|
+
case 504:
|
|
373
|
+
throw new NDExServerError(message, statusCode);
|
|
374
|
+
default:
|
|
375
|
+
throw new NDExError(message, statusCode, errorCode, description);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Update client configuration
|
|
381
|
+
*/
|
|
382
|
+
updateConfig(newConfig: Partial<NDExClientConfig>): void {
|
|
383
|
+
this.config = { ...this.config, ...newConfig };
|
|
384
|
+
|
|
385
|
+
if (newConfig.baseURL) {
|
|
386
|
+
this.axiosInstance.defaults.baseURL = newConfig.baseURL;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (newConfig.timeout) {
|
|
390
|
+
this.axiosInstance.defaults.timeout = newConfig.timeout;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (newConfig.headers) {
|
|
394
|
+
Object.assign(this.axiosInstance.defaults.headers, newConfig.headers);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Update auth headers if auth config changed
|
|
398
|
+
if ('auth' in newConfig) {
|
|
399
|
+
const authHeaders = this.getAuthHeaders();
|
|
400
|
+
|
|
401
|
+
// Clear existing authorization header first
|
|
402
|
+
delete this.axiosInstance.defaults.headers.common['Authorization'];
|
|
403
|
+
|
|
404
|
+
// Set new auth headers
|
|
405
|
+
Object.assign(this.axiosInstance.defaults.headers.common, authHeaders);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Get current configuration
|
|
411
|
+
*/
|
|
412
|
+
getConfig(): NDExClientConfig {
|
|
413
|
+
return { ...this.config };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Get current authentication type
|
|
418
|
+
* @returns The type of authentication configured ('basic' | 'oauth'), or undefined if no auth is configured
|
|
419
|
+
*/
|
|
420
|
+
getAuthType(): 'basic' | 'oauth' | undefined {
|
|
421
|
+
return this.config.auth?.type;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Get ID token from OAuth authentication
|
|
426
|
+
* @returns The ID token if OAuth auth is configured, undefined otherwise
|
|
427
|
+
*/
|
|
428
|
+
getIdToken(): string | undefined {
|
|
429
|
+
return this.config.auth?.type === 'oauth' ? this.config.auth.idToken : undefined;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Set ID token for OAuth authentication
|
|
434
|
+
* Creates OAuth auth configuration if no auth is currently configured
|
|
435
|
+
* @param idToken - The ID token to set
|
|
436
|
+
*/
|
|
437
|
+
setIdToken(idToken: string): void {
|
|
438
|
+
this.updateConfig({
|
|
439
|
+
auth: { type: 'oauth', idToken }
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|