@jax-data-science/api-clients 0.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.
@@ -0,0 +1,911 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Injectable, inject, Inject, NgModule } from '@angular/core';
3
+ import { throwError, map, catchError, Observable, BehaviorSubject, combineLatest, tap } from 'rxjs';
4
+ import { fetchEventSource } from '@microsoft/fetch-event-source';
5
+ import * as i1 from '@angular/common/http';
6
+ import { CommonModule } from '@angular/common';
7
+
8
+ class ApiBaseServiceFactory {
9
+ http;
10
+ constructor(http) {
11
+ this.http = http;
12
+ }
13
+ create(baseUrl) {
14
+ return new ApiBaseService(this.http, baseUrl);
15
+ }
16
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: ApiBaseServiceFactory, deps: [{ token: i1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
17
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: ApiBaseServiceFactory, providedIn: 'root' });
18
+ }
19
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: ApiBaseServiceFactory, decorators: [{
20
+ type: Injectable,
21
+ args: [{
22
+ providedIn: 'root'
23
+ }]
24
+ }], ctorParameters: () => [{ type: i1.HttpClient }] });
25
+ class ApiBaseService {
26
+ http;
27
+ baseUrl;
28
+ constructor(http, baseUrl) {
29
+ this.http = http;
30
+ this.baseUrl = baseUrl;
31
+ }
32
+ /**
33
+ * Handles API response errors and HTTP errors
34
+ * @param response The API response
35
+ * @private
36
+ */
37
+ handleResponse(response) {
38
+ if (response.errors) {
39
+ // TO-DO: [GIK 6/6/2025] once the API service has the capabilities to respond
40
+ // with meaningful validation and analytical errors, this will need to be
41
+ // updated to typecast these into the ErrorResponse model and rethrow the error object
42
+ throw new Error(response.errors.join(', '));
43
+ }
44
+ return response;
45
+ }
46
+ /**
47
+ * Handles HTTP errors by catching them and rethrowing a consistent
48
+ * error response that contains an error code, numeric code, and
49
+ * a user-friendly message.
50
+ * TO-DO: [GIK 6/10/2025] consider adding error logging capabilities to an external service
51
+ * @param error an error object, typically an HttpErrorResponse
52
+ * @returns an Observable that emits an ErrorResponse object
53
+ */
54
+ handleHttpError(error) {
55
+ let errorResponse;
56
+ // errors returned on the Observable response stream
57
+ // will be wrapped in an HttpErrorResponse class
58
+ if (error.name === 'HttpErrorResponse') {
59
+ errorResponse = {
60
+ code: error.statusText || 'HTTP_ERROR',
61
+ num_code: error.status,
62
+ message: this.getErrorMessage(error),
63
+ };
64
+ }
65
+ else {
66
+ errorResponse = {
67
+ code: 'UNKNOWN_ERROR',
68
+ num_code: 0,
69
+ message: error.message || 'An unknown error occurred',
70
+ };
71
+ }
72
+ return throwError(() => errorResponse);
73
+ }
74
+ /**
75
+ * Get a user-friendly error message based on the HTTP status code
76
+ *
77
+ * @param error
78
+ * @return a string containing the error message
79
+ */
80
+ getErrorMessage(error) {
81
+ switch (error.status) {
82
+ case 400:
83
+ return 'Bad request - malformed request syntax or invalid parameters';
84
+ break;
85
+ case 401:
86
+ return 'Unauthorized - authentication required or invalid credentials';
87
+ break;
88
+ case 403:
89
+ return 'Forbidden - the authenticated user does not have permission to perform the operation';
90
+ break;
91
+ case 404:
92
+ return 'Not found - the requested resource does not exist';
93
+ break;
94
+ case 422:
95
+ return 'Unprocessable content - the request is correctly formed but contained semantic errors';
96
+ break;
97
+ case 500:
98
+ return 'Internal Server Error - generic server-side error';
99
+ break;
100
+ default:
101
+ return `Unexpected error occurred - status code: ${error.status}`;
102
+ }
103
+ }
104
+ /**
105
+ * Get a single resource
106
+ * @param url The endpoint URL
107
+ * @param params Optional query parameters
108
+ */
109
+ get(url, params) {
110
+ return this.http.get(`${this.baseUrl}${url}`, { params }).pipe(map((response) => this.handleResponse(response)), catchError((error) => this.handleHttpError(error)));
111
+ }
112
+ /**
113
+ * Get a collection of resources
114
+ * @param url The endpoint URL
115
+ * @param params Optional query parameters
116
+ */
117
+ handleCollectionResponse(response) {
118
+ if (response.errors) {
119
+ throw new Error(response.errors.join(', '));
120
+ }
121
+ return response;
122
+ }
123
+ getCollection(url, params) {
124
+ return this.http
125
+ .get(`${this.baseUrl}${url}`, { params })
126
+ .pipe(map((response) => this.handleCollectionResponse(response)), catchError((error) => this.handleHttpError(error)));
127
+ }
128
+ /**
129
+ * Create a new resource
130
+ * @param url The endpoint URL
131
+ * @param body The resource to create
132
+ */
133
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
134
+ post(url, body) {
135
+ return this.http.post(`${this.baseUrl}${url}`, body).pipe(map((response) => this.handleResponse(response)), catchError((error) => this.handleHttpError(error)));
136
+ }
137
+ /**
138
+ * Update an existing resource
139
+ * @param url The endpoint URL
140
+ * @param body The resource updates
141
+ */
142
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
143
+ put(url, body) {
144
+ return this.http.put(`${this.baseUrl}${url}`, body).pipe(map((response) => this.handleResponse(response)), catchError((error) => this.handleHttpError(error)));
145
+ }
146
+ /**
147
+ * Partially update an existing resource
148
+ * @param url The endpoint URL
149
+ * @param body The partial resource updates
150
+ */
151
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
152
+ patch(url, body) {
153
+ return this.http.patch(`${this.baseUrl}${url}`, body).pipe(map((response) => this.handleResponse(response)), catchError((error) => this.handleHttpError(error)));
154
+ }
155
+ /**
156
+ * Delete a resource
157
+ * @param url The endpoint URL
158
+ */
159
+ delete(url) {
160
+ return this.http.delete(`${this.baseUrl}${url}`).pipe(map((response) => this.handleResponse(response)), catchError((error) => this.handleHttpError(error)));
161
+ }
162
+ }
163
+
164
+ class AsyncTaskService {
165
+ // TO-DO [GIK 7/9/2025]: move 'https://astra-dev.jax.org' to an environment variable
166
+ apiBaseUrl = '/asynctask/api';
167
+ apiServiceFactory = inject(ApiBaseServiceFactory);
168
+ apiBaseService = this.apiServiceFactory.create(this.apiBaseUrl);
169
+ setApiBaseUrl(baseUrl) {
170
+ this.apiBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
171
+ // ensure the URL ends with '/asynctask/api'
172
+ if (!this.apiBaseUrl.endsWith('asynctask/api')) {
173
+ this.apiBaseUrl = `${this.apiBaseUrl}/asynctask/api`;
174
+ }
175
+ // recreate the apiService
176
+ this.apiBaseService = this.apiServiceFactory.create(this.apiBaseUrl);
177
+ }
178
+ getApiBaseUrl() {
179
+ return this.apiBaseUrl;
180
+ }
181
+ // INPUT
182
+ addInput(inputSubmission) {
183
+ return this.apiBaseService.post('/inputs', inputSubmission);
184
+ }
185
+ getInput(id) {
186
+ return this.apiBaseService.get(`/inputs/${id}`);
187
+ }
188
+ getInputs() {
189
+ return this.apiBaseService.getCollection('/inputs');
190
+ }
191
+ /**
192
+ * Updates an existing input - only 'name' and 'description' can be updated
193
+ * @param inputId
194
+ * @param name - (optional) name to update
195
+ * @param description - (optional) description to update
196
+ */
197
+ updateInput(inputId, name, description) {
198
+ let url = `/inputs/${inputId}?`;
199
+ if (name !== undefined) {
200
+ url += `name=${name}&`;
201
+ }
202
+ if (description !== undefined) {
203
+ url += `description=${description}`;
204
+ }
205
+ return this.apiBaseService.patch(url, null);
206
+ }
207
+ // RUN
208
+ createRun(inputId, inputSubmission) {
209
+ const url = inputId ? `/runs?input_id=${inputId}` : '/runs';
210
+ return this.apiBaseService.post(url, inputSubmission || null);
211
+ }
212
+ getRun(id) {
213
+ return this.apiBaseService.get(`/runs/${id}`);
214
+ }
215
+ /**
216
+ *
217
+ * @param workflowId - (optional) workflow identifier
218
+ */
219
+ getRuns(workflowId) {
220
+ const url = (workflowId ? `/runs?workflow_id=${workflowId}` : '/runs');
221
+ return this.apiBaseService.getCollection(url);
222
+ }
223
+ /**
224
+ * Gets the input associated with the specific run ID. One run is associated
225
+ * with only one input, so this function returns a single input object.
226
+ * @param runId
227
+ */
228
+ getRunInput(runId) {
229
+ return this.apiBaseService.get(`/runs/${runId}/inputs`);
230
+ }
231
+ /**
232
+ * Gets the result associated with the specific run ID. One run is associated
233
+ * with only one result, so this function returns a single result object.
234
+ * @param runId
235
+ */
236
+ getRunResult(runId) {
237
+ return this.apiBaseService.get(`/runs/${runId}/results`);
238
+ }
239
+ /**
240
+ * Calls the fetchEventSource() function, which is a wrapper around the native
241
+ * EventSource API. This function is used to establish connection to an API
242
+ * endpoint and listen to event streaming data associated with task runs.
243
+ * TO-DO [GIK 7/9/2025]: needs to add a reconnect logic
244
+ * @return an observable that emits run events
245
+ */
246
+ getRunEvents(accessToken) {
247
+ const api = this.apiBaseService;
248
+ // observable constructor argument is 'subscribe()' method implementation
249
+ return new Observable((subscriber) => {
250
+ // abort controller to cancel the request (on component destroy)
251
+ const abortController = new AbortController();
252
+ fetchEventSource(`${this.apiBaseUrl}/runs/events`, {
253
+ method: 'GET',
254
+ headers: {
255
+ 'Content-Type': 'text/event-stream',
256
+ 'Cache-Control': 'no-cache',
257
+ 'Authorization': `Bearer ${accessToken}`
258
+ },
259
+ async onopen(response) {
260
+ // SSE media type must be 'text/event-stream'
261
+ const contentType = response.headers.get('content-type');
262
+ if (!contentType?.startsWith('text/event-stream')) {
263
+ const errorResponse = {
264
+ code: 'INVALID_CONTENT_TYPE',
265
+ num_code: response.status,
266
+ message: `Expected content-type to be text/event-stream, but got: ${contentType}`
267
+ };
268
+ throw errorResponse;
269
+ }
270
+ // resolve promise (without returning anything), which indicates that the connection is open
271
+ if (response.ok && response.status === 200)
272
+ return;
273
+ // opening SSE connection failed
274
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) {
275
+ const errorResponse = {
276
+ code: 'RESOURCE_NOT_FOUND',
277
+ num_code: response.status,
278
+ message: 'Resource not found or access denied'
279
+ };
280
+ throw errorResponse;
281
+ }
282
+ },
283
+ onmessage(event) {
284
+ try {
285
+ const run = JSON.parse(event.data);
286
+ subscriber.next(run);
287
+ }
288
+ catch (error) {
289
+ const errorResponse = {
290
+ code: 'PARSE_ERROR',
291
+ num_code: 0,
292
+ message: `Failed to parse event data: ${event.data}`
293
+ };
294
+ throw errorResponse;
295
+ }
296
+ },
297
+ onclose() {
298
+ subscriber.complete();
299
+ },
300
+ onerror(errorRes) {
301
+ // send an error message to subscriber's error handler
302
+ subscriber.error(errorRes);
303
+ // rethrow the error to stop the operation
304
+ throw errorRes;
305
+ },
306
+ signal: abortController.signal
307
+ });
308
+ return () => {
309
+ abortController.abort(); // close connection on unsubscribe
310
+ };
311
+ });
312
+ }
313
+ // RESULT
314
+ getResult(resId) {
315
+ return this.apiBaseService.get(`/results/${resId}`);
316
+ }
317
+ getResults() {
318
+ return this.apiBaseService.getCollection('/results');
319
+ }
320
+ /**
321
+ * Updates an existing result record - 'name' and 'description' can be updated
322
+ * @param resId
323
+ * @param name - (optional) result's name to update
324
+ * @param description - (optional) result's description to update
325
+ */
326
+ updateResult(resId, name, description) {
327
+ let url = `/results/${resId}?`;
328
+ if (name !== undefined) {
329
+ url += `name=${name}&`;
330
+ }
331
+ if (description !== undefined) {
332
+ url += `description=${description}`;
333
+ }
334
+ return this.apiBaseService.patch(url, null);
335
+ }
336
+ // HEALTH CHECK
337
+ // TO-DO [GIK 05/30/2025]: should be moved outside this service
338
+ getHealthCheck() {
339
+ return this.apiBaseService.get('/monitors/servers/health');
340
+ }
341
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: AsyncTaskService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
342
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: AsyncTaskService, providedIn: 'root' });
343
+ }
344
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: AsyncTaskService, decorators: [{
345
+ type: Injectable,
346
+ args: [{
347
+ providedIn: 'root'
348
+ }]
349
+ }] });
350
+
351
+ /**
352
+ * AsyncTask Service Models
353
+ *
354
+ * This module contains TypeScript interfaces for the AsyncTask service API.
355
+ * These models are based on the OpenAPI specification and represent the
356
+ * core entities used in the AsyncTask workflow system.
357
+ */
358
+ /**
359
+ * Represents the current execution status of a workflow.
360
+ * Based on temporalio.api.enums.v1.WorkflowExecutionStatus
361
+ */
362
+ var WorkflowExecutionStatus;
363
+ (function (WorkflowExecutionStatus) {
364
+ WorkflowExecutionStatus[WorkflowExecutionStatus["RUNNING"] = 1] = "RUNNING";
365
+ WorkflowExecutionStatus[WorkflowExecutionStatus["COMPLETED"] = 2] = "COMPLETED";
366
+ WorkflowExecutionStatus[WorkflowExecutionStatus["FAILED"] = 3] = "FAILED";
367
+ WorkflowExecutionStatus[WorkflowExecutionStatus["CANCELED"] = 4] = "CANCELED";
368
+ WorkflowExecutionStatus[WorkflowExecutionStatus["TERMINATED"] = 5] = "TERMINATED";
369
+ WorkflowExecutionStatus[WorkflowExecutionStatus["CONTINUED_AS_NEW"] = 6] = "CONTINUED_AS_NEW";
370
+ WorkflowExecutionStatus[WorkflowExecutionStatus["TIMED_OUT"] = 7] = "TIMED_OUT";
371
+ })(WorkflowExecutionStatus || (WorkflowExecutionStatus = {}));
372
+
373
+ var Ontology;
374
+ (function (Ontology) {
375
+ Ontology["HP"] = "HP";
376
+ Ontology["MONDO"] = "MONDO";
377
+ Ontology["MP"] = "MP";
378
+ Ontology["CL"] = "CL";
379
+ Ontology["MAXO"] = "MAXO";
380
+ })(Ontology || (Ontology = {}));
381
+
382
+ class OntologyService {
383
+ httpClient;
384
+ config_location = 'https://raw.githubusercontent.com/TheJacksonLaboratory/ontology-service/refs/heads/main/config/ontologies-internal.json';
385
+ config;
386
+ /**
387
+ * Get the configuration file from the source for the backend api service
388
+ */
389
+ constructor(httpClient) {
390
+ this.httpClient = httpClient;
391
+ this.httpClient.get(this.config_location).subscribe({
392
+ next: (config) => this.config = config,
393
+ error: () => {
394
+ this.config = [];
395
+ }
396
+ });
397
+ }
398
+ /**
399
+ * Search for terms in an ontology
400
+ * @param query - the search query
401
+ * @param limit - the number of results to return
402
+ * @param ontology - the ontology to search
403
+ */
404
+ search(query, limit, ontology) {
405
+ return this.httpClient.get(`${this.ontologyBaseResolver(ontology)}/search?q=${query}&limit=${limit}`);
406
+ }
407
+ /**
408
+ * Get a term by its ID
409
+ * @param id - the term ID
410
+ */
411
+ term(id) {
412
+ try {
413
+ const url = `${this.ontologyBaseResolver(this.ontologyFromCurie(id))}/${id}`;
414
+ return this.httpClient.get(url);
415
+ }
416
+ catch (error) {
417
+ return throwError(error);
418
+ }
419
+ }
420
+ /**
421
+ * Get the parents of a term
422
+ * @param id - the term ID
423
+ */
424
+ parents(id) {
425
+ try {
426
+ const url = `${this.ontologyBaseResolver(this.ontologyFromCurie(id))}/${id}/parents`;
427
+ return this.httpClient.get(url);
428
+ }
429
+ catch (error) {
430
+ return throwError(error);
431
+ }
432
+ }
433
+ /**
434
+ * Get the children of a term
435
+ * @param id - the term ID
436
+ */
437
+ children(id) {
438
+ try {
439
+ const url = `${this.ontologyBaseResolver(this.ontologyFromCurie(id))}/${id}/children`;
440
+ return this.httpClient.get(url);
441
+ }
442
+ catch (error) {
443
+ return throwError(error);
444
+ }
445
+ }
446
+ /**
447
+ * Get the ancestors of a term
448
+ * @param id - the term ID
449
+ */
450
+ ancestors(id) {
451
+ try {
452
+ const url = `${this.ontologyBaseResolver(this.ontologyFromCurie(id))}/${id}/ancestors`;
453
+ return this.httpClient.get(url);
454
+ }
455
+ catch (error) {
456
+ return throwError(error);
457
+ }
458
+ }
459
+ /**
460
+ * Get the descendants of a term
461
+ * @param id - the term ID
462
+ */
463
+ descendants(id) {
464
+ try {
465
+ const url = `${this.ontologyBaseResolver(this.ontologyFromCurie(id))}/${id}/descendants`;
466
+ return this.httpClient.get(url);
467
+ }
468
+ catch (error) {
469
+ return throwError(error);
470
+ }
471
+ }
472
+ /**
473
+ * Get the ontology from curie
474
+ */
475
+ ontologyFromCurie(curie) {
476
+ return Ontology[curie.split(':')[0]];
477
+ }
478
+ /**
479
+ * Get the ontology api base url configuration
480
+ **/
481
+ ontologyBaseResolver(ontology) {
482
+ if (!this.config || this.config.length === 0) {
483
+ throw new Error('No ontology configuration found.');
484
+ }
485
+ const ontology_config = this.config.find((config) => config.prefix.toUpperCase() === ontology);
486
+ if (!ontology_config) {
487
+ throw new Error('Ontology not found in configuration.');
488
+ }
489
+ return ontology_config.api.base;
490
+ }
491
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: OntologyService, deps: [{ token: i1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
492
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: OntologyService, providedIn: 'root' });
493
+ }
494
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: OntologyService, decorators: [{
495
+ type: Injectable,
496
+ args: [{
497
+ providedIn: 'root'
498
+ }]
499
+ }], ctorParameters: () => [{ type: i1.HttpClient }] });
500
+
501
+ // TODO - move to constants file/library
502
+ const MUS_CHRS = [
503
+ '1',
504
+ '2',
505
+ '3',
506
+ '4',
507
+ '5',
508
+ '6',
509
+ '7',
510
+ '8',
511
+ '9',
512
+ '10',
513
+ '11',
514
+ '12',
515
+ '13',
516
+ '14',
517
+ '15',
518
+ '16',
519
+ '17',
520
+ '18',
521
+ '19',
522
+ 'X',
523
+ 'Y',
524
+ ];
525
+
526
+ class MVarService {
527
+ http;
528
+ environment;
529
+ api;
530
+ sequenceOntologyMapping = {};
531
+ soTerms = new BehaviorSubject([]);
532
+ soTerms$ = this.soTerms.asObservable();
533
+ constructor(http, environment) {
534
+ this.http = http;
535
+ this.environment = environment;
536
+ this.api = environment.unsecuredURLs.mvar;
537
+ // create a sequence ontology lookup
538
+ this.getSequenceOntologyTerms().subscribe((terms) => {
539
+ terms.forEach((t) => {
540
+ if (t.label) {
541
+ this.sequenceOntologyMapping[t.label] = t;
542
+ }
543
+ });
544
+ });
545
+ }
546
+ /**
547
+ * Gets Variants from mvar for a given set snp regions
548
+ * @param regions Array of snp regions
549
+ * @param pageStart snp start location
550
+ * @param pageEnd snp end location
551
+ * @param assembly Desired assembly version. Acceptable values are 'mm10' (GRCm38) and 'mm39' (GRCm39)
552
+ */
553
+ getVariants(regions, pageStart, pageEnd, assembly) {
554
+ return combineLatest(this.getRegionsToRequestVariantsFor(regions, pageStart, pageEnd).map((r) => {
555
+ return this.http.get(`${this.api}/variant/query?chr=${r.chromosome}&startPos=${r.start_position}&endPos=${r.end_position}&max=8000&assembly=${assembly}`);
556
+ })).pipe(map((variants) => {
557
+ const allVariants = variants.map((v) => v.variants).flat();
558
+ allVariants.forEach((variant) => {
559
+ const classCodes = variant.functionalClassCode.split(',')[0];
560
+ // add the ontology term
561
+ variant.functionalClasses = classCodes.split('&').map((c) => this.sequenceOntologyMapping[c]);
562
+ });
563
+ return allVariants;
564
+ }));
565
+ }
566
+ /**
567
+ * Returns all sequence ontology terms in MVAR
568
+ */
569
+ getSequenceOntologyTerms() {
570
+ return this.http
571
+ .get(`${this.api}/sequenceOntology/query?max=3000`)
572
+ .pipe(tap((terms) => this.soTerms.next(terms)));
573
+ }
574
+ /**
575
+ * Translates the regions requested from MUSter and the regions actually displayed on the current page into
576
+ * regions to request from MVAR
577
+ * @param requestedRegions - regions included in the request to MUSter
578
+ * @param pageStart - first SNP from the sorted results from MUSter to display in the table
579
+ * @param pageEnd - last SNP from the sorted results from MUSter to display in the table
580
+ */
581
+ getRegionsToRequestVariantsFor(requestedRegions, pageStart, pageEnd) {
582
+ const displayStartBP = pageStart.start_position || 0;
583
+ const displayEndBP = pageEnd.start_position || 0;
584
+ const regionsByChr = {};
585
+ requestedRegions.forEach((r) => {
586
+ if (regionsByChr[r.chromosome]) {
587
+ regionsByChr[r.chromosome].push(r);
588
+ }
589
+ else {
590
+ regionsByChr[r.chromosome] = [r];
591
+ }
592
+ });
593
+ const displayedRegions = [];
594
+ if (pageStart.chr === pageEnd.chr) {
595
+ regionsByChr[pageStart.chr].forEach((r) => {
596
+ if (r.start_position <= displayEndBP && r.end_position >= displayStartBP) {
597
+ displayedRegions.push({
598
+ chromosome: r.chromosome,
599
+ start_position: Math.max(r.start_position, displayStartBP),
600
+ end_position: Math.min(r.end_position, displayEndBP),
601
+ });
602
+ }
603
+ });
604
+ }
605
+ else {
606
+ const displayedChrs = Object.keys(regionsByChr).filter((r) => MUS_CHRS.indexOf(r) >= MUS_CHRS.indexOf(pageStart.chr) &&
607
+ MUS_CHRS.indexOf(r) <= MUS_CHRS.indexOf(pageEnd.chr));
608
+ displayedChrs.forEach((chr) => {
609
+ regionsByChr[chr].forEach((r) => {
610
+ if (r.end_position >= displayStartBP && chr === pageStart.chr) {
611
+ displayedRegions.push({
612
+ chromosome: r.chromosome,
613
+ start_position: Math.max(r.start_position, displayStartBP),
614
+ end_position: r.end_position,
615
+ });
616
+ }
617
+ else if (r.start_position <= displayEndBP && chr === pageEnd.chr) {
618
+ displayedRegions.push({
619
+ chromosome: r.chromosome,
620
+ start_position: r.start_position,
621
+ end_position: Math.min(r.end_position, displayEndBP),
622
+ });
623
+ }
624
+ else if (chr !== pageStart.chr && chr !== pageEnd.chr) {
625
+ displayedRegions.push({
626
+ chromosome: r.chromosome,
627
+ start_position: r.start_position,
628
+ end_position: r.end_position,
629
+ });
630
+ }
631
+ });
632
+ });
633
+ }
634
+ return displayedRegions;
635
+ }
636
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: MVarService, deps: [{ token: i1.HttpClient }, { token: 'environment' }], target: i0.ɵɵFactoryTarget.Injectable });
637
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: MVarService, providedIn: 'root' });
638
+ }
639
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: MVarService, decorators: [{
640
+ type: Injectable,
641
+ args: [{
642
+ providedIn: 'root'
643
+ }]
644
+ }], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
645
+ type: Inject,
646
+ args: ['environment']
647
+ }] }] });
648
+
649
+ class MvarClientModule {
650
+ static forRoot(environment) {
651
+ return {
652
+ ngModule: MvarClientModule,
653
+ providers: [
654
+ MVarService,
655
+ {
656
+ provide: 'environment', // you can also use InjectionToken
657
+ useValue: environment
658
+ }
659
+ ]
660
+ };
661
+ }
662
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: MvarClientModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
663
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.1.5", ngImport: i0, type: MvarClientModule, imports: [CommonModule] });
664
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: MvarClientModule, imports: [CommonModule] });
665
+ }
666
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: MvarClientModule, decorators: [{
667
+ type: NgModule,
668
+ args: [{
669
+ imports: [CommonModule],
670
+ }]
671
+ }] });
672
+
673
+ class SnpGridService {
674
+ http;
675
+ environment;
676
+ api;
677
+ // default true but becomes false if any of the API calls for general information fails
678
+ apiAvailable = true;
679
+ strains = new BehaviorSubject([]);
680
+ strains$ = this.strains.asObservable();
681
+ constructor(http, environment) {
682
+ this.http = http;
683
+ this.environment = environment;
684
+ this.api = environment.securedURLs.genomeMUSter;
685
+ this.getHealthCheck().subscribe({
686
+ error: () => {
687
+ this.apiAvailable = false;
688
+ },
689
+ });
690
+ this.getStrains().subscribe({
691
+ next: (strains) => {
692
+ this.apiAvailable = Boolean(strains.length);
693
+ },
694
+ error: () => {
695
+ this.apiAvailable = false;
696
+ },
697
+ });
698
+ }
699
+ /**
700
+ * Returns the result of a health check for the API
701
+ */
702
+ getHealthCheck() {
703
+ return this.http.get(`${this.api}/healthcheck`);
704
+ }
705
+ /**
706
+ * Returns the metadata on the MUSter DB
707
+ */
708
+ getMusterMetadata() {
709
+ return this.http.get(`${this.api}/db_info`).pipe(catchError((err) => {
710
+ this.apiAvailable = false;
711
+ throw err;
712
+ }));
713
+ }
714
+ /**
715
+ * Returns strains available in the API and takes an optional limit; by default the limit is set
716
+ * to 5000 to get all strains
717
+ * @param limit - maximum number of strains to be returned
718
+ */
719
+ getStrains(limit = 5000) {
720
+ return this.http
721
+ .get(`${this.api}/strains/?limit=${limit}`)
722
+ .pipe(tap((strains) => this.strains.next(strains.filter((s) => s.mpd_strainid))));
723
+ }
724
+ /**
725
+ * Returns a list of known genes whose symbols/coordinates start with the specified value.
726
+ * @param searchValue - value to use for the "starts with" filter
727
+ * @param limit - maximum number of genes to return, default is 20
728
+ */
729
+ getGenes(searchValue, limit = 20) {
730
+ return this.http.get(`${this.api}/genes/?symbol=${searchValue}%&limit=${limit}`);
731
+ }
732
+ /**
733
+ * Returns the gene that matches the specified gene symbol
734
+ * @param geneSymbol - symbol to use to get the associated gene info
735
+ */
736
+ getGene(geneSymbol) {
737
+ return this.http
738
+ .get(`${this.api}/genes/?symbol=${geneSymbol}`)
739
+ .pipe(map((genes) => (genes.length ? genes[0] : null)));
740
+ }
741
+ /**
742
+ * Returns true if the specified gene symbol is valid from the perspective of MUSter
743
+ * @param geneSymbol - symbol to use to check the validity of
744
+ */
745
+ isGeneSymbolValid(geneSymbol) {
746
+ return this.getGene(geneSymbol).pipe(map((g) => g !== null && geneSymbol.toLowerCase() === g.symbol.toLowerCase()));
747
+ }
748
+ /**
749
+ * Returns list of known reference SNP (RS) data which includes IDs and coordinates
750
+ * @param searchValue - value to use to filter the search results
751
+ * @param limit - maximum number of results to return, default is 20
752
+ */
753
+ getReferenceSNPs(searchValue, limit = 20) {
754
+ return this.http.get(`${this.api}/rsids/?rsid=${searchValue}%&limit=${limit}`);
755
+ }
756
+ /**
757
+ * Returns the RS info that matches the specified rsID
758
+ * @param rsid - the RSID to use to get the associated RS info
759
+ */
760
+ getReferenceSNP(rsid) {
761
+ return this.http
762
+ .get(`${this.api}/rsids/?rsid=${rsid}`)
763
+ .pipe(map((rsids) => (rsids.length ? { ...rsids[0], end_position: rsids[0].start_position } : null)));
764
+ }
765
+ /**
766
+ * Returns true if the specified rsID is valid from the perspective of MUSter
767
+ * @param rsid - rsID to use to check the validity of
768
+ */
769
+ isRSIDValid(rsid) {
770
+ return this.getReferenceSNP(rsid).pipe(map((rs) => rs !== null && rsid.toLowerCase() === rs.rsid.toLowerCase()));
771
+ }
772
+ /**
773
+ * Returns the SNP results generated from the query constructed from the specified parameters and page
774
+ * @param parameters - search properties (strain IDs, regions, assembly version, etc.)
775
+ * @param page - requested page, which is 0-indexed. The 0th page is the initial page
776
+ * @param pageSize - number of records to show per page. Default of 5000
777
+ */
778
+ getGenotypes(parameters, page = 0, pageSize = 5000) {
779
+ let constructedURL = `${this.api}/snps/?` +
780
+ `assembly=${parameters.assembly}&` +
781
+ `mpd_strain_ids=${parameters.strains.join(',')}&` +
782
+ `limit=${pageSize}`;
783
+ if (parameters.strains_b?.length) {
784
+ constructedURL += `&mpd_strain_ids_b=${parameters.strains_b.join(',')}&strain_limit=${parameters.strains.length + parameters.strains_b.length}`;
785
+ }
786
+ else {
787
+ constructedURL += `&strain_limit=${parameters.strains.length}`;
788
+ }
789
+ if (parameters.rsids?.length) {
790
+ constructedURL += `&rsids=${parameters.rsids.join(',')}`;
791
+ }
792
+ if (parameters.genes?.length) {
793
+ constructedURL += `&genes=${parameters.genes.join(',')}`;
794
+ }
795
+ if (parameters.regions?.length) {
796
+ constructedURL += `&regions=${parameters.regions.join(',')}`;
797
+ }
798
+ if (parameters.dataset_id) {
799
+ constructedURL += `&dataset_id=${parameters.dataset_id}`;
800
+ }
801
+ if (parameters.is_unique_row) {
802
+ constructedURL += `&is_unique_row=${parameters.is_unique_row}`;
803
+ }
804
+ if (page) {
805
+ constructedURL += `&offset=${pageSize * page}`;
806
+ }
807
+ return this.http.get(constructedURL);
808
+ }
809
+ /**
810
+ * Returns the URL that will generate a download (in file form) for the specified strain IDs and regions
811
+ * @param strains - list of mpd_strain_ids to query SNPs for
812
+ * @param regions - list of SNPSearchRegions to query SNPs for (chromosome, start and end)
813
+ * @param dataset_id - ID of the dataset to query SNPs for
814
+ * @param is_unique_row - boolean for polymorphism filtering - true for only rows with polymorphism, false for all
815
+ * @param limit - the limit of the number of result rows to include in the download - you're likely to
816
+ * prefer to pass the total number of rows generated by the query itself, which is included
817
+ * as part of the GenotypeResults returned by getGenotypes()
818
+ */
819
+ getGenotypeDownloadURLForCurrentData(strains, regions, dataset_id, is_unique_row = false, limit) {
820
+ if (!dataset_id) {
821
+ // default to the GenomeMuster dataset id
822
+ dataset_id = 2;
823
+ }
824
+ const stringifiedRegions = `regions=${regions
825
+ .map((reg) => `${reg.chromosome}:${reg.start_position}-${reg.end_position}`)
826
+ .join(',')}`;
827
+ const stringifiedStrains = `mpd_strain_ids=${strains.join(',')}`;
828
+ return `${this.api}/snps/download?dataset_id=${dataset_id}&${stringifiedStrains}&${stringifiedRegions}&limit=${limit}&is_unique_row=${is_unique_row}`;
829
+ }
830
+ /**
831
+ * Returns the dataset with the given ID
832
+ * @param id
833
+ */
834
+ getDataset(id) {
835
+ return this.http.get(`${this.api}/datasets/${id}`);
836
+ }
837
+ /**
838
+ * Returns a list of Datasets that match the given criteria
839
+ * @param status The value of the status to be used for filtering
840
+ * @param limit The maximum number of records to return
841
+ */
842
+ findDatasets(status, limit = 50) {
843
+ let url = `${this.api}/datasets`;
844
+ url += `/?limit=${limit}`;
845
+ if (status) {
846
+ url += `&status=${status}`;
847
+ }
848
+ return this.http.get(url);
849
+ }
850
+ /**
851
+ * Return a list of strains that match the given criteria
852
+ * @param datasetId The ID of the dataset from which to fetch the strains
853
+ * @param limit The maximum number of records to return
854
+ */
855
+ datasetStrains(datasetId, limit = 5000) {
856
+ const url = `${this.api}/dataset_strain/?dataset_id=${datasetId}&limit=${limit}`;
857
+ return this.http.get(url);
858
+ }
859
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: SnpGridService, deps: [{ token: i1.HttpClient }, { token: 'environment' }], target: i0.ɵɵFactoryTarget.Injectable });
860
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: SnpGridService, providedIn: 'root' });
861
+ }
862
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: SnpGridService, decorators: [{
863
+ type: Injectable,
864
+ args: [{
865
+ providedIn: 'root'
866
+ }]
867
+ }], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
868
+ type: Inject,
869
+ args: ['environment']
870
+ }] }] });
871
+
872
+ class SnpGridClientModule {
873
+ static forRoot(environment) {
874
+ return {
875
+ ngModule: SnpGridClientModule,
876
+ providers: [
877
+ SnpGridService,
878
+ {
879
+ provide: 'environment', // you can also use InjectionToken
880
+ useValue: environment
881
+ }
882
+ ]
883
+ };
884
+ }
885
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: SnpGridClientModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
886
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.1.5", ngImport: i0, type: SnpGridClientModule, imports: [CommonModule] });
887
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: SnpGridClientModule, imports: [CommonModule] });
888
+ }
889
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.5", ngImport: i0, type: SnpGridClientModule, decorators: [{
890
+ type: NgModule,
891
+ args: [{
892
+ imports: [CommonModule],
893
+ }]
894
+ }] });
895
+
896
+ var DatasetStatus;
897
+ (function (DatasetStatus) {
898
+ DatasetStatus["Queued"] = "queued";
899
+ DatasetStatus["Loading"] = "loading";
900
+ DatasetStatus["Done"] = "done";
901
+ DatasetStatus["Archived"] = "archived";
902
+ })(DatasetStatus || (DatasetStatus = {}));
903
+
904
+ // AsyncTask API client
905
+
906
+ /**
907
+ * Generated bundle index. Do not edit.
908
+ */
909
+
910
+ export { AsyncTaskService, DatasetStatus, MUS_CHRS, MVarService, MvarClientModule, OntologyService, SnpGridClientModule, SnpGridService, WorkflowExecutionStatus };
911
+ //# sourceMappingURL=jax-data-science-api-clients.mjs.map