@jbrowse/plugin-bed 4.3.0 → 5.0.0-beta.10

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 (64) hide show
  1. package/esm/BedAdapter/BedAdapter.d.ts +5 -22
  2. package/esm/BedAdapter/BedAdapter.js +29 -93
  3. package/esm/BedAdapter/configSchema.d.ts +59 -32
  4. package/esm/BedAdapter/configSchema.js +64 -14
  5. package/esm/BedAdapter/index.js +4 -2
  6. package/esm/BedGraphAdapter/BedGraphAdapter.d.ts +5 -12
  7. package/esm/BedGraphAdapter/BedGraphAdapter.js +33 -89
  8. package/esm/BedGraphAdapter/configSchema.d.ts +35 -11
  9. package/esm/BedGraphAdapter/configSchema.js +43 -13
  10. package/esm/BedGraphAdapter/index.js +3 -2
  11. package/esm/BedGraphTabixAdapter/BedGraphTabixAdapter.d.ts +5 -5
  12. package/esm/BedGraphTabixAdapter/BedGraphTabixAdapter.js +65 -57
  13. package/esm/BedGraphTabixAdapter/configSchema.d.ts +46 -22
  14. package/esm/BedGraphTabixAdapter/configSchema.js +50 -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 -72
  18. package/esm/BedTabixAdapter/configSchema.d.ts +55 -31
  19. package/esm/BedTabixAdapter/configSchema.js +58 -35
  20. package/esm/BedTabixAdapter/index.js +4 -2
  21. package/esm/BedpeAdapter/BedpeAdapter.d.ts +6 -13
  22. package/esm/BedpeAdapter/BedpeAdapter.js +26 -74
  23. package/esm/BedpeAdapter/configSchema.d.ts +31 -12
  24. package/esm/BedpeAdapter/configSchema.js +37 -13
  25. package/esm/BedpeAdapter/index.js +4 -2
  26. package/esm/BedpeAdapter/util.js +25 -25
  27. package/esm/BigBedAdapter/BigBedAdapter.d.ts +8 -7
  28. package/esm/BigBedAdapter/BigBedAdapter.js +99 -107
  29. package/esm/BigBedAdapter/configSchema.d.ts +38 -16
  30. package/esm/BigBedAdapter/configSchema.js +46 -13
  31. package/esm/BigBedAdapter/index.js +3 -2
  32. package/esm/BigBedTextSearchAdapter/BigBedTextSearchAdapter.d.ts +27 -0
  33. package/esm/BigBedTextSearchAdapter/BigBedTextSearchAdapter.js +128 -0
  34. package/esm/BigBedTextSearchAdapter/configSchema.d.ts +51 -0
  35. package/esm/BigBedTextSearchAdapter/configSchema.js +57 -0
  36. package/esm/BigBedTextSearchAdapter/index.d.ts +2 -0
  37. package/esm/BigBedTextSearchAdapter/index.js +11 -0
  38. package/esm/StarFusionAdapter/StarFusionAdapter.d.ts +14 -0
  39. package/esm/StarFusionAdapter/StarFusionAdapter.js +66 -0
  40. package/esm/StarFusionAdapter/configSchema.d.ts +30 -0
  41. package/esm/StarFusionAdapter/configSchema.js +43 -0
  42. package/esm/StarFusionAdapter/index.d.ts +2 -0
  43. package/esm/StarFusionAdapter/index.js +12 -0
  44. package/esm/adapterUtil.d.ts +5 -0
  45. package/esm/adapterUtil.js +43 -0
  46. package/esm/bedGraphUtil.d.ts +11 -0
  47. package/esm/bedGraphUtil.js +20 -0
  48. package/esm/copyExcept.d.ts +1 -0
  49. package/esm/copyExcept.js +11 -0
  50. package/esm/generateBedMethylFeature.d.ts +1 -1
  51. package/esm/generateBedMethylFeature.js +24 -6
  52. package/esm/generateRastairMethylFeature.d.ts +28 -0
  53. package/esm/generateRastairMethylFeature.js +38 -0
  54. package/esm/generateRepeatMaskerFeature.d.ts +17 -51
  55. package/esm/generateRepeatMaskerFeature.js +27 -37
  56. package/esm/generateUcscTranscript.d.ts +15 -7
  57. package/esm/generateUcscTranscript.js +106 -29
  58. package/esm/index.d.ts +2 -0
  59. package/esm/index.js +12 -8
  60. package/esm/util.d.ts +32 -27
  61. package/esm/util.js +139 -98
  62. package/package.json +10 -13
  63. package/esm/GuessAdapter/index.d.ts +0 -2
  64. package/esm/GuessAdapter/index.js +0 -71
@@ -1,6 +1,25 @@
1
- import { ConfigurationSchema } from '@jbrowse/core/configuration';
2
- function x() { }
1
+ import { ConfigurationSchema, expandUriShorthand, } from '@jbrowse/core/configuration';
2
+ export function normalizeSnapshot(snap) {
3
+ return expandUriShorthand(snap, 'bedpeLocation');
4
+ }
5
+ /**
6
+ * #config BedpeAdapter
7
+ * #trackType VariantTrack
8
+ * #fileFormat variants | BEDPE | Paired/breakend records, e.g. SV calls or Hi-C loops
9
+ * intended for SVs in a single assembly
10
+ *
11
+ * #example
12
+ * ```js
13
+ * {
14
+ * type: 'BedpeAdapter',
15
+ * uri: 'https://example.com/sv.bedpe',
16
+ * }
17
+ * ```
18
+ */
3
19
  const BedpeAdapter = ConfigurationSchema('BedpeAdapter', {
20
+ /**
21
+ * #slot
22
+ */
4
23
  bedpeLocation: {
5
24
  type: 'fileLocation',
6
25
  description: 'can be plaintext or gzipped, not indexed so loaded into memory on startup',
@@ -9,6 +28,9 @@ const BedpeAdapter = ConfigurationSchema('BedpeAdapter', {
9
28
  locationType: 'UriLocation',
10
29
  },
11
30
  },
31
+ /**
32
+ * #slot
33
+ */
12
34
  columnNames: {
13
35
  type: 'stringArray',
14
36
  description: 'List of column names',
@@ -16,16 +38,18 @@ const BedpeAdapter = ConfigurationSchema('BedpeAdapter', {
16
38
  },
17
39
  }, {
18
40
  explicitlyTyped: true,
19
- preProcessSnapshot: snap => {
20
- return snap.uri
21
- ? {
22
- ...snap,
23
- bedpeLocation: {
24
- uri: snap.uri,
25
- baseUri: snap.baseUri,
26
- },
27
- }
28
- : snap;
29
- },
41
+ /**
42
+ * #preProcessSnapshot
43
+ *
44
+ *
45
+ * preprocessor to allow minimal config:
46
+ * ```json
47
+ * {
48
+ * "type": "BedpeAdapter",
49
+ * "uri": "yourfile.bedpe.gz"
50
+ * }
51
+ * ```
52
+ */
53
+ preProcessSnapshot: normalizeSnapshot,
30
54
  });
31
55
  export default BedpeAdapter;
@@ -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 BedpeAdapterF(pluginManager) {
4
4
  pluginManager.addAdapterType(() => new AdapterType({
5
5
  name: 'BedpeAdapter',
6
6
  displayName: 'BEDPE adapter',
7
7
  configSchema,
8
- getAdapterClass: () => import("./BedpeAdapter.js").then(r => r.default),
8
+ getAdapterClass: () => import('./BedpeAdapter.js').then(r => r.default),
9
+ locationKey: 'bedpeLocation',
10
+ normalizeSnapshot,
9
11
  }));
10
12
  }
@@ -1,22 +1,36 @@
1
1
  import { SimpleFeature } from '@jbrowse/core/util';
2
+ import { svClassOfToken } from '@jbrowse/core/util/svAlt';
3
+ import { parseStrand } from '../util.js';
2
4
  const svTypes = new Set(['DUP', 'TRA', 'INV', 'CNV', 'DEL']);
3
5
  export function featureData(line, uniqueId, flip, names) {
4
6
  const l = line.split('\t');
5
7
  const ref1 = l[flip ? 3 : 0];
6
8
  const start1 = +l[flip ? 4 : 1];
7
9
  const end1 = +l[flip ? 5 : 2];
8
- const ref2 = l[!flip ? 3 : 0];
9
- const start2 = +l[!flip ? 4 : 1];
10
- const end2 = +l[!flip ? 5 : 2];
11
- const name = l[6];
12
- const score = l[7] ? +l[7] : undefined;
13
- const strand1 = parseStrand(l[8]);
14
- const strand2 = parseStrand(l[9]);
10
+ const ref2 = l[flip ? 0 : 3];
11
+ const start2 = +l[flip ? 1 : 4];
12
+ const end2 = +l[flip ? 2 : 5];
13
+ // '.' is BED's "missing" marker, so a file that leaves the standard name and
14
+ // score columns unset must not produce the literal string '.' and a NaN
15
+ // score. Same rule `defaultParser` applies on the plain-BED path.
16
+ const name = l[6] === '.' ? undefined : l[6];
17
+ const score = l[7] && l[7] !== '.' ? +l[7] : undefined;
18
+ // Columns 9 and 10 ride with the blocks they describe. Left behind on a
19
+ // flipped row they anchored the feature at one end and gave it the other
20
+ // end's orientation, which every consumer reading a junction edge off the
21
+ // strand then got backwards for that half of the record.
22
+ const strand1 = parseStrand(l[flip ? 9 : 8]);
23
+ const strand2 = parseStrand(l[flip ? 8 : 9]);
15
24
  const extra = l.slice(10);
16
25
  const rest = names
17
26
  ? Object.fromEntries(names.slice(10).map((n, idx) => [n, extra[idx]]))
18
27
  : {};
19
- const ALT = svTypes.has(extra[0]) ? `<${extra[0]}>` : undefined;
28
+ const token = svTypes.has(extra[0]) ? extra[0] : undefined;
29
+ // `name` and `score` are spread only when the file actually sets them, which
30
+ // is what lets a column past 10 of the same name through. juicer's bedpe is
31
+ // the case that needs it: Arrowhead writes '.' in both standard columns and
32
+ // its corner score in column 12, also called `score`, so an unconditional
33
+ // positional key shadowed the only column the caller ranks domains by.
20
34
  return new SimpleFeature({
21
35
  ...rest,
22
36
  start: start1,
@@ -24,8 +38,8 @@ export function featureData(line, uniqueId, flip, names) {
24
38
  type: 'paired_feature',
25
39
  refName: ref1,
26
40
  strand: strand1,
27
- name,
28
- score,
41
+ ...(name === undefined ? {} : { name }),
42
+ ...(score === undefined ? {} : { score }),
29
43
  uniqueId,
30
44
  mate: {
31
45
  refName: ref2,
@@ -33,20 +47,6 @@ export function featureData(line, uniqueId, flip, names) {
33
47
  end: end2,
34
48
  strand: strand2,
35
49
  },
36
- ...(ALT ? { ALT: [ALT] } : {}),
50
+ ...(token ? { ALT: [`<${token}>`], svType: svClassOfToken(token) } : {}),
37
51
  });
38
52
  }
39
- function parseStrand(strand) {
40
- if (strand === '+') {
41
- return 1;
42
- }
43
- else if (strand === '-') {
44
- return -1;
45
- }
46
- else if (strand === '.') {
47
- return 0;
48
- }
49
- else {
50
- return undefined;
51
- }
52
- }
@@ -1,32 +1,33 @@
1
1
  import { BigBed } from '@gmod/bbi';
2
2
  import BED from '@gmod/bed';
3
3
  import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
4
+ import type { BigBedAdapterConfig } from './configSchema.ts';
4
5
  import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
5
6
  import type { Feature } from '@jbrowse/core/util';
6
7
  import type { Region } from '@jbrowse/core/util/types';
7
8
  import type { Observer } from 'rxjs';
8
- export default class BigBedAdapter extends BaseFeatureDataAdapter {
9
- private cachedP?;
10
- configurePre(opts?: BaseOptions): Promise<{
9
+ export default class BigBedAdapter extends BaseFeatureDataAdapter<BigBedAdapterConfig> {
10
+ configure: (opts?: BaseOptions) => Promise<{
11
11
  bigbed: BigBed;
12
12
  header: import("@gmod/bbi").BigWigHeaderWithRefNames;
13
13
  parser: BED;
14
14
  }>;
15
- configure(opts?: BaseOptions): Promise<{
15
+ configurePre(opts?: BaseOptions): Promise<{
16
16
  bigbed: BigBed;
17
- header: Awaited<ReturnType<BigBed["getHeader"]>>;
17
+ header: import("@gmod/bbi").BigWigHeaderWithRefNames;
18
18
  parser: BED;
19
19
  }>;
20
20
  getRefNames(opts?: BaseOptions): Promise<string[]>;
21
+ getRegionByteSize(regions: Region[], opts?: BaseOptions): Promise<number>;
21
22
  getRefNameAliases(opts?: BaseOptions): Promise<{
22
23
  refName: unknown;
23
- aliases: unknown[];
24
+ aliases: string[];
24
25
  override: boolean;
25
26
  }[]>;
26
27
  getData(): Promise<Feature[]>;
27
28
  getHeader(opts?: BaseOptions): Promise<{
28
29
  version: number;
29
- fileType: string;
30
+ fileType: "bigbed" | "bigwig";
30
31
  autoSql: {};
31
32
  fields: {
32
33
  [k: string]: string;
@@ -1,13 +1,19 @@
1
1
  import { BigBed } from '@gmod/bbi';
2
2
  import BED from '@gmod/bed';
3
- import { BaseFeatureDataAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
4
- import { SimpleFeature, doesIntersect2, max, min, updateStatus, } from '@jbrowse/core/util';
3
+ import { BaseFeatureDataAdapter, cachedSetup, } from '@jbrowse/core/data_adapters/BaseAdapter';
4
+ import { SimpleFeature, doesIntersect2, downloadStatus, max, min, updateStatus, } from '@jbrowse/core/util';
5
5
  import { openLocation } from '@jbrowse/core/util/io';
6
6
  import { ObservableCreate } from '@jbrowse/core/util/rxjs';
7
7
  import { firstValueFrom, toArray } from 'rxjs';
8
- import { featureData2 } from "../util.js";
8
+ import { featureData } from '../util.js';
9
9
  export default class BigBedAdapter extends BaseFeatureDataAdapter {
10
- cachedP;
10
+ // `label` on cachedSetup so "Downloading header" shows only while the first
11
+ // fetch is in flight; every later getFeatures resolves from the memo with no
12
+ // re-flash.
13
+ configure = cachedSetup({
14
+ label: 'Downloading header',
15
+ setup: opts => this.configurePre(opts),
16
+ });
11
17
  async configurePre(opts) {
12
18
  const pm = this.pluginManager;
13
19
  const bigbed = new BigBed({
@@ -23,19 +29,19 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter {
23
29
  parser,
24
30
  };
25
31
  }
26
- async configure(opts) {
27
- if (!this.cachedP) {
28
- this.cachedP = this.configurePre(opts).catch((e) => {
29
- this.cachedP = undefined;
30
- throw e;
31
- });
32
- }
33
- return this.cachedP;
34
- }
35
32
  async getRefNames(opts) {
36
33
  const { header } = await this.configure(opts);
37
34
  return Object.keys(header.refsByName);
38
35
  }
36
+ // Compressed download-size estimate straight from the bbi R-tree index (block
37
+ // byte lengths, no data blocks read), so a display can byte-gate an over-large
38
+ // BigBed fetch before it starts. Overrides the base no-estimate default, which
39
+ // left BigBed tracks ungated.
40
+ async getRegionByteSize(regions, opts) {
41
+ const { bigbed } = await this.configure(opts);
42
+ return bigbed.getRegionByteSizeMulti(regions, opts);
43
+ }
44
+ // allow using BigBedAdapter for aliases with chromAlias.bb file from UCSC
39
45
  async getRefNameAliases(opts) {
40
46
  const { header } = await this.configure(opts);
41
47
  const ret = await Promise.all(Object.keys(header.refsByName).map(async (refName) => (await firstValueFrom(this.getFeatures({
@@ -48,22 +54,20 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter {
48
54
  .map(r => r.toJSON())
49
55
  .map(r => ({
50
56
  refName: r.ucsc,
51
- aliases: [r.ncbi, r.refseq, r.genbank],
57
+ // chromAlias.bb columns are sparse: a chrom may lack an ncbi/refseq/
58
+ // genbank name, returned as empty strings that are not valid refNames
59
+ aliases: [r.ncbi, r.refseq, r.genbank].filter((alias) => !!alias),
52
60
  override: true,
53
61
  }));
54
62
  }
55
63
  async getData() {
56
64
  const refNames = await this.getRefNames();
57
- const features = [];
58
- for (const refName of refNames) {
59
- const f = await firstValueFrom(this.getFeatures({
60
- assemblyName: 'unknown',
61
- refName,
62
- start: 0,
63
- end: Number.MAX_SAFE_INTEGER,
64
- }).pipe(toArray()));
65
- features.push(f);
66
- }
65
+ const features = await Promise.all(refNames.map(refName => firstValueFrom(this.getFeatures({
66
+ assemblyName: 'unknown',
67
+ refName,
68
+ start: 0,
69
+ end: Number.MAX_SAFE_INTEGER,
70
+ }).pipe(toArray()))));
67
71
  return features.flat();
68
72
  }
69
73
  async getHeader(opts) {
@@ -83,78 +87,69 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter {
83
87
  return Object.fromEntries(fields.map(({ name, comment }) => [name, comment]));
84
88
  }
85
89
  async getFeaturesHelper({ query, opts, observer, allowRedispatch, originalQuery = query, }) {
86
- const { statusCallback = () => { } } = opts;
90
+ const { statusCallback } = opts;
87
91
  const scoreColumn = this.getConf('scoreColumn');
88
92
  const aggregateField = this.getConf('aggregateField');
89
93
  const disableGeneHeuristic = this.getConf('disableGeneHeuristic');
90
- const { parser, bigbed } = await updateStatus('Downloading header', statusCallback, () => this.configure(opts));
91
- const feats = await updateStatus('Downloading features', statusCallback, () => bigbed.getFeatures(query.refName, query.start, query.end, {
94
+ const { parser, bigbed } = await this.configure(opts);
95
+ // downloadStatus, not updateStatus: bbi knows the total block bytes up front
96
+ // (they come from the R-tree index) so this reports a determinate fraction
97
+ // rather than an indeterminate spinner. Matters most here — BigBed is a
98
+ // full-feature download, which is why getRegionByteSize gates it above.
99
+ const feats = await downloadStatus('Downloading features', statusCallback, onProgress => bigbed.getFeatures(query.refName, query.start, query.end, {
92
100
  basesPerSpan: query.end - query.start,
101
+ onProgress,
102
+ signal: opts.signal,
93
103
  }));
94
104
  await updateStatus('Processing features', statusCallback, async () => {
95
105
  const parentAggregation = {};
96
- const parentAggregationFlat = [];
97
- if (feats.some(f => f.uniqueId === undefined)) {
98
- throw new Error('found uniqueId undefined');
99
- }
106
+ const singletons = [];
107
+ let minAggStart = Infinity;
108
+ let maxAggEnd = -Infinity;
100
109
  for (const feat of feats) {
101
- const splitLine = [
102
- query.refName,
103
- `${feat.start}`,
104
- `${feat.end}`,
105
- ...(feat.rest?.split('\t') || []),
106
- ];
107
- const data = parser.parseLine(splitLine, {
108
- uniqueId: feat.uniqueId,
109
- });
110
- const aggr = data[aggregateField];
111
- const aggrIsNotNone = aggr && aggr !== 'none';
112
- const aggrKey = typeof aggr === 'string' ? aggr : `${aggr}`;
113
- if (aggrIsNotNone && !parentAggregation[aggrKey]) {
114
- parentAggregation[aggrKey] = [];
115
- }
116
- const { uniqueId, type, chrom, chromStart, chromEnd, description, chromStarts: chromStarts2, blockStarts: blockStarts2, blockSizes: blockSizes2, score: score2, blockCount, thickStart, thickEnd, strand, ...rest } = data;
117
- const f = featureData2({
118
- ...rest,
110
+ // empty chrom/start/end placeholders keep `rest` columns aligned to the
111
+ // autoSql schema (which begins at chrom); featureData reads back the
112
+ // numeric start/end/refName passed below, so positions 0-2 are unused
113
+ const splitLine = ['', '', '', ...(feat.rest?.split('\t') ?? [])];
114
+ const f = featureData({
119
115
  scoreColumn,
120
116
  splitLine,
121
117
  parser,
122
- uniqueId: uniqueId,
118
+ uniqueId: feat.uniqueId,
123
119
  start: feat.start,
124
120
  end: feat.end,
125
121
  refName: query.refName,
126
122
  disableGeneHeuristic,
127
123
  });
124
+ const aggr = f[aggregateField];
125
+ const aggrIsNotNone = typeof aggr === 'string' && aggr && aggr !== 'none';
128
126
  if (aggrIsNotNone) {
129
- parentAggregation[aggrKey].push(f);
130
- parentAggregationFlat.push(f);
127
+ parentAggregation[aggr] ??= [];
128
+ parentAggregation[aggr].push(f);
129
+ if (f.start < minAggStart) {
130
+ minAggStart = f.start;
131
+ }
132
+ if (f.end > maxAggEnd) {
133
+ maxAggEnd = f.end;
134
+ }
131
135
  }
132
136
  else {
133
- if (doesIntersect2(f.start, f.end, originalQuery.start, originalQuery.end)) {
134
- observer.next(new SimpleFeature({
135
- id: `${this.id}-${uniqueId}`,
136
- data: f,
137
- }));
138
- }
137
+ singletons.push(f);
139
138
  }
140
139
  }
141
- if (allowRedispatch && parentAggregationFlat.length) {
142
- let minStart = Number.POSITIVE_INFINITY;
143
- let maxEnd = Number.NEGATIVE_INFINITY;
144
- for (const feat of parentAggregationFlat) {
145
- if (feat.start < minStart) {
146
- minStart = feat.start;
147
- }
148
- if (feat.end > maxEnd) {
149
- maxEnd = feat.end;
150
- }
151
- }
152
- if (maxEnd > query.end || minStart < query.start) {
140
+ if (allowRedispatch && maxAggEnd > -Infinity) {
141
+ if (maxAggEnd > query.end || minAggStart < query.start) {
142
+ // redispatch re-fetches a superset region, so emitting singletons
143
+ // here too would double-emit them; defer emission to the recursion
153
144
  await this.getFeaturesHelper({
154
145
  query: {
155
146
  ...query,
156
- start: minStart - 500_000,
157
- end: maxEnd + 500_000,
147
+ // extend query to catch gene subfeatures outside the current view;
148
+ // 500 kbp heuristic covers most genes/transcripts. Clamped at 0 so
149
+ // a gene near the start of a contig doesn't produce a negative
150
+ // region start.
151
+ start: Math.max(0, minAggStart - 500_000),
152
+ end: maxAggEnd + 500_000,
158
153
  },
159
154
  opts,
160
155
  observer,
@@ -164,63 +159,60 @@ export default class BigBedAdapter extends BaseFeatureDataAdapter {
164
159
  return;
165
160
  }
166
161
  }
167
- Object.entries(parentAggregation).map(([name, subfeatures]) => {
168
- const s = min(subfeatures.map(f => f.start));
169
- const e = max(subfeatures.map(f => f.end));
170
- if (doesIntersect2(s, e, originalQuery.start, originalQuery.end)) {
171
- const subs = subfeatures.sort((a, b) => a.uniqueId.localeCompare(b.uniqueId));
172
- if (subs.some((a, i) => subs.some((b, j) => i !== j && doesIntersect2(a.start, a.end, b.start, b.end)))) {
162
+ for (const f of singletons) {
163
+ if (doesIntersect2(f.start, f.end, originalQuery.start, originalQuery.end)) {
164
+ observer.next(new SimpleFeature({
165
+ id: `${this.id}-${f.uniqueId}`,
166
+ data: f,
167
+ }));
168
+ }
169
+ }
170
+ for (const [name, subfeatures] of Object.entries(parentAggregation)) {
171
+ const subs = subfeatures.sort((a, b) => a.uniqueId.localeCompare(b.uniqueId));
172
+ const sortedByStart = [...subs].sort((a, b) => a.start - b.start);
173
+ const hasOverlaps = sortedByStart.some((f, i) => i > 0 && sortedByStart[i - 1].end > f.start);
174
+ // overlapping subs → one gene parent; non-overlapping → one parent per sub
175
+ // (handles bacterial GFF where two genes share a name but are distinct loci)
176
+ const groups = hasOverlaps ? [subs] : subs.map(sub => [sub]);
177
+ for (const group of groups) {
178
+ // gate on each parent's own extent: a distant non-overlapping locus
179
+ // sharing this name must not ride in on a sibling that intersects
180
+ const groupStart = min(group.map(f => f.start));
181
+ const groupEnd = max(group.map(f => f.end));
182
+ if (doesIntersect2(groupStart, groupEnd, originalQuery.start, originalQuery.end)) {
173
183
  observer.next(new SimpleFeature({
174
- id: `${this.id}-${subs[0]?.uniqueId}-parent`,
184
+ id: `${this.id}-${group[0].uniqueId}-parent`,
175
185
  data: {
176
186
  type: 'gene',
177
- subfeatures: subs,
178
- strand: subs[0]?.strand || 1,
187
+ subfeatures: group,
188
+ strand: group.find(f => f.strand !== 0)?.strand ?? 1,
179
189
  name,
180
- start: s,
181
- end: e,
190
+ start: groupStart,
191
+ end: groupEnd,
182
192
  refName: query.refName,
183
193
  },
184
194
  }));
185
195
  }
186
- else {
187
- for (const sub of subs) {
188
- observer.next(new SimpleFeature({
189
- id: `${this.id}-${sub.uniqueId}-parent`,
190
- data: {
191
- type: 'gene',
192
- subfeatures: [sub],
193
- strand: subs[0]?.strand || 1,
194
- name,
195
- start: sub.start,
196
- end: sub.end,
197
- refName: query.refName,
198
- },
199
- }));
200
- }
201
- }
202
196
  }
203
- });
197
+ }
204
198
  });
205
- observer.complete();
206
199
  }
207
200
  getFeatures(query, opts = {}) {
208
201
  return ObservableCreate(async (observer) => {
209
202
  try {
210
203
  await this.getFeaturesHelper({
211
- query: {
212
- ...query,
213
- start: query.start,
214
- end: query.end,
215
- },
204
+ query,
216
205
  opts,
217
206
  observer,
218
207
  allowRedispatch: true,
219
208
  });
209
+ // complete once here, not in the helper: a redispatch recurses into the
210
+ // helper, so completing there would fire complete() twice
211
+ observer.complete();
220
212
  }
221
213
  catch (e) {
222
214
  observer.error(e);
223
215
  }
224
- }, opts.stopToken);
216
+ }, opts.signal);
225
217
  }
226
218
  }
@@ -1,25 +1,47 @@
1
+ import type { Instance } from '@jbrowse/mobx-state-tree';
2
+ export declare function normalizeSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
3
+ /**
4
+ * #config BigBedAdapter
5
+ * #trackType FeatureTrack
6
+ * #fileFormat feature | BigBed
7
+ *
8
+ * #example
9
+ * ```js
10
+ * {
11
+ * type: 'BigBedAdapter',
12
+ * uri: 'https://example.com/features.bb',
13
+ * }
14
+ * ```
15
+ */
1
16
  declare const BigBedAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
2
- bigBedLocation: {
3
- type: string;
4
- defaultValue: {
5
- uri: string;
6
- locationType: string;
7
- };
17
+ readonly densityAdapter: {
18
+ type: "maybeFrozen";
8
19
  };
9
- scoreColumn: {
10
- type: string;
11
- description: string;
20
+ type: {
21
+ type: "string";
22
+ } & {
12
23
  defaultValue: string;
13
24
  };
14
- aggregateField: {
15
- type: string;
16
- description: string;
25
+ readonly bigBedLocation: {
26
+ type: "fileLocation";
27
+ } & {
28
+ defaultValue: unknown;
29
+ };
30
+ readonly scoreColumn: {
31
+ type: "string";
32
+ } & {
33
+ defaultValue: string;
34
+ };
35
+ readonly aggregateField: {
36
+ type: "string";
37
+ } & {
17
38
  defaultValue: string;
18
39
  };
19
- disableGeneHeuristic: {
20
- type: string;
21
- description: string;
40
+ readonly disableGeneHeuristic: {
41
+ type: "boolean";
42
+ } & {
22
43
  defaultValue: boolean;
23
44
  };
24
- }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
45
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, true, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
46
+ export type BigBedAdapterConfig = Instance<typeof BigBedAdapter>;
25
47
  export default BigBedAdapter;
@@ -1,6 +1,27 @@
1
- import { ConfigurationSchema } from '@jbrowse/core/configuration';
2
- function x() { }
1
+ import { ConfigurationSchema, expandUriShorthand, } from '@jbrowse/core/configuration';
2
+ import { densityAdapterConfigSchemaFields } from '@jbrowse/core/data_adapters/BaseAdapter';
3
+ export function normalizeSnapshot(snap) {
4
+ return expandUriShorthand(snap, 'bigBedLocation');
5
+ }
6
+ /**
7
+ * #config BigBedAdapter
8
+ * #trackType FeatureTrack
9
+ * #fileFormat feature | BigBed
10
+ *
11
+ * #example
12
+ * ```js
13
+ * {
14
+ * type: 'BigBedAdapter',
15
+ * uri: 'https://example.com/features.bb',
16
+ * }
17
+ * ```
18
+ */
3
19
  const BigBedAdapter = ConfigurationSchema('BigBedAdapter', {
20
+ /**
21
+ * #slot
22
+ * location of the BigBed file. It carries its own index and summary zoom
23
+ * levels, so there is no separate index to configure.
24
+ */
4
25
  bigBedLocation: {
5
26
  type: 'fileLocation',
6
27
  defaultValue: {
@@ -8,33 +29,45 @@ const BigBedAdapter = ConfigurationSchema('BigBedAdapter', {
8
29
  locationType: 'UriLocation',
9
30
  },
10
31
  },
32
+ /**
33
+ * #slot
34
+ */
11
35
  scoreColumn: {
12
36
  type: 'string',
13
37
  description: 'The column to use as a "score" attribute',
14
38
  defaultValue: '',
15
39
  },
40
+ /**
41
+ * #slot
42
+ */
16
43
  aggregateField: {
17
44
  type: 'string',
18
45
  description: 'An attribute to aggregate features with',
19
46
  defaultValue: 'geneName2',
20
47
  },
48
+ /**
49
+ * #slot
50
+ */
21
51
  disableGeneHeuristic: {
22
52
  type: 'boolean',
23
53
  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)',
24
54
  defaultValue: false,
25
55
  },
56
+ ...densityAdapterConfigSchemaFields,
26
57
  }, {
27
58
  explicitlyTyped: true,
28
- preProcessSnapshot: snap => {
29
- return snap.uri
30
- ? {
31
- ...snap,
32
- bigBedLocation: {
33
- uri: snap.uri,
34
- baseUri: snap.baseUri,
35
- },
36
- }
37
- : snap;
38
- },
59
+ /**
60
+ * #preProcessSnapshot
61
+ *
62
+ *
63
+ * preprocessor to allow minimal config:
64
+ * ```json
65
+ * {
66
+ * "type": "BigBedAdapter",
67
+ * "uri": "yourfile.bigBed"
68
+ * }
69
+ * ```
70
+ */
71
+ preProcessSnapshot: normalizeSnapshot,
39
72
  });
40
73
  export default BigBedAdapter;