@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
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
|
+
}
|