@jbrowse/plugin-bed 5.0.0-beta.1 → 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 (45) hide show
  1. package/esm/BedAdapter/BedAdapter.d.ts +1 -3
  2. package/esm/BedAdapter/BedAdapter.js +3 -10
  3. package/esm/BedAdapter/configSchema.d.ts +30 -52
  4. package/esm/BedAdapter/configSchema.js +2 -4
  5. package/esm/BedGraphAdapter/BedGraphAdapter.d.ts +1 -3
  6. package/esm/BedGraphAdapter/BedGraphAdapter.js +8 -10
  7. package/esm/BedGraphAdapter/configSchema.d.ts +15 -17
  8. package/esm/BedGraphAdapter/configSchema.js +5 -10
  9. package/esm/BedGraphTabixAdapter/BedGraphTabixAdapter.js +8 -5
  10. package/esm/BedGraphTabixAdapter/configSchema.d.ts +25 -30
  11. package/esm/BedGraphTabixAdapter/configSchema.js +11 -3
  12. package/esm/BedTabixAdapter/BedTabixAdapter.js +7 -8
  13. package/esm/BedTabixAdapter/configSchema.d.ts +33 -46
  14. package/esm/BedTabixAdapter/configSchema.js +9 -2
  15. package/esm/BedpeAdapter/BedpeAdapter.d.ts +3 -4
  16. package/esm/BedpeAdapter/BedpeAdapter.js +3 -10
  17. package/esm/BedpeAdapter/configSchema.d.ts +12 -16
  18. package/esm/BedpeAdapter/configSchema.js +2 -11
  19. package/esm/BedpeAdapter/util.js +3 -2
  20. package/esm/BigBedAdapter/BigBedAdapter.js +4 -5
  21. package/esm/BigBedAdapter/configSchema.d.ts +21 -29
  22. package/esm/BigBedAdapter/configSchema.js +4 -10
  23. package/esm/BigBedTextSearchAdapter/BigBedTextSearchAdapter.d.ts +27 -0
  24. package/esm/BigBedTextSearchAdapter/BigBedTextSearchAdapter.js +128 -0
  25. package/esm/BigBedTextSearchAdapter/configSchema.d.ts +51 -0
  26. package/esm/BigBedTextSearchAdapter/configSchema.js +57 -0
  27. package/esm/BigBedTextSearchAdapter/index.d.ts +2 -0
  28. package/esm/BigBedTextSearchAdapter/index.js +11 -0
  29. package/esm/StarFusionAdapter/StarFusionAdapter.d.ts +2 -3
  30. package/esm/StarFusionAdapter/StarFusionAdapter.js +16 -71
  31. package/esm/StarFusionAdapter/configSchema.d.ts +9 -10
  32. package/esm/StarFusionAdapter/configSchema.js +2 -11
  33. package/esm/adapterUtil.d.ts +1 -1
  34. package/esm/adapterUtil.js +20 -5
  35. package/esm/bedGraphUtil.d.ts +2 -1
  36. package/esm/bedGraphUtil.js +5 -3
  37. package/esm/copyExcept.d.ts +1 -0
  38. package/esm/copyExcept.js +11 -0
  39. package/esm/generateBedMethylFeature.js +12 -7
  40. package/esm/generateRepeatMaskerFeature.d.ts +17 -4
  41. package/esm/generateRepeatMaskerFeature.js +29 -26
  42. package/esm/generateUcscTranscript.js +51 -4
  43. package/esm/index.js +2 -0
  44. package/esm/util.js +25 -14
  45. package/package.json +8 -7
@@ -2,44 +2,10 @@ import { BaseFeatureDataAdapter, cachedSetup, } from '@jbrowse/core/data_adapter
2
2
  import { SimpleFeature, fetchAndMaybeUnzip } from '@jbrowse/core/util';
3
3
  import { openLocation } from '@jbrowse/core/util/io';
4
4
  import { parseLineByLine } from '@jbrowse/core/util/parseLineByLine';
5
+ import { parseStarFusionBreakpoint, starFusionColumns, } from '@jbrowse/core/util/starFusion';
5
6
  import { buildPairedIntervalTree, intervalTreeFeatures, } from '../adapterUtil.js';
6
- /**
7
- * A STAR-Fusion `refName:pos:strand` breakpoint, read from the RIGHT: a refName
8
- * may contain colons. GRCh38's full analysis set names its HLA contigs
9
- * `HLA-A*01:01:01:01`, so a breakpoint on one arrives as
10
- * `HLA-A*01:01:01:01:5000:+` and splitting at the first colon read the contig as
11
- * `HLA-A*01`, the position as 1 and the strand as absent. Same rule
12
- * `parseSvAlt` applies to a BND mate locstring, one field further along.
13
- *
14
- * A string with fewer than three fields keeps what it always parsed to, so a
15
- * truncated or strandless row is no worse off than before.
16
- */
17
- function parseBreakpoint(str) {
18
- const parts = str.split(':');
19
- const strandStr = parts.length >= 3 ? parts.pop() : undefined;
20
- const pos = parts.length >= 2 ? +parts.pop() : Number.NaN;
21
- return {
22
- refName: parts.join(':'),
23
- start: pos,
24
- end: pos + 1,
25
- strand: strandStr === '+' ? 1 : strandStr === '-' ? -1 : undefined,
26
- };
27
- }
28
- /**
29
- * Which way the sequence a breakpoint keeps runs from it, as the paired-arc
30
- * display's mate-direction tick (1 = right, -1 = left, 0 = unknown). A
31
- * breakpoint's strand in STAR-Fusion is its gene's transcription strand, and the
32
- * fusion transcript keeps the donor's sequence 5' of the junction and the
33
- * acceptor's 3' of it: so on a + strand donor the retained side is at lower
34
- * coordinates and on a + strand acceptor it is at higher ones.
35
- */
36
- function tickDirection(strand, isDonor) {
37
- return strand === undefined ? 0 : isDonor ? -strand : strand;
38
- }
39
7
  export default class StarFusionAdapter extends BaseFeatureDataAdapter {
40
- // No `label`: `fetchAndMaybeUnzip` narrates the download from inside.
41
8
  loadData = cachedSetup({ setup: opts => this.loadDataP(opts) });
42
- intervalTrees = {};
43
9
  static capabilities = ['getFeatures', 'getRefNames'];
44
10
  async loadDataP(opts) {
45
11
  const buffer = await fetchAndMaybeUnzip(openLocation(this.getConf('starFusionLocation'), this.pluginManager), opts);
@@ -48,23 +14,19 @@ export default class StarFusionAdapter extends BaseFeatureDataAdapter {
48
14
  let rightIdx = -1;
49
15
  const feats1 = {};
50
16
  const feats2 = {};
51
- parseLineByLine(buffer, line => {
52
- if (line.startsWith('#')) {
53
- columnNames = line.slice(1).split('\t');
17
+ parseLineByLine(buffer, (line, lineIndex) => {
18
+ if (lineIndex === 0) {
19
+ columnNames = starFusionColumns(line);
54
20
  leftIdx = columnNames.indexOf('LeftBreakpoint');
55
21
  rightIdx = columnNames.indexOf('RightBreakpoint');
56
22
  }
57
- else if (leftIdx >= 0 && rightIdx >= 0) {
23
+ else if (!line.startsWith('#')) {
58
24
  const cols = line.split('\t');
59
25
  const left = cols[leftIdx];
60
26
  const right = cols[rightIdx];
61
- // through parseBreakpoint, so the bucket a row is filed under is the
62
- // refName its feature will report -- a colon-bearing contig split two
63
- // ways puts the row in a bucket no query can reach
64
- const leftRef = left ? parseBreakpoint(left).refName : undefined;
65
- const rightRef = right ? parseBreakpoint(right).refName : undefined;
66
- if (leftRef && rightRef) {
67
- ;
27
+ if (left && right) {
28
+ const leftRef = parseStarFusionBreakpoint(left, true).refName;
29
+ const rightRef = parseStarFusionBreakpoint(right, false).refName;
68
30
  (feats1[leftRef] ??= []).push(line);
69
31
  (feats2[rightRef] ??= []).push(line);
70
32
  }
@@ -80,27 +42,16 @@ export default class StarFusionAdapter extends BaseFeatureDataAdapter {
80
42
  featureFromLine(line, columnNames, uniqueId, flip) {
81
43
  const cols = line.split('\t');
82
44
  const row = Object.fromEntries(columnNames.map((name, i) => [name, cols[i]]));
83
- const primaryBp = parseBreakpoint((flip ? row.RightBreakpoint : row.LeftBreakpoint));
84
- const mateBp = parseBreakpoint((flip ? row.LeftBreakpoint : row.RightBreakpoint));
85
- // the LeftBreakpoint is the donor, so this feature is the donor end of the
86
- // junction whenever it is not flipped, and the mate is the other end
45
+ const donor = parseStarFusionBreakpoint(row.LeftBreakpoint, true);
46
+ const acceptor = parseStarFusionBreakpoint(row.RightBreakpoint, false);
47
+ const [primary, mate] = flip ? [acceptor, donor] : [donor, acceptor];
87
48
  return new SimpleFeature({
88
49
  uniqueId,
89
- refName: primaryBp.refName,
90
- start: primaryBp.start,
91
- end: primaryBp.end,
92
- strand: primaryBp.strand,
93
- mateDirection: tickDirection(primaryBp.strand, !flip),
50
+ ...primary,
94
51
  name: row.FusionName,
95
52
  score: row.JunctionReadCount ? +row.JunctionReadCount : undefined,
96
53
  type: 'fusion',
97
- mate: {
98
- refName: mateBp.refName,
99
- start: mateBp.start,
100
- end: mateBp.end,
101
- strand: mateBp.strand,
102
- mateDirection: tickDirection(mateBp.strand, flip),
103
- },
54
+ mate,
104
55
  ...row,
105
56
  });
106
57
  }
@@ -108,14 +59,8 @@ export default class StarFusionAdapter extends BaseFeatureDataAdapter {
108
59
  const { columnNames, feats1, feats2 } = await this.loadData();
109
60
  return buildPairedIntervalTree(feats1, feats2, refName, this.id, (line, uniqueId, flip) => this.featureFromLine(line, columnNames, uniqueId, flip));
110
61
  }
111
- async loadFeatureTree(refName) {
112
- this.intervalTrees[refName] ??= this.loadFeatureTreeP(refName).catch((e) => {
113
- this.intervalTrees[refName] = undefined;
114
- throw e;
115
- });
116
- return this.intervalTrees[refName];
117
- }
118
- getFeatures(query, opts = {}) {
119
- return intervalTreeFeatures(query, opts, refName => this.loadFeatureTree(refName));
62
+ treeFeatures = intervalTreeFeatures(opts => this.loadData(opts), refName => this.loadFeatureTreeP(refName));
63
+ getFeatures(query, opts) {
64
+ return this.treeFeatures(query, opts);
120
65
  }
121
66
  }
@@ -15,17 +15,16 @@ export declare function normalizeSnapshot(snap: Record<string, unknown>): Record
15
15
  * ```
16
16
  */
17
17
  declare const StarFusionAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
18
- /**
19
- * #slot
20
- */
18
+ type: {
19
+ type: "string";
20
+ } & {
21
+ defaultValue: string;
22
+ };
21
23
  readonly starFusionLocation: {
22
- readonly type: "fileLocation";
23
- readonly description: "STAR-Fusion TSV output file (plain text or gzipped)";
24
- readonly defaultValue: {
25
- readonly uri: "/path/to/star-fusion.fusion_predictions.tsv";
26
- readonly locationType: "UriLocation";
27
- };
24
+ type: "fileLocation";
25
+ } & {
26
+ defaultValue: unknown;
28
27
  };
29
- }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
28
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, true, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
30
29
  export type StarFusionAdapterConfig = Instance<typeof StarFusionAdapter>;
31
30
  export default StarFusionAdapter;
@@ -1,15 +1,6 @@
1
- import { ConfigurationSchema } from '@jbrowse/core/configuration';
1
+ import { ConfigurationSchema, expandUriShorthand, } from '@jbrowse/core/configuration';
2
2
  export function normalizeSnapshot(snap) {
3
- return snap.uri
4
- ? {
5
- ...snap,
6
- starFusionLocation: {
7
- uri: snap.uri,
8
- baseUri: snap.baseUri,
9
- locationType: 'UriLocation',
10
- },
11
- }
12
- : snap;
3
+ return expandUriShorthand(snap, 'starFusionLocation');
13
4
  }
14
5
  /**
15
6
  * #config StarFusionAdapter
@@ -1,5 +1,5 @@
1
1
  import { IntervalTree } from '@jbrowse/core/util';
2
2
  import type { BaseOptions } from '@jbrowse/core/data_adapters/BaseAdapter';
3
3
  import type { Feature, Region } from '@jbrowse/core/util';
4
- export declare function intervalTreeFeatures(query: Region, opts: BaseOptions, loadTree: (refName: string) => Promise<IntervalTree<Feature> | undefined>): import("rxjs").Observable<Feature>;
4
+ export declare function intervalTreeFeatures(loadData: (opts: BaseOptions) => Promise<unknown>, buildTree: (refName: string) => Promise<IntervalTree<Feature> | undefined>): (query: Region, opts?: BaseOptions) => import("rxjs").Observable<Feature>;
5
5
  export declare function buildPairedIntervalTree(feats1: Record<string, string[]>, feats2: Record<string, string[]>, refName: string, idPrefix: string, makeFeature: (line: string, uniqueId: string, flip: boolean) => Feature): IntervalTree<Feature>;
@@ -1,15 +1,30 @@
1
1
  import { IntervalTree } from '@jbrowse/core/util';
2
2
  import { ObservableCreate } from '@jbrowse/core/util/rxjs';
3
- // Emit the features of a per-refName interval tree that intersect the query.
4
- // Shared by the in-memory interval-tree adapters.
5
- export function intervalTreeFeatures(query, opts, loadTree) {
6
- return ObservableCreate(async (observer) => {
3
+ // `getFeatures` for an in-memory interval-tree adapter: one tree per refName,
4
+ // built once and rebuilt after a failure. `loadData` is awaited with each
5
+ // caller's opts ahead of the tree, so every fetch joins the whole-file load's
6
+ // progress rather than only the one that started it.
7
+ export function intervalTreeFeatures(loadData, buildTree) {
8
+ const trees = new Map();
9
+ const loadTree = (refName) => {
10
+ let tree = trees.get(refName);
11
+ if (tree === undefined) {
12
+ tree = buildTree(refName).catch((e) => {
13
+ trees.delete(refName);
14
+ throw e;
15
+ });
16
+ trees.set(refName, tree);
17
+ }
18
+ return tree;
19
+ };
20
+ return (query, opts = {}) => ObservableCreate(async (observer) => {
21
+ await loadData(opts);
7
22
  const tree = await loadTree(query.refName);
8
23
  for (const f of tree?.search([query.start, query.end]) ?? []) {
9
24
  observer.next(f);
10
25
  }
11
26
  observer.complete();
12
- }, opts.stopToken);
27
+ }, opts.signal);
13
28
  }
14
29
  // Build one interval tree for a refName from paired-feature buckets, inserting
15
30
  // the records that start on this ref (feats1, flip=false) and those whose mate
@@ -1,5 +1,5 @@
1
1
  import type { Feature } from '@jbrowse/core/util';
2
- export declare function makeBedGraphFeature({ uniqueId, refName, start, end, names, j, value, }: {
2
+ export declare function makeBedGraphFeature({ uniqueId, refName, start, end, names, j, value, rowSource, }: {
3
3
  uniqueId: string;
4
4
  refName: string;
5
5
  start: number;
@@ -7,4 +7,5 @@ export declare function makeBedGraphFeature({ uniqueId, refName, start, end, nam
7
7
  names: string[];
8
8
  j: number;
9
9
  value: string;
10
+ rowSource?: string;
10
11
  }): Feature | undefined;
@@ -1,7 +1,9 @@
1
1
  import { SimpleFeature } from '@jbrowse/core/util';
2
2
  // Build a single-score bedGraph feature for value column j, or undefined when
3
- // the value is not numeric. Shared by BedGraphAdapter and BedGraphTabixAdapter.
4
- export function makeBedGraphFeature({ uniqueId, refName, start, end, names, j, value, }) {
3
+ // the value is not numeric. A wide file names each value column's source in
4
+ // its header; a tidy one has a `source` column naming each row's instead.
5
+ // Shared by BedGraphAdapter and BedGraphTabixAdapter.
6
+ export function makeBedGraphFeature({ uniqueId, refName, start, end, names, j, value, rowSource, }) {
5
7
  const score = +value;
6
8
  return Number.isNaN(score)
7
9
  ? undefined
@@ -12,7 +14,7 @@ export function makeBedGraphFeature({ uniqueId, refName, start, end, names, j, v
12
14
  start,
13
15
  end,
14
16
  score,
15
- source: names[j] || `col${j}`,
17
+ source: rowSource ?? (names[j] || `col${j}`),
16
18
  },
17
19
  });
18
20
  }
@@ -0,0 +1 @@
1
+ export declare function copyExcept(data: Record<string, unknown>, excluded: ReadonlySet<string>): Record<string, unknown>;
@@ -0,0 +1,11 @@
1
+ // `const { a, b, ...rest } = data` in the same key order, which measured
2
+ // slower on every BED line shape (benches/featureData.bench.ts)
3
+ export function copyExcept(data, excluded) {
4
+ const out = {};
5
+ for (const key in data) {
6
+ if (!excluded.has(key)) {
7
+ out[key] = data[key];
8
+ }
9
+ }
10
+ return out;
11
+ }
@@ -1,14 +1,19 @@
1
1
  import { parseStrand } from './util.js';
2
2
  // this uses modkit bedMethyl. unclear how to reliably detect minimal 9+2 bedMethyl
3
3
  export function isBedMethylFeature({ splitLine, start, end, }) {
4
+ if (splitLine.length < 18 ||
5
+ +splitLine[6] !== start ||
6
+ +splitLine[7] !== end) {
7
+ return false;
8
+ }
4
9
  // columns 9-17 are the nine numeric methylation stats
5
- const nums = splitLine.slice(9, 18);
6
- return (splitLine[6] !== undefined &&
7
- +splitLine[6] === start &&
8
- splitLine[7] !== undefined &&
9
- +splitLine[7] === end &&
10
- nums.length === 9 &&
11
- nums.every(x => x && !Number.isNaN(+x)));
10
+ for (let i = 9; i < 18; i++) {
11
+ const x = splitLine[i];
12
+ if (!x || Number.isNaN(+x)) {
13
+ return false;
14
+ }
15
+ }
16
+ return true;
12
17
  }
13
18
  export function generateBedMethylFeature({ splitLine, uniqueId, refName, start, end, }) {
14
19
  // see
@@ -1,4 +1,17 @@
1
- declare const FIELDS: readonly ['bitsw_score', 'percent_div', 'percent_del', 'percent_ins', 'query_chr', 'query_begin', 'query_end', 'query_remaining', 'orientation', 'matching_repeat_name', 'matching_repeat_class', 'matching_repeat_begin', 'matching_repeat_end', 'matching_repeat_remaining', 'repeat_id'];
2
- export type RepeatMaskerFields = Record<(typeof FIELDS)[number], string>;
3
- export declare function parseRepeatMaskerDescription(desc: unknown): RepeatMaskerFields | undefined;
4
- export {};
1
+ export declare function parseRepeatMaskerDescription(desc: unknown): {
2
+ bitsw_score: string;
3
+ percent_div: string;
4
+ percent_del: string;
5
+ percent_ins: string;
6
+ query_chr: string;
7
+ query_begin: string;
8
+ query_end: string;
9
+ query_remaining: string;
10
+ orientation: string;
11
+ matching_repeat_name: string;
12
+ matching_repeat_class: string;
13
+ matching_repeat_begin: string;
14
+ matching_repeat_end: string;
15
+ matching_repeat_remaining: string;
16
+ repeat_id: string;
17
+ } | undefined;
@@ -1,30 +1,33 @@
1
- const FIELDS = [
2
- 'bitsw_score',
3
- 'percent_div',
4
- 'percent_del',
5
- 'percent_ins',
6
- 'query_chr',
7
- 'query_begin',
8
- 'query_end',
9
- 'query_remaining',
10
- 'orientation',
11
- 'matching_repeat_name',
12
- 'matching_repeat_class',
13
- 'matching_repeat_begin',
14
- 'matching_repeat_end',
15
- 'matching_repeat_remaining',
16
- 'repeat_id',
17
- ];
18
- // RepeatMasker .out-derived description: 15 space-separated fields with numeric
19
- // values at positions 0,1,2,3,5,6. Returns parsed fields or undefined if the
20
- // description doesn't match the format.
1
+ const NUMERIC_PARTS = [0, 1, 2, 3, 5, 6];
2
+ // RepeatMasker .out-derived description: 15 space-separated fields, numeric at
3
+ // positions 0,1,2,3,5,6. A literal rather than an object built from the field
4
+ // list, since this runs on every line of a RepeatMasker track.
21
5
  export function parseRepeatMaskerDescription(desc) {
22
- if (typeof desc === 'string') {
23
- const parts = desc.trim().split(' ');
24
- const looksRight = [0, 1, 2, 3, 5, 6].every(i => parts[i] !== undefined && !Number.isNaN(+parts[i]));
25
- if (looksRight) {
26
- return Object.fromEntries(FIELDS.map((f, i) => [f, parts[i] ?? '']));
6
+ if (typeof desc !== 'string') {
7
+ return undefined;
8
+ }
9
+ const p = desc.trim().split(' ');
10
+ for (const i of NUMERIC_PARTS) {
11
+ const v = p[i];
12
+ if (v === undefined || Number.isNaN(+v)) {
13
+ return undefined;
27
14
  }
28
15
  }
29
- return undefined;
16
+ return {
17
+ bitsw_score: p[0] ?? '',
18
+ percent_div: p[1] ?? '',
19
+ percent_del: p[2] ?? '',
20
+ percent_ins: p[3] ?? '',
21
+ query_chr: p[4] ?? '',
22
+ query_begin: p[5] ?? '',
23
+ query_end: p[6] ?? '',
24
+ query_remaining: p[7] ?? '',
25
+ orientation: p[8] ?? '',
26
+ matching_repeat_name: p[9] ?? '',
27
+ matching_repeat_class: p[10] ?? '',
28
+ matching_repeat_begin: p[11] ?? '',
29
+ matching_repeat_end: p[12] ?? '',
30
+ matching_repeat_remaining: p[13] ?? '',
31
+ repeat_id: p[14] ?? '',
32
+ };
30
33
  }
@@ -1,9 +1,24 @@
1
- // type guard: narrows input fields to required + non-zero when true
1
+ import { copyExcept } from './copyExcept.js';
2
+ // type guard: narrows input fields to required when true
2
3
  // strand===0 means unstranded — likely not a gene
4
+ //
5
+ // Blocks are what make a transcript, and UCSC's own format definition calls
6
+ // them exons. Thickness says whether the transcript CODES, which is a separate
7
+ // axis: an lncRNA, a spliced EST and any other 12-column feature with
8
+ // thickStart===thickEnd is a transcript by every structural measure, so it is
9
+ // not tested here. `generateUcscTranscript` reads the same equality to emit
10
+ // exons and no CDS.
11
+ //
12
+ // This does conflate a spliced alignment with a transcript, deliberately: a
13
+ // bigPsl track (`est`, `intronEst`, `mrna`, `xenoMrna`, IsoSeq) has an empty
14
+ // thick range too, so it promotes where the same alignment as GFF3
15
+ // `cDNA_match` does not. BED12 carries no column that separates one from an
16
+ // lncRNA, and the promotion stays honest without one — no CDS children, so the
17
+ // sequence panel withholds CDS and protein and HGVS stays `n.`, and the type
18
+ // is `transcript` rather than `mRNA`.
3
19
  export function isUcscTranscript(input) {
4
20
  return (input.thickStart !== undefined &&
5
21
  input.thickEnd !== undefined &&
6
- input.thickStart !== input.thickEnd &&
7
22
  input.blockCount !== undefined &&
8
23
  input.blockCount > 0 &&
9
24
  input.strand !== undefined &&
@@ -32,8 +47,26 @@ function parseFrames(frames) {
32
47
  ? frames.replace(/,$/, '').split(',').map(Number)
33
48
  : frames;
34
49
  }
50
+ const TRANSCRIPT_OWN = new Set([
51
+ 'strand',
52
+ 'uniqueId',
53
+ 'start',
54
+ 'end',
55
+ 'thickStart',
56
+ 'thickEnd',
57
+ 'refName',
58
+ 'subfeatures',
59
+ 'chrom',
60
+ 'chromStart',
61
+ 'chromEnd',
62
+ 'chromStarts',
63
+ 'blockStarts',
64
+ 'blockSizes',
65
+ 'blockCount',
66
+ ]);
35
67
  export function generateUcscTranscript(data) {
36
- const { strand, uniqueId, start, end, thickStart, thickEnd, refName, subfeatures: oldSubfeatures, chrom, chromStart, chromEnd, chromStarts, blockStarts, blockSizes, blockCount, ...rest } = data;
68
+ const { strand, uniqueId, start, end, thickStart, thickEnd, refName, subfeatures: oldSubfeatures, } = data;
69
+ const rest = copyExcept(data, TRANSCRIPT_OWN);
37
70
  // exonFrames from bigGenePred - the @gmod/bed parser returns it in genomic order.
38
71
  // _exonFrames fallback supports BED files that use the underscore-prefixed column
39
72
  // name, which the parser leaves as raw text because it is not a standard column.
@@ -43,8 +76,22 @@ export function generateUcscTranscript(data) {
43
76
  .sort((a, b) => a.start - b.start);
44
77
  const fiveUTR = strand > 0 ? 'five_prime_UTR' : 'three_prime_UTR';
45
78
  const threeUTR = strand > 0 ? 'three_prime_UTR' : 'five_prime_UTR';
79
+ // thickStart===thickEnd is UCSC for no coding region, and the block walk
80
+ // below cannot express that: a thick point inside a block would emit a
81
+ // zero-length CDS carrying a phase, and one outside every block would make
82
+ // the whole transcript UTR.
83
+ //
84
+ // cdsStartStat and cdsEndStat `none` alone do not mean non-coding: UCSC's
85
+ // GENCODE bigGenePred (gencodeV44.bb through V50) writes `none` on every
86
+ // transcript, and reading it as non-coding stripped the CDS from all 370,886
87
+ // of V50's coding ones, TP53 included. exonFrames tells them apart there:
88
+ // every coding transcript has a block with a frame, every other one has -1
89
+ // throughout.
46
90
  const { cdsEndStat, cdsStartStat } = rest;
47
- if (cdsStartStat === 'none' && cdsEndStat === 'none') {
91
+ if (thickStart === thickEnd ||
92
+ (cdsStartStat === 'none' &&
93
+ cdsEndStat === 'none' &&
94
+ !exonFrames?.some(frame => frame >= 0))) {
48
95
  return {
49
96
  ...rest,
50
97
  uniqueId,
package/esm/index.js CHANGED
@@ -5,6 +5,7 @@ import BedGraphTabixAdapterF from './BedGraphTabixAdapter/index.js';
5
5
  import BedTabixAdapterF from './BedTabixAdapter/index.js';
6
6
  import BedpeAdapterF from './BedpeAdapter/index.js';
7
7
  import BigBedAdapterF from './BigBedAdapter/index.js';
8
+ import BigBedTextSearchAdapterF from './BigBedTextSearchAdapter/index.js';
8
9
  import StarFusionAdapterF from './StarFusionAdapter/index.js';
9
10
  export { default as BedTabixAdapter } from './BedTabixAdapter/BedTabixAdapter.js';
10
11
  export { default as bedTabixConfigSchema } from './BedTabixAdapter/configSchema.js';
@@ -12,6 +13,7 @@ export default class BedPlugin extends Plugin {
12
13
  name = 'BedPlugin';
13
14
  install(pluginManager) {
14
15
  BigBedAdapterF(pluginManager);
16
+ BigBedTextSearchAdapterF(pluginManager);
15
17
  BedAdapterF(pluginManager);
16
18
  BedpeAdapterF(pluginManager);
17
19
  StarFusionAdapterF(pluginManager);
package/esm/util.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import BED from '@gmod/bed';
2
2
  import { parseLineByLine } from '@jbrowse/core/util/parseLineByLine';
3
+ import { copyExcept } from './copyExcept.js';
3
4
  import { generateBedMethylFeature, isBedMethylFeature, } from './generateBedMethylFeature.js';
4
5
  import { generateRastairMethylFeature, isRastairMethylFeature, } from './generateRastairMethylFeature.js';
5
6
  import { parseRepeatMaskerDescription } from './generateRepeatMaskerFeature.js';
@@ -99,6 +100,17 @@ export function parseStrand(strand) {
99
100
  }
100
101
  return 0;
101
102
  }
103
+ const BED_OWN = new Set(['strand', 'score', 'chrom', 'chromStart', 'chromEnd']);
104
+ const REPEAT_OWN = new Set([
105
+ ...BED_OWN,
106
+ 'description',
107
+ 'chromStarts',
108
+ 'blockSizes',
109
+ 'blockStarts',
110
+ 'blockCount',
111
+ 'thickStart',
112
+ 'thickEnd',
113
+ ]);
102
114
  export function featureData({ splitLine, refName, start, end, parser, uniqueId, scoreColumn, names, disableGeneHeuristic, }) {
103
115
  // bedMethyl detection runs on raw splitLine before parsing
104
116
  if (isBedMethylFeature({ splitLine, start, end })) {
@@ -122,15 +134,13 @@ export function featureData({ splitLine, refName, start, end, parser, uniqueId,
122
134
  });
123
135
  }
124
136
  const data = parser.parseLine(splitLine, { uniqueId });
125
- const { strand: strandRaw, score: scoreRaw, chrom, chromStart, chromEnd, ...rest } = data;
126
- const strand = parseStrand(strandRaw);
127
- const rawScore = scoreColumn ? data[scoreColumn] : scoreRaw;
137
+ const strand = parseStrand(data.strand);
138
+ const rawScore = scoreColumn ? data[scoreColumn] : data.score;
128
139
  const score = rawScore === undefined ? undefined : Number(rawScore);
129
- const repeat = parseRepeatMaskerDescription(rest.description);
140
+ const repeat = parseRepeatMaskerDescription(data.description);
130
141
  if (repeat) {
131
- const { description, chromStarts, blockSizes, blockStarts, blockCount, thickStart, thickEnd, ...rest2 } = rest;
132
142
  return {
133
- ...rest2,
143
+ ...copyExcept(data, REPEAT_OWN),
134
144
  ...repeat,
135
145
  uniqueId,
136
146
  score,
@@ -140,22 +150,23 @@ export function featureData({ splitLine, refName, start, end, parser, uniqueId,
140
150
  refName,
141
151
  };
142
152
  }
143
- const subfeatures = rest.blockCount
153
+ const rest = copyExcept(data, BED_OWN);
154
+ const subfeatures = data.blockCount
144
155
  ? makeBlocks({
145
156
  start,
146
157
  uniqueId,
147
158
  refName,
148
- chromStarts: rest.chromStarts,
149
- blockCount: rest.blockCount,
150
- blockSizes: rest.blockSizes,
151
- blockStarts: rest.blockStarts,
159
+ chromStarts: data.chromStarts,
160
+ blockCount: data.blockCount,
161
+ blockSizes: data.blockSizes,
162
+ blockStarts: data.blockStarts,
152
163
  })
153
164
  : undefined;
154
165
  const transcriptCheck = {
155
166
  strand,
156
- blockCount: rest.blockCount,
157
- thickStart: rest.thickStart,
158
- thickEnd: rest.thickEnd,
167
+ blockCount: data.blockCount,
168
+ thickStart: data.thickStart,
169
+ thickEnd: data.thickEnd,
159
170
  };
160
171
  if (!disableGeneHeuristic &&
161
172
  subfeatures &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-bed",
3
- "version": "5.0.0-beta.1",
3
+ "version": "5.0.0-beta.10",
4
4
  "type": "module",
5
5
  "description": "JBrowse 2 bed adapters, tracks, etc.",
6
6
  "keywords": [
@@ -21,13 +21,14 @@
21
21
  "esm"
22
22
  ],
23
23
  "dependencies": {
24
- "@gmod/bbi": "^11.2.3",
25
- "@gmod/bed": "^2.3.0",
26
- "@gmod/tabix": "^3.8.2",
27
- "@jbrowse/mobx-state-tree": "^6.5.1",
28
- "mobx": "^7.0.3",
24
+ "@gmod/bbi": "^11.3.0",
25
+ "@gmod/bed": "^2.3.1",
26
+ "@gmod/tabix": "^3.8.3",
27
+ "@gmod/trix": "^3.0.22",
28
+ "@jbrowse/mobx-state-tree": "^6.6.1",
29
+ "mobx": "^7.0.5",
29
30
  "rxjs": "^7.8.2",
30
- "@jbrowse/core": "^5.0.0-beta.1"
31
+ "@jbrowse/core": "^5.0.0-beta.10"
31
32
  },
32
33
  "publishConfig": {
33
34
  "access": "public"