@jbrowse/plugin-bed 4.3.0 → 5.0.0-beta.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.
Files changed (56) hide show
  1. package/esm/BedAdapter/BedAdapter.d.ts +4 -19
  2. package/esm/BedAdapter/BedAdapter.js +31 -88
  3. package/esm/BedAdapter/configSchema.d.ts +89 -40
  4. package/esm/BedAdapter/configSchema.js +65 -13
  5. package/esm/BedAdapter/index.js +4 -2
  6. package/esm/BedGraphAdapter/BedGraphAdapter.d.ts +4 -9
  7. package/esm/BedGraphAdapter/BedGraphAdapter.js +31 -85
  8. package/esm/BedGraphAdapter/configSchema.d.ts +35 -9
  9. package/esm/BedGraphAdapter/configSchema.js +47 -12
  10. package/esm/BedGraphAdapter/index.js +3 -2
  11. package/esm/BedGraphTabixAdapter/BedGraphTabixAdapter.d.ts +5 -5
  12. package/esm/BedGraphTabixAdapter/BedGraphTabixAdapter.js +62 -57
  13. package/esm/BedGraphTabixAdapter/configSchema.d.ts +48 -19
  14. package/esm/BedGraphTabixAdapter/configSchema.js +42 -34
  15. package/esm/BedGraphTabixAdapter/index.js +3 -2
  16. package/esm/BedTabixAdapter/BedTabixAdapter.d.ts +34 -38
  17. package/esm/BedTabixAdapter/BedTabixAdapter.js +82 -71
  18. package/esm/BedTabixAdapter/configSchema.d.ts +73 -31
  19. package/esm/BedTabixAdapter/configSchema.js +53 -35
  20. package/esm/BedTabixAdapter/index.js +4 -2
  21. package/esm/BedpeAdapter/BedpeAdapter.d.ts +4 -10
  22. package/esm/BedpeAdapter/BedpeAdapter.js +28 -69
  23. package/esm/BedpeAdapter/configSchema.d.ts +33 -10
  24. package/esm/BedpeAdapter/configSchema.js +45 -12
  25. package/esm/BedpeAdapter/index.js +4 -2
  26. package/esm/BedpeAdapter/util.js +22 -23
  27. package/esm/BigBedAdapter/BigBedAdapter.d.ts +8 -7
  28. package/esm/BigBedAdapter/BigBedAdapter.js +100 -107
  29. package/esm/BigBedAdapter/configSchema.d.ts +52 -17
  30. package/esm/BigBedAdapter/configSchema.js +53 -12
  31. package/esm/BigBedAdapter/index.js +3 -2
  32. package/esm/StarFusionAdapter/StarFusionAdapter.d.ts +15 -0
  33. package/esm/StarFusionAdapter/StarFusionAdapter.js +121 -0
  34. package/esm/StarFusionAdapter/configSchema.d.ts +31 -0
  35. package/esm/StarFusionAdapter/configSchema.js +52 -0
  36. package/esm/StarFusionAdapter/index.d.ts +2 -0
  37. package/esm/StarFusionAdapter/index.js +12 -0
  38. package/esm/adapterUtil.d.ts +5 -0
  39. package/esm/adapterUtil.js +28 -0
  40. package/esm/bedGraphUtil.d.ts +10 -0
  41. package/esm/bedGraphUtil.js +18 -0
  42. package/esm/generateBedMethylFeature.d.ts +1 -1
  43. package/esm/generateBedMethylFeature.js +19 -6
  44. package/esm/generateRastairMethylFeature.d.ts +28 -0
  45. package/esm/generateRastairMethylFeature.js +38 -0
  46. package/esm/generateRepeatMaskerFeature.d.ts +4 -51
  47. package/esm/generateRepeatMaskerFeature.js +28 -41
  48. package/esm/generateUcscTranscript.d.ts +15 -7
  49. package/esm/generateUcscTranscript.js +58 -28
  50. package/esm/index.d.ts +2 -0
  51. package/esm/index.js +10 -8
  52. package/esm/util.d.ts +32 -27
  53. package/esm/util.js +127 -97
  54. package/package.json +9 -13
  55. package/esm/GuessAdapter/index.d.ts +0 -2
  56. package/esm/GuessAdapter/index.js +0 -71
@@ -1,103 +1,114 @@
1
- import BED from '@gmod/bed';
2
1
  import { TabixIndexedFile } from '@gmod/tabix';
3
- import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
4
- import { SimpleFeature, updateStatus } from '@jbrowse/core/util';
5
- import { openLocation } from '@jbrowse/core/util/io';
2
+ import { readConfObject } from '@jbrowse/core/configuration';
3
+ import { BaseFeatureDataAdapter, cachedSetup, } from '@jbrowse/core/data_adapters/BaseAdapter';
4
+ import { SimpleFeature, downloadStatus } from '@jbrowse/core/util';
5
+ import { sharedBgzfWorkerPool } from '@jbrowse/core/util/bgzfWorkerPool';
6
+ import { decompressedBytesBudget } from '@jbrowse/core/util/cacheBudgets';
7
+ import { openLocation, openTabixIndexFilehandle } from '@jbrowse/core/util/io';
6
8
  import { ObservableCreate } from '@jbrowse/core/util/rxjs';
7
- import { checkStopToken } from '@jbrowse/core/util/stopToken';
8
- import { featureData } from "../util.js";
9
+ import { checkStopTokenThrottled, checkStopToken, createStopTokenChecker, withStopTokenSignal, } from '@jbrowse/core/util/stopToken';
10
+ import { readTabixHeaderLines } from '@jbrowse/core/util/tabix';
11
+ import { featureData, makeParser, parseNamesFromHeader } from '../util.js';
9
12
  export default class BedTabixAdapter extends BaseFeatureDataAdapter {
10
- parser;
13
+ // the parser needs the column names, which come from the file's header, so
14
+ // it cannot be built in the constructor
15
+ getParser = cachedSetup({
16
+ setup: async () => makeParser({
17
+ autoSql: readConfObject(this.config, 'autoSql'),
18
+ columnNames: await this.getNames(),
19
+ }),
20
+ });
21
+ bedGzLoc;
11
22
  bed;
12
- columnNames;
13
- scoreColumn;
14
23
  static capabilities = ['getFeatures', 'getRefNames'];
15
- setupP;
24
+ // `label` because the index metadata download narrates nothing itself; the
25
+ // helper shows it only while the first attempt is in flight, so pan/zoom
26
+ // re-entry (every getFeatures awaits it) doesn't re-flash "Downloading index"
27
+ getMetadata = cachedSetup({
28
+ label: 'Downloading index',
29
+ setup: () => this.bed.getMetadata(),
30
+ });
31
+ // Memoized: getFeatures needs the names on every query, and reading them goes
32
+ // to the file's leading blocks — a fetch and a decompress that was being
33
+ // repeated on every pan and zoom. Resolves undefined when the file declares
34
+ // no column names — parseNamesFromHeader's answer for a header it can't read
35
+ // names out of.
36
+ getNames = cachedSetup({ setup: () => this.readNames() });
16
37
  constructor(config, getSubAdapter, pluginManager) {
17
38
  super(config, getSubAdapter, pluginManager);
18
- const bedGzLoc = this.getConf('bedGzLocation');
19
- const type = this.getConf(['index', 'indexType']);
20
- const loc = this.getConf(['index', 'location']);
21
- const autoSql = this.getConf('autoSql');
39
+ this.bedGzLoc = readConfObject(this.config, 'bedGzLocation');
40
+ const type = readConfObject(this.config, ['index', 'indexType']);
41
+ const loc = readConfObject(this.config, ['index', 'location']);
22
42
  const pm = this.pluginManager;
23
43
  this.bed = new TabixIndexedFile({
24
- filehandle: openLocation(bedGzLoc, pm),
25
- csiFilehandle: type === 'CSI' ? openLocation(loc, pm) : undefined,
26
- tbiFilehandle: type !== 'CSI' ? openLocation(loc, pm) : undefined,
27
- chunkCacheSize: 50 * 2 ** 20,
44
+ filehandle: openLocation(this.bedGzLoc, pm),
45
+ ...openTabixIndexFilehandle(loc, type, pm),
46
+ chunkCacheBudget: decompressedBytesBudget,
47
+ bgzfWorkerPool: sharedBgzfWorkerPool(),
28
48
  });
29
- this.columnNames = this.getConf('columnNames');
30
- this.scoreColumn = this.getConf('scoreColumn');
31
- this.parser = new BED({ autoSql });
32
49
  }
33
50
  async getRefNames(opts = {}) {
34
- return this.bed.getReferenceSequenceNames(opts);
51
+ return downloadStatus('Downloading index', opts.statusCallback, onProgress => this.bed.getReferenceSequenceNames({ ...opts, onProgress }));
35
52
  }
36
53
  async getHeader(opts) {
37
54
  return this.bed.getHeader(opts);
38
55
  }
39
- async getMetadataPre2(_opts) {
40
- if (!this.setupP) {
41
- this.setupP = this.getMetadataPre().catch((e) => {
42
- this.setupP = undefined;
43
- throw e;
44
- });
45
- }
46
- return this.setupP;
47
- }
48
- async getMetadataPre() {
49
- const meta = await this.bed.getMetadata();
50
- return { meta };
51
- }
52
- async getMetadata(opts) {
53
- const { statusCallback = () => { } } = opts || {};
54
- return updateStatus('Downloading index', statusCallback, () => this.getMetadataPre2(opts));
56
+ /**
57
+ * Estimate compressed bytes for regions straight from the tabix index — no
58
+ * feature download. Lets wrapping adapters (e.g. MafTabix) byte-budget a
59
+ * fetch before pulling the (potentially huge) per-line payload.
60
+ */
61
+ async getRegionByteSize(regions, opts) {
62
+ return this.bed.bytesForRegions(regions, opts);
55
63
  }
56
- async getNames() {
57
- if (this.columnNames.length) {
58
- return this.columnNames;
64
+ async readNames() {
65
+ const columnNames = readConfObject(this.config, 'columnNames');
66
+ if (columnNames.length) {
67
+ return columnNames;
59
68
  }
60
- const header = await this.getHeader();
61
- const defs = header.split(/\n|\r\n|\r/).filter(f => !!f);
62
- const defline = defs.at(-1);
63
- return defline?.includes('\t')
64
- ? defline
65
- .slice(1)
66
- .split('\t')
67
- .map(f => f.trim())
68
- : undefined;
69
+ // readTabixHeaderLines covers both ways a tabix file keeps a header: a
70
+ // `#`-commented block, which getHeader() returns, and a plain row skipped
71
+ // via `tabix -S N`, which it does not.
72
+ return parseNamesFromHeader((await readTabixHeaderLines(this.bed)).join('\n'));
69
73
  }
70
74
  getFeatures(query, opts) {
71
- const { stopToken, statusCallback = () => { } } = opts || {};
75
+ const { stopToken, statusCallback } = opts ?? {};
72
76
  return ObservableCreate(async (observer) => {
73
- const { meta } = await this.getMetadata();
74
- const { columnNumbers } = meta;
75
- const colRef = columnNumbers.ref - 1;
76
- const colStart = columnNumbers.start - 1;
77
- const colEnd = columnNumbers.end - 1;
77
+ // warms the index under its own status label — getLines would otherwise
78
+ // download it under "Downloading features"
79
+ await this.getMetadata(opts);
78
80
  const names = await this.getNames();
79
- const disableGeneHeuristic = this.getConf('disableGeneHeuristic');
80
- let start = performance.now();
81
+ const parser = await this.getParser();
82
+ const scoreColumn = readConfObject(this.config, 'scoreColumn');
83
+ const disableGeneHeuristic = readConfObject(this.config, 'disableGeneHeuristic');
84
+ const stopTokenCheck = createStopTokenChecker(stopToken);
81
85
  checkStopToken(stopToken);
82
- await updateStatus('Downloading features', statusCallback, () => this.bed.getLines(query.refName, query.start, query.end, {
83
- lineCallback: (line, fileOffset) => {
84
- if (performance.now() - start > 500) {
85
- checkStopToken(stopToken);
86
- start = performance.now();
87
- }
86
+ await withStopTokenSignal(stopToken, signal => downloadStatus('Downloading features', statusCallback, onProgress =>
87
+ // start/end come from @gmod/tabix rather than being re-derived from
88
+ // the line: it located the coordinate columns to find these lines at
89
+ // all, and already applied the index's coordinate offset (-1 for a
90
+ // 1-based-closed preset) and its no-end-column convention (a single
91
+ // position becomes start..start+1). refName is likewise the query's
92
+ // — getLines only calls back for lines whose ref column matches it.
93
+ this.bed.getLines(query.refName, query.start, query.end, {
94
+ lineCallback: (line, fileOffset, start, end) => {
95
+ checkStopTokenThrottled(stopTokenCheck);
96
+ const splitLine = line.split('\t');
88
97
  observer.next(new SimpleFeature(featureData({
89
- line,
90
- colRef,
91
- colStart,
92
- colEnd,
93
- scoreColumn: this.scoreColumn,
94
- parser: this.parser,
98
+ splitLine,
99
+ refName: query.refName,
100
+ start,
101
+ end,
102
+ scoreColumn,
103
+ parser,
95
104
  uniqueId: `${this.id}-${fileOffset}`,
96
105
  names,
97
106
  disableGeneHeuristic,
98
107
  })));
99
108
  },
100
- }));
109
+ onProgress,
110
+ signal,
111
+ })));
101
112
  observer.complete();
102
113
  }, stopToken);
103
114
  }
@@ -1,44 +1,86 @@
1
+ import type { Instance } from '@jbrowse/mobx-state-tree';
2
+ export declare function normalizeSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
3
+ /**
4
+ * #config BedTabixAdapter
5
+ * #trackType FeatureTrack
6
+ * #fileFormat feature | BED (tabix)
7
+ *
8
+ * #example
9
+ * The `uri` shorthand auto-resolves the `.tbi` index; add `csi: true` for a
10
+ * `.csi` index instead:
11
+ * ```js
12
+ * {
13
+ * type: 'BedTabixAdapter',
14
+ * uri: 'https://example.com/features.bed.gz',
15
+ * }
16
+ * ```
17
+ */
1
18
  declare const BedTabixAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
2
- bedGzLocation: {
3
- type: string;
4
- defaultValue: {
5
- uri: string;
6
- locationType: string;
19
+ readonly densityAdapter: {
20
+ readonly type: 'frozen';
21
+ readonly defaultValue: null;
22
+ readonly description: 'optional quantitative sub-adapter (e.g. a BigWigAdapter over a features-per-bin bigWig) drawn as a density band where the region is too large to fetch features; null disables it';
23
+ };
24
+ /**
25
+ * #slot
26
+ * location of the bgzip-compressed BED, sorted by position. Must be bgzip
27
+ * rather than plain gzip, which tabix cannot index.
28
+ */
29
+ readonly bedGzLocation: {
30
+ readonly type: "fileLocation";
31
+ readonly defaultValue: {
32
+ readonly uri: "/path/to/my.bed.gz";
33
+ readonly locationType: "UriLocation";
7
34
  };
8
35
  };
9
- index: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
10
- indexType: {
11
- model: import("@jbrowse/mobx-state-tree").ISimpleType<"TBI" | "CSI">;
12
- type: string;
13
- defaultValue: string;
36
+ readonly index: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
37
+ readonly indexType: {
38
+ readonly model: import("@jbrowse/mobx-state-tree").ISimpleType<"CSI" | "TBI">;
39
+ readonly type: 'stringEnum';
40
+ readonly defaultValue: 'TBI';
41
+ readonly description: '`TBI` is the usual `tabix` output. `CSI` is required for a reference longer than 512 Mb, which TBI cannot address.';
14
42
  };
15
- location: {
16
- type: string;
17
- defaultValue: {
18
- uri: string;
19
- locationType: string;
43
+ readonly location: {
44
+ readonly type: 'fileLocation';
45
+ readonly defaultValue: {
46
+ readonly uri: '/path/to/my.gz.tbi';
47
+ readonly locationType: 'UriLocation';
20
48
  };
49
+ readonly description: 'location of the tabix index. Only needed when it is not named `<file>.tbi` (or `.csi`), which is what the `uri` shorthand assumes.';
21
50
  };
22
51
  }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
23
- columnNames: {
24
- type: string;
25
- description: string;
26
- defaultValue: never[];
52
+ /**
53
+ * #slot
54
+ */
55
+ readonly columnNames: {
56
+ readonly type: "stringArray";
57
+ readonly description: "List of column names. A column named like a standard BED column is parsed as that column's type (chromStart numeric, blockSizes a numeric list); any other column is text";
58
+ readonly defaultValue: readonly [];
27
59
  };
28
- scoreColumn: {
29
- type: string;
30
- description: string;
31
- defaultValue: string;
60
+ /**
61
+ * #slot
62
+ */
63
+ readonly scoreColumn: {
64
+ readonly type: "string";
65
+ readonly description: "The column to use as a \"score\" attribute";
66
+ readonly defaultValue: "";
32
67
  };
33
- autoSql: {
34
- type: string;
35
- description: string;
36
- defaultValue: string;
68
+ /**
69
+ * #slot
70
+ */
71
+ readonly autoSql: {
72
+ readonly type: "string";
73
+ readonly description: "The autoSql definition for the data fields in the file";
74
+ readonly defaultValue: "";
37
75
  };
38
- disableGeneHeuristic: {
39
- type: string;
40
- description: string;
41
- defaultValue: boolean;
76
+ /**
77
+ * #slot
78
+ */
79
+ readonly disableGeneHeuristic: {
80
+ readonly type: "boolean";
81
+ readonly description: "Disable the heuristic that auto-detects BED12 features as gene/transcript structures. Useful for files that have BED12-like structure but are not genes (e.g. tandem duplications)";
82
+ readonly defaultValue: false;
42
83
  };
43
84
  }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
85
+ export type BedTabixAdapterConfig = Instance<typeof BedTabixAdapter>;
44
86
  export default BedTabixAdapter;
@@ -1,7 +1,29 @@
1
- import { ConfigurationSchema } from '@jbrowse/core/configuration';
2
- import { types } from '@jbrowse/mobx-state-tree';
3
- function x() { }
1
+ import { ConfigurationSchema, expandTabixShorthand, tabixIndexFields, } from '@jbrowse/core/configuration';
2
+ import { densityAdapterConfigSchemaFields } from '@jbrowse/core/data_adapters/BaseAdapter';
3
+ export function normalizeSnapshot(snap) {
4
+ return expandTabixShorthand(snap, 'bedGzLocation');
5
+ }
6
+ /**
7
+ * #config BedTabixAdapter
8
+ * #trackType FeatureTrack
9
+ * #fileFormat feature | BED (tabix)
10
+ *
11
+ * #example
12
+ * The `uri` shorthand auto-resolves the `.tbi` index; add `csi: true` for a
13
+ * `.csi` index instead:
14
+ * ```js
15
+ * {
16
+ * type: 'BedTabixAdapter',
17
+ * uri: 'https://example.com/features.bed.gz',
18
+ * }
19
+ * ```
20
+ */
4
21
  const BedTabixAdapter = ConfigurationSchema('BedTabixAdapter', {
22
+ /**
23
+ * #slot
24
+ * location of the bgzip-compressed BED, sorted by position. Must be bgzip
25
+ * rather than plain gzip, which tabix cannot index.
26
+ */
5
27
  bedGzLocation: {
6
28
  type: 'fileLocation',
7
29
  defaultValue: {
@@ -9,58 +31,54 @@ const BedTabixAdapter = ConfigurationSchema('BedTabixAdapter', {
9
31
  locationType: 'UriLocation',
10
32
  },
11
33
  },
12
- index: ConfigurationSchema('TabixIndex', {
13
- indexType: {
14
- model: types.enumeration('IndexType', ['TBI', 'CSI']),
15
- type: 'stringEnum',
16
- defaultValue: 'TBI',
17
- },
18
- location: {
19
- type: 'fileLocation',
20
- defaultValue: {
21
- uri: '/path/to/my.bed.gz.tbi',
22
- locationType: 'UriLocation',
23
- },
24
- },
25
- }),
34
+ index: ConfigurationSchema('TabixIndex', { ...tabixIndexFields }),
35
+ /**
36
+ * #slot
37
+ */
26
38
  columnNames: {
27
39
  type: 'stringArray',
28
- description: 'List of column names',
40
+ description: "List of column names. A column named like a standard BED column is parsed as that column's type (chromStart numeric, blockSizes a numeric list); any other column is text",
29
41
  defaultValue: [],
30
42
  },
43
+ /**
44
+ * #slot
45
+ */
31
46
  scoreColumn: {
32
47
  type: 'string',
33
48
  description: 'The column to use as a "score" attribute',
34
49
  defaultValue: '',
35
50
  },
51
+ /**
52
+ * #slot
53
+ */
36
54
  autoSql: {
37
55
  type: 'string',
38
56
  description: 'The autoSql definition for the data fields in the file',
39
57
  defaultValue: '',
40
58
  },
59
+ /**
60
+ * #slot
61
+ */
41
62
  disableGeneHeuristic: {
42
63
  type: 'boolean',
43
64
  description: 'Disable the heuristic that auto-detects BED12 features as gene/transcript structures. Useful for files that have BED12-like structure but are not genes (e.g. tandem duplications)',
44
65
  defaultValue: false,
45
66
  },
67
+ ...densityAdapterConfigSchemaFields,
46
68
  }, {
47
69
  explicitlyTyped: true,
48
- preProcessSnapshot: snap => {
49
- return snap.uri
50
- ? {
51
- ...snap,
52
- bedGzLocation: {
53
- uri: snap.uri,
54
- baseUri: snap.baseUri,
55
- },
56
- index: {
57
- location: {
58
- uri: `${snap.uri}.tbi`,
59
- baseUri: snap.baseUri,
60
- },
61
- },
62
- }
63
- : snap;
64
- },
70
+ /**
71
+ * #preProcessSnapshot
72
+ *
73
+ *
74
+ * preprocessor to allow minimal config, assumes yourfile.bed.gz.tbi:
75
+ * ```json
76
+ * {
77
+ * "type": "BedTabixAdapter",
78
+ * "uri": "yourfile.bed.gz"
79
+ * }
80
+ * ```
81
+ */
82
+ preProcessSnapshot: normalizeSnapshot,
65
83
  });
66
84
  export default BedTabixAdapter;
@@ -1,10 +1,12 @@
1
1
  import AdapterType from '@jbrowse/core/pluggableElementTypes/AdapterType';
2
- import configSchema from "./configSchema.js";
2
+ import configSchema, { normalizeSnapshot } from './configSchema.js';
3
3
  export default function BedTabixAdapterF(pluginManager) {
4
4
  pluginManager.addAdapterType(() => new AdapterType({
5
5
  name: 'BedTabixAdapter',
6
6
  displayName: 'BED tabix adapter',
7
7
  configSchema,
8
- getAdapterClass: () => import("./BedTabixAdapter.js").then(r => r.default),
8
+ getAdapterClass: () => import('./BedTabixAdapter.js').then(r => r.default),
9
+ locationKey: 'bedGzLocation',
10
+ normalizeSnapshot,
9
11
  }));
10
12
  }
@@ -1,18 +1,12 @@
1
1
  import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
2
- import { IntervalTree } from '@jbrowse/core/util';
2
+ import type { BedpeAdapterConfig } from './configSchema.ts';
3
3
  import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
4
- import type { Feature, Region } from '@jbrowse/core/util';
5
- export default class BedpeAdapter extends BaseFeatureDataAdapter {
6
- protected bedpeFeatures?: Promise<{
7
- header: string;
8
- feats1: Record<string, string[]>;
9
- feats2: Record<string, string[]>;
10
- columnNames: string[];
11
- }>;
4
+ import type { Feature, IntervalTree, Region } from '@jbrowse/core/util';
5
+ export default class BedpeAdapter extends BaseFeatureDataAdapter<BedpeAdapterConfig> {
6
+ private loadData;
12
7
  protected intervalTrees: Record<string, Promise<IntervalTree<Feature> | undefined> | undefined>;
13
8
  static capabilities: string[];
14
9
  private loadDataP;
15
- private loadData;
16
10
  getRefNames(opts?: BaseOptions): Promise<string[]>;
17
11
  getHeader(opts?: BaseOptions): Promise<string>;
18
12
  getNames(): Promise<string[] | undefined>;
@@ -1,54 +1,39 @@
1
- import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
2
- import { IntervalTree, fetchAndMaybeUnzipText } from '@jbrowse/core/util';
1
+ import { BaseFeatureDataAdapter, cachedSetup, } from '@jbrowse/core/data_adapters/BaseAdapter';
2
+ import { fetchAndMaybeUnzip } from '@jbrowse/core/util';
3
3
  import { openLocation } from '@jbrowse/core/util/io';
4
- import { ObservableCreate } from '@jbrowse/core/util/rxjs';
5
- import { featureData } from "./util.js";
4
+ import { parseLineByLine } from '@jbrowse/core/util/parseLineByLine';
5
+ import { buildPairedIntervalTree, intervalTreeFeatures, } from '../adapterUtil.js';
6
+ import { resolveColumnNames } from '../util.js';
7
+ import { featureData } from './util.js';
6
8
  export default class BedpeAdapter extends BaseFeatureDataAdapter {
7
- bedpeFeatures;
9
+ // No `label`: `fetchAndMaybeUnzip` narrates the download from inside.
10
+ loadData = cachedSetup({ setup: opts => this.loadDataP(opts) });
8
11
  intervalTrees = {};
9
12
  static capabilities = ['getFeatures', 'getRefNames'];
10
13
  async loadDataP(opts) {
11
- const data = await fetchAndMaybeUnzipText(openLocation(this.getConf('bedpeLocation'), this.pluginManager), opts);
12
- const lines = data.split(/\n|\r\n|\r/).filter(f => !!f);
14
+ const buffer = await fetchAndMaybeUnzip(openLocation(this.getConf('bedpeLocation'), this.pluginManager), opts);
13
15
  const headerLines = [];
14
- let i = 0;
15
- for (; i < lines.length && lines[i].startsWith('#'); i++) {
16
- headerLines.push(lines[i]);
17
- }
18
- const header = headerLines.join('\n');
19
16
  const feats1 = {};
20
17
  const feats2 = {};
21
- for (; i < lines.length; i++) {
22
- const line = lines[i];
23
- const cols = line.split('\t');
24
- const r1 = cols[0];
25
- const r2 = cols[3];
26
- if (!feats1[r1]) {
27
- feats1[r1] = [];
18
+ parseLineByLine(buffer, line => {
19
+ if (line.startsWith('#')) {
20
+ headerLines.push(line);
28
21
  }
29
- if (!feats2[r2]) {
30
- feats2[r2] = [];
22
+ else if (!line.startsWith('track') && !line.startsWith('browser')) {
23
+ const cols = line.split('\t');
24
+ const r1 = cols[0];
25
+ const r2 = cols[3];
26
+ (feats1[r1] ??= []).push(line);
27
+ (feats2[r2] ??= []).push(line);
31
28
  }
32
- feats1[r1].push(line);
33
- feats2[r2].push(line);
34
- }
35
- const columnNames = this.getConf('columnNames');
29
+ return true;
30
+ }, opts?.statusCallback);
36
31
  return {
37
- header,
32
+ header: headerLines.join('\n'),
38
33
  feats1,
39
34
  feats2,
40
- columnNames,
41
35
  };
42
36
  }
43
- async loadData(opts = {}) {
44
- if (!this.bedpeFeatures) {
45
- this.bedpeFeatures = this.loadDataP(opts).catch((e) => {
46
- this.bedpeFeatures = undefined;
47
- throw e;
48
- });
49
- }
50
- return this.bedpeFeatures;
51
- }
52
37
  async getRefNames(opts = {}) {
53
38
  const { feats1, feats2 } = await this.loadData(opts);
54
39
  return [...new Set([...Object.keys(feats1), ...Object.keys(feats2)])];
@@ -58,47 +43,21 @@ export default class BedpeAdapter extends BaseFeatureDataAdapter {
58
43
  return header;
59
44
  }
60
45
  async getNames() {
61
- const { header, columnNames } = await this.loadData();
62
- if (columnNames.length) {
63
- return columnNames;
64
- }
65
- const defs = header.split(/\n|\r\n|\r/).filter(f => !!f);
66
- const defline = defs.at(-1);
67
- return defline?.includes('\t')
68
- ? defline
69
- .slice(1)
70
- .split('\t')
71
- .map(field => field.trim())
72
- : undefined;
46
+ return resolveColumnNames(this.getConf('columnNames'), async () => (await this.loadData()).header);
73
47
  }
74
48
  async loadFeatureTreeP(refName) {
75
49
  const { feats1, feats2 } = await this.loadData();
76
50
  const names = await this.getNames();
77
- const intervalTree = new IntervalTree();
78
- const ret1 = feats1[refName]?.map((f, i) => featureData(f, `${this.id}-${refName}-${i}-r1`, false, names)) ?? [];
79
- const ret2 = feats2[refName]?.map((f, i) => featureData(f, `${this.id}-${refName}-${i}-r2`, true, names)) ?? [];
80
- for (const obj of [...ret1, ...ret2]) {
81
- intervalTree.insert([obj.get('start'), obj.get('end')], obj);
82
- }
83
- return intervalTree;
51
+ return buildPairedIntervalTree(feats1, feats2, refName, this.id, (line, uniqueId, flip) => featureData(line, uniqueId, flip, names));
84
52
  }
85
53
  async loadFeatureTree(refName) {
86
- if (!this.intervalTrees[refName]) {
87
- this.intervalTrees[refName] = this.loadFeatureTreeP(refName).catch((e) => {
88
- this.intervalTrees[refName] = undefined;
89
- throw e;
90
- });
91
- }
54
+ this.intervalTrees[refName] ??= this.loadFeatureTreeP(refName).catch((e) => {
55
+ this.intervalTrees[refName] = undefined;
56
+ throw e;
57
+ });
92
58
  return this.intervalTrees[refName];
93
59
  }
94
60
  getFeatures(query, opts = {}) {
95
- return ObservableCreate(async (observer) => {
96
- const { start, end, refName } = query;
97
- const intervalTree = await this.loadFeatureTree(refName);
98
- for (const f of intervalTree?.search([start, end]) || []) {
99
- observer.next(f);
100
- }
101
- observer.complete();
102
- }, opts.stopToken);
61
+ return intervalTreeFeatures(query, opts, refName => this.loadFeatureTree(refName));
103
62
  }
104
63
  }
@@ -1,16 +1,39 @@
1
+ import type { Instance } from '@jbrowse/mobx-state-tree';
2
+ export declare function normalizeSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
3
+ /**
4
+ * #config BedpeAdapter
5
+ * #trackType VariantTrack
6
+ * #fileFormat variants | BEDPE | Paired/breakend records, e.g. SV calls or Hi-C loops
7
+ * intended for SVs in a single assembly
8
+ *
9
+ * #example
10
+ * ```js
11
+ * {
12
+ * type: 'BedpeAdapter',
13
+ * uri: 'https://example.com/sv.bedpe',
14
+ * }
15
+ * ```
16
+ */
1
17
  declare const BedpeAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
2
- bedpeLocation: {
3
- type: string;
4
- description: string;
5
- defaultValue: {
6
- uri: string;
7
- locationType: string;
18
+ /**
19
+ * #slot
20
+ */
21
+ readonly bedpeLocation: {
22
+ readonly type: "fileLocation";
23
+ readonly description: "can be plaintext or gzipped, not indexed so loaded into memory on startup";
24
+ readonly defaultValue: {
25
+ readonly uri: "/path/to/my.bedpe.gz";
26
+ readonly locationType: "UriLocation";
8
27
  };
9
28
  };
10
- columnNames: {
11
- type: string;
12
- description: string;
13
- defaultValue: never[];
29
+ /**
30
+ * #slot
31
+ */
32
+ readonly columnNames: {
33
+ readonly type: "stringArray";
34
+ readonly description: "List of column names";
35
+ readonly defaultValue: readonly [];
14
36
  };
15
37
  }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
38
+ export type BedpeAdapterConfig = Instance<typeof BedpeAdapter>;
16
39
  export default BedpeAdapter;