@jbrowse/plugin-trix 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.
@@ -5,10 +5,14 @@ import type PluginManager from '@jbrowse/core/PluginManager';
5
5
  import type { AnyConfigurationModel } from '@jbrowse/core/configuration';
6
6
  import type { BaseTextSearchAdapter, BaseTextSearchArgs } from '@jbrowse/core/data_adapters/BaseAdapter';
7
7
  import type { getSubAdapterType } from '@jbrowse/core/data_adapters/dataAdapterCache';
8
+ export declare function snippetAround(str: string, term: string, w?: number): string;
8
9
  export default class TrixTextSearchAdapter extends BaseAdapter implements BaseTextSearchAdapter {
9
- indexingAttributes?: string[];
10
10
  trixJs: Trix;
11
- tracksNames?: string[];
12
11
  constructor(config: AnyConfigurationModel, getSubAdapter?: getSubAdapterType, pluginManager?: PluginManager);
12
+ /**
13
+ * Returns list of results
14
+ * @param args - search options/arguments include: search query,
15
+ * searchType (prefix | full | exact), etc.
16
+ */
13
17
  searchIndex(args: BaseTextSearchArgs): Promise<BaseResult[]>;
14
18
  }
@@ -3,68 +3,100 @@ import BaseResult from '@jbrowse/core/TextSearch/BaseResults';
3
3
  import { readConfObject } from '@jbrowse/core/configuration';
4
4
  import { BaseAdapter } from '@jbrowse/core/data_adapters/BaseAdapter';
5
5
  import { openLocation } from '@jbrowse/core/util/io';
6
+ import { checkStopToken } from '@jbrowse/core/util/stopToken';
6
7
  function decodeURIComponentNoThrow(uri) {
7
8
  try {
8
9
  return decodeURIComponent(uri);
9
10
  }
10
- catch (e) {
11
+ catch {
11
12
  return uri;
12
13
  }
13
14
  }
14
- function shorten(str, term, w = 15) {
15
- const tidx = str.toLowerCase().indexOf(term);
16
- return str.length < 40
17
- ? str
18
- : (Math.max(0, tidx - w) > 0 ? '...' : '') +
19
- str.slice(Math.max(0, tidx - w), tidx + term.length + w).trim() +
20
- (tidx + term.length < str.length ? '...' : '');
15
+ // Build a search-result snippet: a window of `w` characters either side of
16
+ // where `term` matched, ellipsized on whichever side was cut. Nothing like the
17
+ // core `shorten`, which just elides a long name.
18
+ export function snippetAround(str, term, w = 15) {
19
+ if (str.length < 40) {
20
+ return str;
21
+ }
22
+ const tidx = str.toLowerCase().indexOf(term.toLowerCase());
23
+ if (tidx === -1) {
24
+ return `${str.slice(0, 40).trim()}...`;
25
+ }
26
+ const start = Math.max(0, tidx - w);
27
+ const end = tidx + term.length + w;
28
+ return ((start > 0 ? '...' : '') +
29
+ str.slice(start, end).trim() +
30
+ (end < str.length ? '...' : ''));
21
31
  }
22
32
  export default class TrixTextSearchAdapter extends BaseAdapter {
23
- indexingAttributes;
24
33
  trixJs;
25
- tracksNames;
26
34
  constructor(config, getSubAdapter, pluginManager) {
27
35
  super(config, getSubAdapter, pluginManager);
28
36
  const ixFilePath = readConfObject(config, 'ixFilePath');
29
37
  const ixxFilePath = readConfObject(config, 'ixxFilePath');
30
38
  if (!ixFilePath) {
31
- throw new Error('must provide out.ix');
39
+ throw new Error('TrixTextSearchAdapter requires an ixFilePath');
32
40
  }
33
41
  if (!ixxFilePath) {
34
- throw new Error('must provide out.ixx');
42
+ throw new Error('TrixTextSearchAdapter requires an ixxFilePath');
35
43
  }
36
44
  this.trixJs = new Trix(openLocation(ixxFilePath, pluginManager), openLocation(ixFilePath, pluginManager), 1500);
37
45
  }
46
+ /**
47
+ * Returns list of results
48
+ * @param args - search options/arguments include: search query,
49
+ * searchType (prefix | full | exact), etc.
50
+ */
38
51
  async searchIndex(args) {
39
- const query = args.queryString.toLowerCase();
40
- const strs = query.split(' ');
52
+ const query = args.queryString.toLowerCase().trim();
53
+ // any run of whitespace separates words; splitting on a single ' ' let a
54
+ // double space or a tab produce an empty word that matches everything
55
+ const words = query.split(/\s+/);
41
56
  const results = await this.trixJs.search(query);
57
+ // the index read has landed but nothing below it has run yet; a broad
58
+ // prefix returns thousands of records to JSON-parse, decode and snippet,
59
+ // and the keystroke that asked for them is 50ms stale by now
60
+ checkStopToken(args.stopToken);
42
61
  const formatted = results
43
- .filter(([, data]) => strs.every(r => decodeURIComponentNoThrow(data).toLowerCase().includes(r)))
44
62
  .map(([term, data]) => {
45
- const result = JSON.parse(data.replaceAll('|', ','));
46
- const [loc, trackId, ...rest] = result.map(record => decodeURIComponentNoThrow(record));
47
- const labelFieldIdx = rest.findIndex(elt => !!elt);
48
- const contextIdx = rest
49
- .map(elt => elt.toLowerCase())
50
- .findIndex(f => f.includes(term.toLowerCase()));
51
- const labelField = rest[labelFieldIdx];
52
- const contextField = rest[contextIdx];
53
- const context = contextIdx !== -1 ? shorten(contextField, term) : undefined;
54
- const label = shorten(labelField, term);
63
+ // record is ["loc"|"trackId"|"attr1"|...] with commas pre-encoded as
64
+ // pipes; swap them back to parse it as a JSON array, then URI-decode
65
+ const [loc, trackId, ...attrs] = JSON.parse(data.replaceAll('|', ',')).map(decodeURIComponentNoThrow);
66
+ return { term, loc, trackId, attrs };
67
+ })
68
+ // multi-word search: keep only entries whose attributes contain every word
69
+ .filter(({ attrs }) => {
70
+ const lower = attrs.join(' ').toLowerCase();
71
+ return words.every(w => lower.includes(w));
72
+ })
73
+ .map(({ term, loc, trackId, attrs }) => {
74
+ const termLower = term.toLowerCase();
75
+ const labelField = attrs.find(Boolean) ?? '';
76
+ const contextField = attrs.find(f => f.toLowerCase().includes(termLower));
77
+ const context = contextField
78
+ ? snippetAround(contextField, term)
79
+ : undefined;
80
+ const label = snippetAround(labelField, term);
55
81
  const displayString = !context || label.toLowerCase() === context.toLowerCase()
56
82
  ? label
57
83
  : `${label} (${context})`;
84
+ // exact match succeeds when any indexed attribute (name, id,
85
+ // description, ...) equals the query, so e.g. searching an ID works.
86
+ // Carried on the result as well as filtered on: only this adapter can
87
+ // judge it, and a caller that has the flag can ask one unrestricted
88
+ // question instead of an exact one and then a broad one
89
+ const exact = attrs.some(a => a.toLowerCase() === query);
58
90
  return new BaseResult({
59
91
  locString: loc,
60
- label: labelField,
92
+ label,
61
93
  displayString,
62
- matchedObject: result.map(record => decodeURIComponent(record)),
63
94
  trackId,
95
+ exact,
64
96
  });
65
97
  });
66
98
  return args.searchType === 'exact'
67
- ? formatted.filter(r => r.getLabel().toLowerCase() === args.queryString.toLowerCase())
99
+ ? formatted.filter(r => r.isExact())
68
100
  : formatted;
69
101
  }
70
102
  }
@@ -1,34 +1,76 @@
1
+ export declare function normalizeSnapshot(snap: Record<string, unknown>): Record<string, unknown>;
2
+ /**
3
+ * #config TrixTextSearchAdapter
4
+ * #trackType TextSearchAdapter
5
+ * #fileFormat textsearch | Trix index (.ix/.ixx) | Built by `jbrowse text-index`
6
+ *
7
+ * #example
8
+ * `jbrowse text-index` writes this entry into `aggregateTextSearchAdapters` for
9
+ * you. The `uri` shorthand points at the `.ix`; the sibling `.ixx` and
10
+ * `_meta.json` are derived from it, so all three only need spelling out when
11
+ * they are named against convention.
12
+ * ```js
13
+ * {
14
+ * type: 'TrixTextSearchAdapter',
15
+ * textSearchAdapterId: 'hg38-index',
16
+ * uri: 'trix/hg38.ix',
17
+ * assemblyNames: ['hg38'],
18
+ * }
19
+ * ```
20
+ */
1
21
  declare const TrixTextSearchAdapter: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
2
- ixFilePath: {
3
- type: string;
4
- defaultValue: {
5
- uri: string;
6
- locationType: string;
22
+ /**
23
+ * #slot
24
+ * location of the Trix `.ix` index written by `jbrowse text-index`: the
25
+ * sorted term-to-feature table the search box reads.
26
+ */
27
+ readonly ixFilePath: {
28
+ readonly type: "fileLocation";
29
+ readonly defaultValue: {
30
+ readonly uri: "out.ix";
31
+ readonly locationType: "UriLocation";
7
32
  };
8
33
  };
9
- ixxFilePath: {
10
- type: string;
11
- defaultValue: {
12
- uri: string;
13
- locationType: string;
34
+ /**
35
+ * #slot
36
+ * location of the `.ixx` prefix index, which records where in the `.ix`
37
+ * each prefix begins. It is what makes a lookup a couple of range requests
38
+ * instead of a download of the whole index.
39
+ */
40
+ readonly ixxFilePath: {
41
+ readonly type: "fileLocation";
42
+ readonly defaultValue: {
43
+ readonly uri: "out.ixx";
44
+ readonly locationType: "UriLocation";
14
45
  };
15
46
  };
16
- metaFilePath: {
17
- type: string;
18
- defaultValue: {
19
- uri: string;
20
- locationType: string;
47
+ /**
48
+ * #slot
49
+ * location of the `_meta.json` written beside the index, recording which
50
+ * tracks and assemblies it covers and the attributes it was built from.
51
+ */
52
+ readonly metaFilePath: {
53
+ readonly type: "fileLocation";
54
+ readonly defaultValue: {
55
+ readonly uri: "meta.json";
56
+ readonly locationType: "UriLocation";
21
57
  };
22
58
  };
23
- tracks: {
24
- type: string;
25
- defaultValue: never[];
26
- description: string;
59
+ /**
60
+ * #slot
61
+ */
62
+ readonly tracks: {
63
+ readonly type: "stringArray";
64
+ readonly defaultValue: readonly [];
65
+ readonly description: "List of tracks covered by text search adapter";
27
66
  };
28
- assemblyNames: {
29
- type: string;
30
- defaultValue: never[];
31
- description: string;
67
+ /**
68
+ * #slot
69
+ */
70
+ readonly assemblyNames: {
71
+ readonly type: "stringArray";
72
+ readonly defaultValue: readonly [];
73
+ readonly description: "List of assemblies covered by text search adapter";
32
74
  };
33
- }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, "textSearchAdapterId">>;
75
+ }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
34
76
  export default TrixTextSearchAdapter;
@@ -1,6 +1,50 @@
1
1
  import { ConfigurationSchema } from '@jbrowse/core/configuration';
2
- function x() { }
2
+ export function normalizeSnapshot(snap) {
3
+ return typeof snap.uri === 'string'
4
+ ? {
5
+ ...snap,
6
+ // `uri` points at the `.ix` file; `.ixx` and `_meta.json` sit beside it
7
+ // (the `jbrowse text-index` naming convention), so derive all three
8
+ ixFilePath: {
9
+ uri: snap.uri,
10
+ baseUri: snap.baseUri,
11
+ },
12
+ ixxFilePath: {
13
+ uri: `${snap.uri}x`,
14
+ baseUri: snap.baseUri,
15
+ },
16
+ metaFilePath: {
17
+ uri: `${snap.uri.replace(/\.ix$/, '')}_meta.json`,
18
+ baseUri: snap.baseUri,
19
+ },
20
+ }
21
+ : snap;
22
+ }
23
+ /**
24
+ * #config TrixTextSearchAdapter
25
+ * #trackType TextSearchAdapter
26
+ * #fileFormat textsearch | Trix index (.ix/.ixx) | Built by `jbrowse text-index`
27
+ *
28
+ * #example
29
+ * `jbrowse text-index` writes this entry into `aggregateTextSearchAdapters` for
30
+ * you. The `uri` shorthand points at the `.ix`; the sibling `.ixx` and
31
+ * `_meta.json` are derived from it, so all three only need spelling out when
32
+ * they are named against convention.
33
+ * ```js
34
+ * {
35
+ * type: 'TrixTextSearchAdapter',
36
+ * textSearchAdapterId: 'hg38-index',
37
+ * uri: 'trix/hg38.ix',
38
+ * assemblyNames: ['hg38'],
39
+ * }
40
+ * ```
41
+ */
3
42
  const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
43
+ /**
44
+ * #slot
45
+ * location of the Trix `.ix` index written by `jbrowse text-index`: the
46
+ * sorted term-to-feature table the search box reads.
47
+ */
4
48
  ixFilePath: {
5
49
  type: 'fileLocation',
6
50
  defaultValue: {
@@ -8,6 +52,12 @@ const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
8
52
  locationType: 'UriLocation',
9
53
  },
10
54
  },
55
+ /**
56
+ * #slot
57
+ * location of the `.ixx` prefix index, which records where in the `.ix`
58
+ * each prefix begins. It is what makes a lookup a couple of range requests
59
+ * instead of a download of the whole index.
60
+ */
11
61
  ixxFilePath: {
12
62
  type: 'fileLocation',
13
63
  defaultValue: {
@@ -15,6 +65,11 @@ const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
15
65
  locationType: 'UriLocation',
16
66
  },
17
67
  },
68
+ /**
69
+ * #slot
70
+ * location of the `_meta.json` written beside the index, recording which
71
+ * tracks and assemblies it covers and the attributes it was built from.
72
+ */
18
73
  metaFilePath: {
19
74
  type: 'fileLocation',
20
75
  defaultValue: {
@@ -22,11 +77,17 @@ const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
22
77
  locationType: 'UriLocation',
23
78
  },
24
79
  },
80
+ /**
81
+ * #slot
82
+ */
25
83
  tracks: {
26
84
  type: 'stringArray',
27
85
  defaultValue: [],
28
86
  description: 'List of tracks covered by text search adapter',
29
87
  },
88
+ /**
89
+ * #slot
90
+ */
30
91
  assemblyNames: {
31
92
  type: 'stringArray',
32
93
  defaultValue: [],
@@ -34,21 +95,27 @@ const TrixTextSearchAdapter = ConfigurationSchema('TrixTextSearchAdapter', {
34
95
  },
35
96
  }, {
36
97
  explicitlyTyped: true,
37
- explicitIdentifier: 'textSearchAdapterId',
38
- preProcessSnapshot: snap => {
39
- return snap.uri
40
- ? {
41
- ...snap,
42
- ixFilePath: {
43
- uri: snap.uri,
44
- baseUri: snap.baseUri,
45
- },
46
- ixxFilePath: {
47
- uri: `${snap.uri}x`,
48
- baseUri: snap.baseUri,
49
- },
50
- }
51
- : snap;
52
- },
98
+ /**
99
+ * #identifier
100
+ * an explicit `textSearchAdapterId` is still honored when given
101
+ */
102
+ implicitIdentifier: 'textSearchAdapterId',
103
+ /**
104
+ * #preProcessSnapshot
105
+ *
106
+ *
107
+ * preprocessor to allow minimal config: `uri` points at the `.ix` file and
108
+ * the sibling `.ixx` and `_meta.json` are derived from it (the
109
+ * `jbrowse text-index` naming convention):
110
+ * ```json
111
+ * {
112
+ * "type": "TrixTextSearchAdapter",
113
+ * "uri": "file.ix",
114
+ * "assemblyNames": ["hg19"],
115
+ * "textSearchAdapterId": "hg19SearchIndex"
116
+ * }
117
+ * ```
118
+ */
119
+ preProcessSnapshot: normalizeSnapshot,
53
120
  });
54
121
  export default TrixTextSearchAdapter;
package/esm/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import Plugin from '@jbrowse/core/Plugin';
2
2
  import TextSearchAdapterType from '@jbrowse/core/pluggableElementTypes/TextSearchAdapterType';
3
- import configSchema from "./TrixTextSearchAdapter/configSchema.js";
3
+ import configSchema from './TrixTextSearchAdapter/configSchema.js';
4
4
  export default class TrixPlugin extends Plugin {
5
5
  name = 'TrixPlugin';
6
6
  install(pluginManager) {
@@ -10,7 +10,7 @@ export default class TrixPlugin extends Plugin {
10
10
  displayName: 'Trix text search adapter',
11
11
  configSchema,
12
12
  description: 'Trix text search adapter',
13
- getAdapterClass: () => import("./TrixTextSearchAdapter/TrixTextSearchAdapter.js").then(d => d.default),
13
+ getAdapterClass: () => import('./TrixTextSearchAdapter/TrixTextSearchAdapter.js').then(d => d.default),
14
14
  });
15
15
  });
16
16
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-trix",
3
- "version": "4.3.0",
3
+ "version": "5.0.0-beta.2",
4
4
  "type": "module",
5
5
  "description": "JBrowse 2 trix text search adapter",
6
6
  "keywords": [
@@ -21,11 +21,11 @@
21
21
  "esm"
22
22
  ],
23
23
  "peerDependencies": {
24
- "react": ">=18.0.0"
24
+ "react": ">=19.0.0"
25
25
  },
26
26
  "dependencies": {
27
- "@gmod/trix": "^3.0.12",
28
- "@jbrowse/core": "^4.3.0"
27
+ "@gmod/trix": "^3.0.22",
28
+ "@jbrowse/core": "^5.0.0-beta.2"
29
29
  },
30
30
  "publishConfig": {
31
31
  "access": "public"
@@ -33,16 +33,13 @@
33
33
  "sideEffects": false,
34
34
  "scripts": {
35
35
  "build": "pnpm run /^build:/",
36
- "test": "cd ../..; jest --passWithNoTests plugins/trix",
36
+ "test": "jest --config ../../jest.config.js --passWithNoTests --testPathPatterns plugins/trix",
37
37
  "prebuild": "pnpm clean",
38
- "build:esm": "tsc -p tsconfig.build.esm.json",
38
+ "build:esm": "node ../../node_modules/typescript7/bin/tsc --build tsconfig.build.esm.json",
39
39
  "clean": "rimraf esm *.tsbuildinfo"
40
40
  },
41
41
  "types": "esm/index.d.ts",
42
42
  "exports": {
43
- ".": {
44
- "types": "./esm/index.d.ts",
45
- "import": "./esm/index.js"
46
- }
43
+ ".": "./esm/index.js"
47
44
  }
48
45
  }