@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/src/index.ts ADDED
@@ -0,0 +1,230 @@
1
+ /**
2
+ * NDEx Client Library - Modern TypeScript Implementation
3
+ *
4
+ * A comprehensive, modern TypeScript client for the NDEx (Network Data Exchange) API.
5
+ * Supports both v2 and v3 APIs with intelligent version routing, native CX2 format support,
6
+ * and provides a clean, type-safe interface for network operations.
7
+ */
8
+
9
+ // Core Types
10
+ export * from './types';
11
+
12
+ // Models
13
+ export { CX2Network } from './models/CX2Network';
14
+
15
+ // Core Services
16
+ export { NetworkServiceV2 } from './services/NetworkServiceV2';
17
+ export { NetworkServiceV3 } from './services/NetworkServiceV3';
18
+ export { UnifiedNetworkService } from './services/UnifiedNetworkService';
19
+ export { FilesService } from './services/FilesService';
20
+ export { WorkspaceService } from './services/WorkspaceService';
21
+ export { UserService } from './services/UserService';
22
+ export { AdminService } from './services/AdminService';
23
+ export { CyNDExService } from './services/CyNDExService';
24
+
25
+ // Backward compatibility alias
26
+ export { CyNDExService as CyNDEx } from './services/CyNDExService';
27
+
28
+ // Legacy compatibility exports - temporarily commented out for test setup
29
+ // TODO: Fix ES module compatibility for NDEx.js and CyNDEx.js
30
+ // export { default as NDEx } from './NDEx.js';
31
+ // export { default as CyNDEx } from './CyNDEx.js';
32
+
33
+ // Main Client Class
34
+ import { HTTPService } from './services/HTTPService';
35
+ import { UnifiedNetworkService } from './services/UnifiedNetworkService';
36
+ import { FilesService } from './services/FilesService';
37
+ import { WorkspaceService } from './services/WorkspaceService';
38
+ import { UserService } from './services/UserService';
39
+ import { AdminService } from './services/AdminService';
40
+ import { NDExClientConfig, BasicAuth, OAuthAuth } from './types';
41
+
42
+ /**
43
+ * NDExClient - Main client class for NDEx API operations
44
+ *
45
+ * Provides a unified interface for all NDEx operations with intelligent
46
+ * version routing between v2 and v3 APIs.
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * // Basic usage
51
+ * const client = new NDExClient({
52
+ * baseURL: 'https://www.ndexbio.org',
53
+ * debug: true
54
+ * });
55
+ *
56
+ * // Authenticate
57
+ * await client.user.authenticate({ username: 'user', password: 'pass' });
58
+ *
59
+ * // Search networks
60
+ * const results = await client.networks.searchNetworks({ searchString: 'pathway' });
61
+ *
62
+ * // Get network as CX2
63
+ * const network = await client.networks.getNetworkAsCX2Object('network-uuid');
64
+ *
65
+ * // User operations
66
+ * const profile = await client.user.getCurrentUser();
67
+ *
68
+ * // Admin operations (requires admin privileges)
69
+ * const stats = await client.admin.getSystemStats();
70
+ * ```
71
+ */
72
+ export class NDExClient {
73
+ private httpService: HTTPService;
74
+
75
+ /** Network operations with intelligent v2/v3 routing */
76
+ public readonly networks: UnifiedNetworkService;
77
+
78
+ /** File upload/download and task management */
79
+ public readonly files: FilesService;
80
+
81
+ /** Workspace operations (deprecated - most functionality moved to other services) */
82
+ public readonly workspace: WorkspaceService;
83
+
84
+ /** User authentication and management */
85
+ public readonly user: UserService;
86
+
87
+ /** System administration operations */
88
+ public readonly admin: AdminService;
89
+
90
+ constructor(config: NDExClientConfig = {}) {
91
+ this.httpService = new HTTPService(config);
92
+ this.networks = new UnifiedNetworkService(this.httpService);
93
+ this.files = new FilesService(this.httpService);
94
+ this.workspace = new WorkspaceService(this.httpService);
95
+ this.user = new UserService(this.httpService);
96
+ this.admin = new AdminService(this.httpService);
97
+ }
98
+
99
+
100
+ /**
101
+ * Clear authentication
102
+ */
103
+ logout(): void {
104
+ this.httpService.updateConfig({ auth: undefined });
105
+ }
106
+
107
+ /**
108
+ * Get server status information
109
+ * Can be called by authenticated or anonymous users.
110
+ * @param format - Optional format parameter. Use 'full' for detailed information including properties and importers/exporters
111
+ * @returns Server status including network/user counts and optional server details
112
+ */
113
+ async getServerStatus(format?: 'full'): Promise<{
114
+ message: string;
115
+ properties?: {
116
+ ServerVersion: string;
117
+ Build: string;
118
+ ServerResultLimit: string;
119
+ ImporterExporters: Array<{
120
+ importer: boolean;
121
+ exporter: boolean;
122
+ fileExtension: string;
123
+ name: string;
124
+ description: string;
125
+ }>;
126
+ };
127
+ networkCount: number;
128
+ userCount: number;
129
+ groupCount?: number;
130
+ }> {
131
+ const params = new URLSearchParams();
132
+ if (format) {
133
+ params.append('format', format);
134
+ }
135
+
136
+ const endpoint = `admin/status${params.toString() ? `?${params.toString()}` : ''}`;
137
+ return this.httpService.get(endpoint);
138
+ }
139
+
140
+ /**
141
+ * Check if client has valid authentication information configured
142
+ * @returns true if auth is a valid BasicAuth or OAuthAuth object, false otherwise
143
+ */
144
+ hasAuthInfo(): boolean {
145
+ const auth = this.httpService.getConfig().auth;
146
+ if (!auth) return false;
147
+
148
+ // Check for BasicAuth structure
149
+ if (auth.type === 'basic') {
150
+ return !!(auth as BasicAuth).username && !!(auth as BasicAuth).password;
151
+ }
152
+
153
+ // Check for OAuthAuth structure
154
+ if (auth.type === 'oauth') {
155
+ return !!(auth as OAuthAuth).idToken;
156
+ }
157
+
158
+ return false;
159
+ }
160
+
161
+ /**
162
+ * Update client configuration
163
+ */
164
+ updateConfig(config: Partial<NDExClientConfig>): void {
165
+ this.httpService.updateConfig(config);
166
+ }
167
+
168
+ /**
169
+ * Get current client configuration
170
+ */
171
+ getConfig(): NDExClientConfig {
172
+ return this.httpService.getConfig();
173
+ }
174
+
175
+
176
+ /**
177
+ * Access to low-level HTTP service for advanced usage
178
+ */
179
+ get http(): HTTPService {
180
+ return this.httpService;
181
+ }
182
+
183
+ /**
184
+ * Access to version-specific network services for advanced usage
185
+ */
186
+ get v2() {
187
+ return {
188
+ networks: this.networks.v2,
189
+ };
190
+ }
191
+
192
+ get v3() {
193
+ return {
194
+ networks: this.networks.v3,
195
+ };
196
+ }
197
+
198
+ }
199
+
200
+ // Main export - New modern client (named export for better tree-shaking)
201
+ // Users should import as: import { NDExClient } from '@js4cytoscape/ndex-client'
202
+
203
+ // Version information - imported from package.json to ensure consistency
204
+ import { version as packageVersion } from '../package.json';
205
+
206
+ /**
207
+ * Library version string imported from package.json
208
+ *
209
+ * Provides programmatic access to the current library version for:
210
+ * - Runtime version checking and validation
211
+ * - Debugging and error reporting
212
+ * - Logging and telemetry
213
+ * - Version-dependent feature detection
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * import { NDExClient, version } from '@js4cytoscape/ndex-client';
218
+ *
219
+ * console.log(`Using NDEx Client v${version}`);
220
+ *
221
+ * // In error reports
222
+ * const errorReport = {
223
+ * error: err.message,
224
+ * libraryVersion: version,
225
+ * timestamp: new Date().toISOString()
226
+ * };
227
+ * ```
228
+ */
229
+ export const version = packageVersion;
230
+
@@ -0,0 +1,423 @@
1
+ import {
2
+ CX2Network as CX2NetworkType,
3
+ CX2MetaData,
4
+ CX2Node,
5
+ CX2Edge,
6
+ CX2AttributeDeclarations,
7
+ CX2NetworkAttribute,
8
+ CX2NodeBypass,
9
+ CX2EdgeBypass,
10
+ CX2VisualProperty,
11
+ NetworkSummaryV3
12
+ } from '../types';
13
+
14
+ /**
15
+ * CX2Network - Modern representation of CX2 format networks
16
+ * Provides utilities for creating, manipulating, and validating CX2 networks
17
+ */
18
+ export class CX2Network implements CX2NetworkType {
19
+ CXVersion: string = '2.0';
20
+ hasFragments: boolean = false;
21
+ metaData: CX2MetaData[] = [];
22
+ attributeDeclarations?: CX2AttributeDeclarations;
23
+ networkAttributes?: CX2NetworkAttribute[];
24
+ nodes?: CX2Node[];
25
+ edges?: CX2Edge[];
26
+ nodeBypass?: CX2NodeBypass[];
27
+ edgeBypass?: CX2EdgeBypass[];
28
+ visualProperties?: CX2VisualProperty;
29
+ status?: any[];
30
+
31
+ constructor(data?: Partial<CX2NetworkType>) {
32
+ if (data) {
33
+ Object.assign(this, data);
34
+ }
35
+
36
+ // Ensure required metadata is present
37
+ this.ensureRequiredMetadata();
38
+ }
39
+
40
+ /**
41
+ * Create a new empty CX2Network
42
+ */
43
+ static createEmpty(): CX2Network {
44
+ const network = new CX2Network();
45
+ network.nodes = [];
46
+ network.edges = [];
47
+ network.metaData = [
48
+ {
49
+ name: 'nodes',
50
+ elementCount: 0
51
+ },
52
+ {
53
+ name: 'edges',
54
+ elementCount: 0
55
+ },
56
+ ];
57
+ return network;
58
+ }
59
+
60
+ /**
61
+ * Create CX2Network from raw JSON data
62
+ */
63
+ static fromJSON(json: string | object): CX2Network {
64
+ const data = typeof json === 'string' ? JSON.parse(json) : json;
65
+ return new CX2Network(data);
66
+ }
67
+
68
+ /**
69
+ * Create CX2Network from NetworkSummary (for compatibility)
70
+ */
71
+ static fromNetworkSummary(summary: NetworkSummaryV3): CX2Network {
72
+ const network = CX2Network.createEmpty();
73
+
74
+ network.networkAttributes = [
75
+ { name: summary.name },
76
+ ...(summary.description ? [{ description: summary.description }] : []),
77
+ ...(summary.version ? [{ version: summary.version }] : []),
78
+ ];
79
+
80
+ // Add properties as network attributes
81
+ if (summary.properties) {
82
+ Object.entries(summary.properties).forEach(([key, value]) => {
83
+ network.networkAttributes?.push({
84
+ [key]: (value as { t: string; v: any }).v
85
+ });
86
+ });
87
+ }
88
+
89
+ return network;
90
+ }
91
+
92
+ /**
93
+ * Add a node to the network
94
+ */
95
+ addNode(id: number, attributes?: Record<string, any>): CX2Node {
96
+ if (!this.nodes) {
97
+ this.nodes = [];
98
+ }
99
+
100
+ const node: CX2Node = { id };
101
+ if (attributes) {
102
+ node.v = attributes;
103
+ }
104
+
105
+ this.nodes.push(node);
106
+ this.updateNodeCount();
107
+ return node;
108
+ }
109
+
110
+ /**
111
+ * Add an edge to the network
112
+ */
113
+ addEdge(id: number, sourceId: number, targetId: number, attributes?: Record<string, any>): CX2Edge {
114
+ if (!this.edges) {
115
+ this.edges = [];
116
+ }
117
+
118
+ const edge: CX2Edge = {
119
+ id,
120
+ s: sourceId,
121
+ t: targetId,
122
+ };
123
+
124
+ if (attributes) {
125
+ edge.v = attributes;
126
+ }
127
+
128
+ this.edges.push(edge);
129
+ this.updateEdgeCount();
130
+ return edge;
131
+ }
132
+
133
+ /**
134
+ * Add an individual node attribute
135
+ */
136
+ addNodeAttribute(nodeId: number, attributeName: string, value: any): void {
137
+
138
+ //find the nodes with the given ID
139
+ const node = this.nodes.find(n => n.id === nodeId);
140
+ //set the node attribute
141
+ if (node) {
142
+ if (!node.v) {
143
+ node.v = {};
144
+ }
145
+ node.v[attributeName] = value;
146
+ }
147
+
148
+ }
149
+
150
+ /**
151
+ * Add edge attributes (bulk)
152
+ */
153
+ addEdgeAttribute(edgeId: number, attributeName: string, value: any): void {
154
+ //similar to addNodeAttribute
155
+ const edge = this.edges.find(e => e.id === edgeId);
156
+ if (edge) {
157
+ if (!edge.v) {
158
+ edge.v = {};
159
+ }
160
+ edge.v[attributeName] = value;
161
+ }
162
+
163
+
164
+ }
165
+
166
+ /**
167
+ * Set node coordinates
168
+ */
169
+ setNodeCoordinates(nodeId: number, x: number, y: number, z?: number): void {
170
+ if (!this.nodes) {
171
+ this.nodes = [];
172
+ }
173
+
174
+ // Find the node and update its coordinates directly
175
+ let node = this.nodes.find(n => n.id === nodeId);
176
+
177
+ if (!node) {
178
+ // Create new node if it doesn't exist
179
+ node = { id: nodeId };
180
+ this.nodes.push(node);
181
+ this.updateNodeCount();
182
+ }
183
+
184
+ // Set coordinates directly on the node
185
+ node.x = x;
186
+ node.y = y;
187
+ if (z !== undefined) {
188
+ node.z = z;
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Get node by ID
194
+ */
195
+ getNode(id: number): CX2Node | undefined {
196
+ return this.nodes?.find(node => node.id === id);
197
+ }
198
+
199
+ /**
200
+ * Get edge by ID
201
+ */
202
+ getEdge(id: number): CX2Edge | undefined {
203
+ return this.edges?.find(edge => edge.id === id);
204
+ }
205
+
206
+ /**
207
+ * Get all nodes
208
+ */
209
+ getNodes(): CX2Node[] {
210
+ return this.nodes || [];
211
+ }
212
+
213
+ /**
214
+ * Get all edges
215
+ */
216
+ getEdges(): CX2Edge[] {
217
+ return this.edges || [];
218
+ }
219
+
220
+ /**
221
+ * Get node count
222
+ */
223
+ getNodeCount(): number {
224
+ return this.nodes?.length || 0;
225
+ }
226
+
227
+ /**
228
+ * Get edge count
229
+ */
230
+ getEdgeCount(): number {
231
+ return this.edges?.length || 0;
232
+ }
233
+
234
+ /**
235
+ * Get network name from attributes
236
+ */
237
+ getNetworkName(): string | undefined {
238
+ return this.networkAttributes?.find(attr => 'name' in attr)?.name;
239
+ }
240
+
241
+ /**
242
+ * Set network name
243
+ */
244
+ setNetworkName(name: string): void {
245
+ if (!this.networkAttributes) {
246
+ this.networkAttributes = [];
247
+ }
248
+
249
+ // Remove existing name attribute
250
+ this.networkAttributes = this.networkAttributes.filter(attr => !('name' in attr));
251
+
252
+ // Add new name
253
+ this.networkAttributes.push({ name });
254
+ }
255
+
256
+ /**
257
+ * Get network attribute by key
258
+ */
259
+ getNetworkAttribute(key: string): any {
260
+ return this.networkAttributes?.find(attr => key in attr)?.[key];
261
+ }
262
+
263
+ /**
264
+ * Set network attribute
265
+ */
266
+ setNetworkAttribute(key: string, value: any): void {
267
+ if (!this.networkAttributes) {
268
+ this.networkAttributes = [];
269
+ }
270
+
271
+ // Remove existing attribute with same key
272
+ this.networkAttributes = this.networkAttributes.filter(attr => !(key in attr));
273
+
274
+ // Add new attribute
275
+ this.networkAttributes.push({ [key]: value });
276
+ }
277
+
278
+ /**
279
+ * Validate the CX2 network structure
280
+ */
281
+ validate(): { isValid: boolean; errors: string[] } {
282
+ const errors: string[] = [];
283
+
284
+ // Check required fields
285
+ if (!this.CXVersion) {
286
+ errors.push('CXVersion is required');
287
+ }
288
+
289
+ if (this.hasFragments === undefined) {
290
+ errors.push('hasFragments is required');
291
+ }
292
+
293
+ if (!this.metaData || this.metaData.length === 0) {
294
+ errors.push('metaData is required and must not be empty');
295
+ }
296
+
297
+ // Validate metadata
298
+ this.metaData?.forEach((meta, index) => {
299
+ if (!meta.name) {
300
+ errors.push(`metaData[${index}] is missing required 'name' field`);
301
+ }
302
+ });
303
+
304
+ // Validate nodes
305
+ if (this.nodes) {
306
+ this.nodes.forEach((node, index) => {
307
+ if (node.id === undefined || node.id === null) {
308
+ errors.push(`nodes[${index}] is missing required 'id' field`);
309
+ }
310
+ });
311
+ }
312
+
313
+ // Validate edges
314
+ if (this.edges) {
315
+ this.edges.forEach((edge, index) => {
316
+ if (edge.id === undefined || edge.id === null) {
317
+ errors.push(`edges[${index}] is missing required 'id' field`);
318
+ }
319
+ if (edge.s === undefined || edge.s === null) {
320
+ errors.push(`edges[${index}] is missing required 's' (source) field`);
321
+ }
322
+ if (edge.t === undefined || edge.t === null) {
323
+ errors.push(`edges[${index}] is missing required 't' (target) field`);
324
+ }
325
+
326
+ // Check if source and target nodes exist
327
+ if (this.nodes && edge.s !== undefined && edge.t !== undefined) {
328
+ const sourceExists = this.nodes.some(node => node.id === edge.s);
329
+ const targetExists = this.nodes.some(node => node.id === edge.t);
330
+
331
+ if (!sourceExists) {
332
+ errors.push(`edges[${index}] references non-existent source node ${edge.s}`);
333
+ }
334
+ if (!targetExists) {
335
+ errors.push(`edges[${index}] references non-existent target node ${edge.t}`);
336
+ }
337
+ }
338
+ });
339
+ }
340
+
341
+ return {
342
+ isValid: errors.length === 0,
343
+ errors,
344
+ };
345
+ }
346
+
347
+ /**
348
+ * Convert to JSON string
349
+ */
350
+ toJSON(): string {
351
+ return JSON.stringify(this, null, 2);
352
+ }
353
+
354
+ /**
355
+ * Create a deep copy of the network
356
+ */
357
+ clone(): CX2Network {
358
+ return new CX2Network(JSON.parse(this.toJSON()));
359
+ }
360
+
361
+ /**
362
+ * Get basic statistics about the network
363
+ */
364
+ getStatistics() {
365
+ return {
366
+ nodeCount: this.getNodeCount(),
367
+ edgeCount: this.getEdgeCount(),
368
+ hasCoordinates: Boolean(this.nodes && this.nodes.some(node =>
369
+ node.x !== undefined || node.y !== undefined || node.z !== undefined
370
+ )),
371
+ hasVisualProperties: Boolean(this.visualProperties && (
372
+ this.visualProperties.default ||
373
+ this.visualProperties.edgeMapping ||
374
+ this.visualProperties.nodeMapping
375
+ ))
376
+ };
377
+ }
378
+
379
+ /**
380
+ * Ensure required metadata is present
381
+ */
382
+ private ensureRequiredMetadata(): void {
383
+ if (!this.metaData) {
384
+ this.metaData = [];
385
+ }
386
+
387
+ // Ensure nodes metadata exists
388
+ if (this.nodes && !this.metaData.find(m => m.name === 'nodes')) {
389
+ this.metaData.push({
390
+ name: 'nodes',
391
+ elementCount: this.nodes.length
392
+ });
393
+ }
394
+
395
+ // Ensure edges metadata exists
396
+ if (this.edges && !this.metaData.find(m => m.name === 'edges')) {
397
+ this.metaData.push({
398
+ name: 'edges',
399
+ elementCount: this.edges.length
400
+ });
401
+ }
402
+ }
403
+
404
+ /**
405
+ * Update node count in metadata
406
+ */
407
+ private updateNodeCount(): void {
408
+ const nodesMeta = this.metaData?.find(m => m.name === 'nodes');
409
+ if (nodesMeta) {
410
+ nodesMeta.elementCount = this.nodes?.length || 0;
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Update edge count in metadata
416
+ */
417
+ private updateEdgeCount(): void {
418
+ const edgesMeta = this.metaData?.find(m => m.name === 'edges');
419
+ if (edgesMeta) {
420
+ edgesMeta.elementCount = this.edges?.length || 0;
421
+ }
422
+ }
423
+ }
@@ -0,0 +1,10 @@
1
+ import { HTTPService } from './HTTPService';
2
+
3
+ /**
4
+ * AdminService - NDEx admin operations
5
+ * Handles system administration and user management for admin users
6
+ */
7
+ export class AdminService {
8
+ constructor(private http: HTTPService) {}
9
+
10
+ }