@datagrok/bio 2.27.2 → 2.27.4

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 (57) hide show
  1. package/CLAUDE.md +50 -0
  2. package/agents/package-knowledge.yaml +53 -0
  3. package/dist/455.js +1 -1
  4. package/dist/455.js.map +1 -1
  5. package/dist/682.js +1 -1
  6. package/dist/682.js.map +1 -1
  7. package/dist/705.js +1 -1
  8. package/dist/705.js.map +1 -1
  9. package/dist/909.js +2 -0
  10. package/dist/909.js.map +1 -0
  11. package/dist/immunum_bg.wasm +0 -0
  12. package/dist/package-test.js +3 -3
  13. package/dist/package-test.js.map +1 -1
  14. package/dist/package.js +3 -3
  15. package/dist/package.js.map +1 -1
  16. package/package.json +4 -2
  17. package/src/demo/bio01b-hierarchical-clustering-and-activity-cliffs.ts +24 -11
  18. package/src/package-api.ts +15 -1
  19. package/src/package-test.ts +1 -0
  20. package/src/package.g.ts +12 -1
  21. package/src/package.ts +22 -4
  22. package/src/tests/antibody-numbering-tests.ts +190 -0
  23. package/src/tests/detectors-tests.ts +5 -1
  24. package/src/tests/splitters-test.ts +8 -4
  25. package/src/tests/to-atomic-level-tests.ts +144 -0
  26. package/src/utils/annotations/numbering-ui.ts +34 -90
  27. package/src/utils/antibody-numbering/immunum-client.ts +45 -0
  28. package/src/utils/antibody-numbering/immunum-glue.js +275 -0
  29. package/src/utils/antibody-numbering/immunum.worker.ts +159 -0
  30. package/src/utils/antibody-numbering/number-antibody.ts +105 -0
  31. package/src/utils/antibody-numbering/types.ts +48 -0
  32. package/src/utils/seq-helper/seq-handler.ts +25 -9
  33. package/test-console-output-1.log +582 -485
  34. package/test-record-1.mp4 +0 -0
  35. package/webpack.config.js +13 -0
  36. package/dist/282.js +0 -2
  37. package/dist/282.js.map +0 -1
  38. package/dist/287.js +0 -2
  39. package/dist/287.js.map +0 -1
  40. package/dist/422.js +0 -2
  41. package/dist/422.js.map +0 -1
  42. package/dist/767.js +0 -2
  43. package/dist/767.js.map +0 -1
  44. package/src/utils/antibody-numbering (WIP)/alignment.ts +0 -578
  45. package/src/utils/antibody-numbering (WIP)/annotator.ts +0 -120
  46. package/src/utils/antibody-numbering (WIP)/data/blosum62.ts +0 -55
  47. package/src/utils/antibody-numbering (WIP)/data/consensus-aho.ts +0 -155
  48. package/src/utils/antibody-numbering (WIP)/data/consensus-imgt.ts +0 -162
  49. package/src/utils/antibody-numbering (WIP)/data/consensus-kabat.ts +0 -157
  50. package/src/utils/antibody-numbering (WIP)/data/consensus-martin.ts +0 -152
  51. package/src/utils/antibody-numbering (WIP)/data/consensus.ts +0 -36
  52. package/src/utils/antibody-numbering (WIP)/data/regions.ts +0 -63
  53. package/src/utils/antibody-numbering (WIP)/index.ts +0 -31
  54. package/src/utils/antibody-numbering (WIP)/testdata.ts +0 -5356
  55. package/src/utils/antibody-numbering (WIP)/types.ts +0 -69
  56. /package/dist/{8473fcbfb6e85ca6c852.wasm → wasmCluster.wasm} +0 -0
  57. /package/dist/{9a8fbf37666e32487835.wasm → wasmDbscan.wasm} +0 -0
@@ -7,99 +7,48 @@ import {TAGS as bioTAGS, ALIGNMENT, ALPHABET, NOTATION} from '@datagrok-librarie
7
7
  import {
8
8
  SeqAnnotation, SeqAnnotationHit, AnnotationCategory,
9
9
  } from '@datagrok-libraries/bio/src/utils/macromolecule/annotations';
10
- import {NumberingScheme} from '@datagrok-libraries/bio/src/utils/macromolecule/numbering-schemes';
11
10
  import {
12
11
  setColumnAnnotations, getColumnAnnotations,
13
12
  getOrCreateAnnotationColumn, getRowAnnotations, setRowAnnotations, mergeRowHits,
14
13
  } from './annotation-manager';
15
- import {_package} from '../../package';
16
- import type {NumberingResult, Scheme} from '../antibody-numbering (WIP)';
17
- import {VdRegionsViewer} from '../../viewers/vd-regions-viewer';
18
- import { VdRegion, VdRegionType } from '@datagrok-libraries/bio/src/viewers/vd-regions';
19
-
20
- const BUILTIN_ENGINE_KEY = '__builtin__';
21
- const BUILTIN_ENGINE_LABEL = 'Built-in (TypeScript)';
22
-
23
- /** An engine entry: either a dynamically discovered DG.Func or the built-in TS engine. */
14
+ /** An engine entry — a dynamically discovered DG.Func with meta.role: 'antibodyNumbering'. */
24
15
  interface NumberingEngine {
25
- /** Display label for the dropdown */
26
16
  label: string;
27
- /** Unique key — nqName for DG.Func engines, BUILTIN_ENGINE_KEY for built-in */
17
+ /** Unique key — `${package}:${name}`. */
28
18
  key: string;
29
- /** The DG.Func to call, or null for the built-in engine */
30
- func: DG.Func | null;
19
+ func: DG.Func;
31
20
  }
32
21
 
33
- /** Discovers all registered antibody numbering engines + the built-in TS engine.
34
- * Dynamic engines (meta.role = 'antibodyNumbering') come first; built-in is last. */
22
+ /** Discovers all registered antibody numbering engines (functions with
23
+ * meta.role = 'antibodyNumbering'). Built-in engines: Bio package ships an
24
+ * immunum-WASM-based engine; other packages (Proteomics etc.) can register
25
+ * AntPack/ANARCI/etc. by adding their own meta.role function. */
35
26
  function discoverEngines(): NumberingEngine[] {
36
27
  const engines: NumberingEngine[] = [];
37
-
38
28
  const funcs = DG.Func.find({meta: {role: 'antibodyNumbering'}});
39
29
  if (funcs.length === 0) {
40
- grok.shell.error('No antibody numbering engines found. Make sure that Proteomics plugin is installed and up to date.');
41
- throw new Error('No external antibody numbering engines found. Make sure that Proteomics plugin is installed and up to date.');
30
+ grok.shell.error('No antibody numbering engines found. Make sure the Bio package is up to date.');
31
+ throw new Error('No antibody numbering engines found.');
42
32
  }
43
33
  for (const f of funcs) {
44
34
  const pkgName = f.package?.name ?? '';
45
- const label = f.friendlyName || f.name;
46
35
  engines.push({
47
- label: label,
36
+ label: f.friendlyName || f.name,
48
37
  key: pkgName ? `${pkgName}:${f.name}` : f.name,
49
38
  func: f,
50
39
  });
51
40
  }
52
-
53
- // Built-in TS engine is always last
54
- engines.push({label: BUILTIN_ENGINE_LABEL, key: BUILTIN_ENGINE_KEY, func: null});
55
41
  return engines;
56
42
  }
57
43
 
58
- /** Converts TS NumberingResult[] to a DG.DataFrame matching the expected output shape.
59
- * Columns: position_names, chain_type, annotations_json, numbering_detail, numbering_map. */
60
- export function numberingResultsToDataFrame(results: NumberingResult[]): DG.DataFrame {
61
- const n = results.length;
62
- const posNames = DG.Column.fromType(DG.COLUMN_TYPE.STRING, 'position_names', n);
63
- const chainTypes = DG.Column.fromType(DG.COLUMN_TYPE.STRING, 'chain_type', n);
64
- const annotJson = DG.Column.fromType(DG.COLUMN_TYPE.STRING, 'annotations_json', n);
65
- const numDetail = DG.Column.fromType(DG.COLUMN_TYPE.STRING, 'numbering_detail', n);
66
- const numMap = DG.Column.fromType(DG.COLUMN_TYPE.STRING, 'numbering_map', n);
67
-
68
- for (let i = 0; i < n; i++) {
69
- const r = results[i];
70
- if (r.error && r.percentIdentity < 0.3) {
71
- posNames.set(i, '');
72
- chainTypes.set(i, '');
73
- annotJson.set(i, '[]');
74
- numDetail.set(i, '');
75
- numMap.set(i, '');
76
- } else {
77
- posNames.set(i, r.positionNames);
78
- chainTypes.set(i, r.chainType);
79
- annotJson.set(i, JSON.stringify(r.annotations));
80
- numDetail.set(i, JSON.stringify(r.numberingDetail));
81
- numMap.set(i, JSON.stringify(r.numberingMap));
82
- }
83
- }
84
-
85
- return DG.DataFrame.fromColumns([posNames, chainTypes, annotJson, numDetail, numMap]);
86
- }
87
-
88
- /** Runs the built-in TS numbering engine on all rows of a sequence column. */
89
- async function runBuiltinNumbering(
90
- seqCol: DG.Column<string>, schemeName: string,
91
- ): Promise<DG.DataFrame> {
92
- const {numberSequences, extractSequence} = await import('../antibody-numbering (WIP)');
93
- const scheme = schemeName.toLowerCase() as Scheme;
94
-
95
- const sequences: string[] = [];
96
- for (let i = 0; i < seqCol.length; i++) {
97
- const raw = seqCol.get(i);
98
- sequences.push(extractSequence(raw ?? ''));
99
- }
100
-
101
- const results = numberSequences(sequences, scheme);
102
- return numberingResultsToDataFrame(results);
44
+ /** Reads the `scheme` parameter's declared choices off an engine function.
45
+ * Engines advertise which schemes they support via the `choices` option of
46
+ * their `scheme: string` parameter; if the function has none we fall back to
47
+ * a single generic IMGT option so the dropdown is never empty. */
48
+ function getEngineSchemes(engine: NumberingEngine): string[] {
49
+ const schemeInput = engine.func.inputs.find((p) => p.name.toLowerCase() === 'scheme');
50
+ const choices = schemeInput?.choices ?? [];
51
+ return choices.length > 0 ? choices.slice() : ['imgt'];
103
52
  }
104
53
 
105
54
  export function showNumberingSchemeDialog(): void {
@@ -117,45 +66,40 @@ export function showNumberingSchemeDialog(): void {
117
66
 
118
67
  const engines = discoverEngines();
119
68
  const engineLabels = engines.map((e) => e.label);
120
- const schemeChoices = Object.values(NumberingScheme);
121
69
 
122
70
  const tableInput = ui.input.table('Table', {value: df});
123
71
  const seqInput = ui.input.column('Sequence', {
124
72
  table: df, value: seqCols[0],
125
73
  filter: (col: DG.Column) => col.semType === DG.SEMTYPE.MACROMOLECULE,
126
74
  });
127
- const schemeInput = ui.input.choice('Scheme', {value: NumberingScheme.IMGT, items: schemeChoices});
128
75
  const engineInput = ui.input.choice('Engine', {
129
76
  value: engineLabels[0], items: engineLabels,
130
77
  });
131
- // const populateRegions = ui.input.bool('Populate FR/CDR regions', {value: true});
132
- // const openVdRegions = ui.input.bool('Open VD Regions viewer', {value: true});
78
+ const initialSchemes = getEngineSchemes(engines[0]);
79
+ const schemeInput = ui.input.choice('Scheme', {value: initialSchemes[0], items: initialSchemes});
80
+
81
+ // Switch the scheme list when the user picks a different engine — each engine
82
+ // advertises its own set via the `scheme` parameter's `choices`.
83
+ engineInput.onChanged.subscribe(() => {
84
+ const selected = engines.find((e) => e.label === engineInput.value);
85
+ if (!selected) return;
86
+ const schemes = getEngineSchemes(selected);
87
+ const prev = schemeInput.value;
88
+ schemeInput.items = schemes;
89
+ schemeInput.value = schemes.includes(prev ?? '') ? prev : schemes[0];
90
+ });
133
91
 
134
92
  const dialog = ui.dialog({title: 'Apply Antibody Numbering'})
135
- .add(ui.inputs([tableInput, seqInput, schemeInput, engineInput]))
93
+ .add(ui.inputs([tableInput, seqInput, engineInput, schemeInput]))
136
94
  .onOK(async () => {
137
95
  const seqCol = seqInput.value!;
138
96
  const schemeName = schemeInput.value!;
139
97
  const selectedLabel = engineInput.value!;
140
- const engine = engines.find((e) => e.label === selectedLabel) ?? engines[engines.length - 1];
98
+ const engine = engines.find((e) => e.label === selectedLabel) ?? engines[0];
141
99
  const pi = DG.TaskBarProgressIndicator.create(`Applying ${schemeName} numbering...`);
142
100
  try {
143
- let result: DG.DataFrame;
144
- if (engine.func)
145
- result = await engine.func.apply({df: df, seqCol: seqCol, scheme: schemeName.toLowerCase()});
146
- else
147
- result = await runBuiltinNumbering(seqCol, schemeName);
148
-
101
+ const result: DG.DataFrame = await engine.func.apply({df: df, seqCol: seqCol, scheme: schemeName});
149
102
  applyNumberingResults(df, seqCol, result, schemeName, true, engine.label);
150
-
151
- // // Open VD Regions viewer
152
- // if (openVdRegions.value && grok.shell.tv) {
153
- // try {
154
- // await grok.shell.tv.dataFrame.plot.fromType('VdRegions', {});
155
- // } catch (err) {
156
- // console.warn('Could not open VD Regions viewer:', err);
157
- // }
158
- // }
159
103
  } catch (err: any) {
160
104
  grok.shell.error(`Numbering failed: ${err.message ?? err}`);
161
105
  console.error(err);
@@ -0,0 +1,45 @@
1
+ import type {
2
+ ImmunumNumberingRow, ImmunumWorkerRequest, ImmunumWorkerResponse,
3
+ } from './types';
4
+
5
+ /** Short-lived worker: we spin it up per call and tear it down immediately
6
+ * after. Numbering is a one-shot batch operation — keeping the worker alive
7
+ * would pin the immunum WASM instance (≈700 KB) in memory indefinitely. */
8
+ function spawnWorker(): Worker {
9
+ return new Worker(new URL('./immunum.worker', import.meta.url));
10
+ }
11
+
12
+ function callOnce(worker: Worker, req: ImmunumWorkerRequest): Promise<ImmunumWorkerResponse> {
13
+ return new Promise((resolve, reject) => {
14
+ const ch = new MessageChannel();
15
+ ch.port1.onmessage = ({data}) => {
16
+ ch.port1.close();
17
+ resolve(data as ImmunumWorkerResponse);
18
+ };
19
+ ch.port1.onmessageerror = (err) => {
20
+ ch.port1.close();
21
+ reject(err);
22
+ };
23
+ worker.postMessage({req}, [ch.port2]);
24
+ });
25
+ }
26
+
27
+ /** Runs immunum numbering on a batch of sequences inside a web worker. Spawns a
28
+ * fresh worker for this call and terminates it before returning so the WASM
29
+ * instance is freed. Throws on WASM/init errors; individual per-row errors
30
+ * are attached to each row's `error` field. */
31
+ export async function numberSequencesWithImmunum(
32
+ sequences: string[],
33
+ scheme: string,
34
+ chains?: string[],
35
+ minConfidence?: number | null,
36
+ ): Promise<ImmunumNumberingRow[]> {
37
+ const worker = spawnWorker();
38
+ try {
39
+ const resp = await callOnce(worker, {op: 'number', sequences, scheme, chains, minConfidence});
40
+ if (!resp.ok) throw new Error(resp.error);
41
+ return resp.rows ?? [];
42
+ } finally {
43
+ worker.terminate();
44
+ }
45
+ }
@@ -0,0 +1,275 @@
1
+ /* eslint-disable */
2
+ // @ts-nocheck
3
+ //
4
+ // Browser/worker-friendly port of node_modules/immunum/immunum.js (v1.1.0).
5
+ // The upstream file uses require('fs').readFileSync to load the WASM at top
6
+ // level, which breaks in the browser. This file is byte-identical up to the
7
+ // wasm-bindgen glue; the only change is that the WASM bytes are supplied via
8
+ // an explicit initImmunum() call instead of being read from disk at import
9
+ // time. See README.md in the immunum package for the upstream source.
10
+
11
+ let wasm = null;
12
+
13
+ export class Annotator {
14
+ __destroy_into_raw() {
15
+ const ptr = this.__wbg_ptr;
16
+ this.__wbg_ptr = 0;
17
+ AnnotatorFinalization.unregister(this);
18
+ return ptr;
19
+ }
20
+ free() {
21
+ const ptr = this.__destroy_into_raw();
22
+ wasm.__wbg_annotator_free(ptr, 0);
23
+ }
24
+ constructor(chains, scheme, min_confidence) {
25
+ const ptr0 = passArrayJsValueToWasm0(chains, wasm.__wbindgen_malloc);
26
+ const len0 = WASM_VECTOR_LEN;
27
+ const ptr1 = passStringToWasm0(scheme, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
28
+ const len1 = WASM_VECTOR_LEN;
29
+ const ret = wasm.annotator_new(ptr0, len0, ptr1, len1,
30
+ isLikeNone(min_confidence) ? 0x100000001 : Math.fround(min_confidence));
31
+ if (ret[2]) {
32
+ throw takeFromExternrefTable0(ret[1]);
33
+ }
34
+ this.__wbg_ptr = ret[0] >>> 0;
35
+ AnnotatorFinalization.register(this, this.__wbg_ptr, this);
36
+ return this;
37
+ }
38
+ number(sequence) {
39
+ const ptr0 = passStringToWasm0(sequence, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
40
+ const len0 = WASM_VECTOR_LEN;
41
+ return wasm.annotator_number(this.__wbg_ptr, ptr0, len0);
42
+ }
43
+ segment(sequence) {
44
+ const ptr0 = passStringToWasm0(sequence, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
45
+ const len0 = WASM_VECTOR_LEN;
46
+ return wasm.annotator_segment(this.__wbg_ptr, ptr0, len0);
47
+ }
48
+ }
49
+ if (typeof Symbol !== 'undefined' && Symbol.dispose) Annotator.prototype[Symbol.dispose] = Annotator.prototype.free;
50
+
51
+ const AnnotatorFinalization = (typeof FinalizationRegistry === 'undefined')
52
+ ? {register: () => {}, unregister: () => {}}
53
+ : new FinalizationRegistry((ptr) => wasm.__wbg_annotator_free(ptr >>> 0, 1));
54
+
55
+ function __wbg_get_imports() {
56
+ const import0 = {
57
+ __proto__: null,
58
+ __wbg___wbindgen_debug_string_5398f5bb970e0daa: function(arg0, arg1) {
59
+ const ret = debugString(arg1);
60
+ const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
61
+ const len1 = WASM_VECTOR_LEN;
62
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
63
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
64
+ },
65
+ __wbg___wbindgen_string_get_395e606bd0ee4427: function(arg0, arg1) {
66
+ const obj = arg1;
67
+ const ret = typeof(obj) === 'string' ? obj : undefined;
68
+ const ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
69
+ const len1 = WASM_VECTOR_LEN;
70
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
71
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
72
+ },
73
+ __wbg___wbindgen_throw_6ddd609b62940d55: function(arg0, arg1) {
74
+ throw new Error(getStringFromWasm0(arg0, arg1));
75
+ },
76
+ __wbg_new_49d5571bd3f0c4d4: function() {
77
+ return new Map();
78
+ },
79
+ __wbg_new_ab79df5bd7c26067: function() {
80
+ return new Object();
81
+ },
82
+ __wbg_set_7eaa4f96924fd6b3: function() {
83
+ return handleError(function(arg0, arg1, arg2) {
84
+ return Reflect.set(arg0, arg1, arg2);
85
+ }, arguments);
86
+ },
87
+ __wbg_set_bf7251625df30a02: function(arg0, arg1, arg2) {
88
+ return arg0.set(arg1, arg2);
89
+ },
90
+ __wbindgen_cast_0000000000000001: function(arg0) {
91
+ return arg0;
92
+ },
93
+ __wbindgen_cast_0000000000000002: function(arg0, arg1) {
94
+ return getStringFromWasm0(arg0, arg1);
95
+ },
96
+ __wbindgen_init_externref_table: function() {
97
+ const table = wasm.__wbindgen_externrefs;
98
+ const offset = table.grow(4);
99
+ table.set(0, undefined);
100
+ table.set(offset + 0, undefined);
101
+ table.set(offset + 1, null);
102
+ table.set(offset + 2, true);
103
+ table.set(offset + 3, false);
104
+ },
105
+ };
106
+ return {
107
+ __proto__: null,
108
+ './immunum_bg.js': import0,
109
+ };
110
+ }
111
+
112
+ function addToExternrefTable0(obj) {
113
+ const idx = wasm.__externref_table_alloc();
114
+ wasm.__wbindgen_externrefs.set(idx, obj);
115
+ return idx;
116
+ }
117
+
118
+ function debugString(val) {
119
+ const type = typeof val;
120
+ if (type == 'number' || type == 'boolean' || val == null) return `${val}`;
121
+ if (type == 'string') return `"${val}"`;
122
+ if (type == 'symbol') {
123
+ const description = val.description;
124
+ return description == null ? 'Symbol' : `Symbol(${description})`;
125
+ }
126
+ if (type == 'function') {
127
+ const name = val.name;
128
+ return (typeof name == 'string' && name.length > 0) ? `Function(${name})` : 'Function';
129
+ }
130
+ if (Array.isArray(val)) {
131
+ const length = val.length;
132
+ let debug = '[';
133
+ if (length > 0) debug += debugString(val[0]);
134
+ for (let i = 1; i < length; i++) debug += ', ' + debugString(val[i]);
135
+ debug += ']';
136
+ return debug;
137
+ }
138
+ const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
139
+ let className;
140
+ if (builtInMatches && builtInMatches.length > 1) {
141
+ className = builtInMatches[1];
142
+ } else {
143
+ return toString.call(val);
144
+ }
145
+ if (className == 'Object') {
146
+ try {
147
+ return 'Object(' + JSON.stringify(val) + ')';
148
+ } catch (_) {
149
+ return 'Object';
150
+ }
151
+ }
152
+ if (val instanceof Error) return `${val.name}: ${val.message}\n${val.stack}`;
153
+ return className;
154
+ }
155
+
156
+ let cachedDataViewMemory0 = null;
157
+ function getDataViewMemory0() {
158
+ if (cachedDataViewMemory0 === null ||
159
+ cachedDataViewMemory0.buffer.detached === true ||
160
+ (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
161
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
162
+ }
163
+ return cachedDataViewMemory0;
164
+ }
165
+
166
+ function getStringFromWasm0(ptr, len) {
167
+ ptr = ptr >>> 0;
168
+ return decodeText(ptr, len);
169
+ }
170
+
171
+ let cachedUint8ArrayMemory0 = null;
172
+ function getUint8ArrayMemory0() {
173
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0)
174
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
175
+ return cachedUint8ArrayMemory0;
176
+ }
177
+
178
+ function handleError(f, args) {
179
+ try {
180
+ return f.apply(this, args);
181
+ } catch (e) {
182
+ const idx = addToExternrefTable0(e);
183
+ wasm.__wbindgen_exn_store(idx);
184
+ }
185
+ }
186
+
187
+ function isLikeNone(x) {
188
+ return x === undefined || x === null;
189
+ }
190
+
191
+ function passArrayJsValueToWasm0(array, malloc) {
192
+ const ptr = malloc(array.length * 4, 4) >>> 0;
193
+ for (let i = 0; i < array.length; i++) {
194
+ const add = addToExternrefTable0(array[i]);
195
+ getDataViewMemory0().setUint32(ptr + 4 * i, add, true);
196
+ }
197
+ WASM_VECTOR_LEN = array.length;
198
+ return ptr;
199
+ }
200
+
201
+ function passStringToWasm0(arg, malloc, realloc) {
202
+ if (realloc === undefined) {
203
+ const buf = cachedTextEncoder.encode(arg);
204
+ const ptr = malloc(buf.length, 1) >>> 0;
205
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
206
+ WASM_VECTOR_LEN = buf.length;
207
+ return ptr;
208
+ }
209
+
210
+ let len = arg.length;
211
+ let ptr = malloc(len, 1) >>> 0;
212
+ const mem = getUint8ArrayMemory0();
213
+
214
+ let offset = 0;
215
+ for (; offset < len; offset++) {
216
+ const code = arg.charCodeAt(offset);
217
+ if (code > 0x7F) break;
218
+ mem[ptr + offset] = code;
219
+ }
220
+ if (offset !== len) {
221
+ if (offset !== 0) arg = arg.slice(offset);
222
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
223
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
224
+ const ret = cachedTextEncoder.encodeInto(arg, view);
225
+ offset += ret.written;
226
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
227
+ }
228
+
229
+ WASM_VECTOR_LEN = offset;
230
+ return ptr;
231
+ }
232
+
233
+ function takeFromExternrefTable0(idx) {
234
+ const value = wasm.__wbindgen_externrefs.get(idx);
235
+ wasm.__externref_table_dealloc(idx);
236
+ return value;
237
+ }
238
+
239
+ const cachedTextDecoder = new TextDecoder('utf-8', {ignoreBOM: true, fatal: true});
240
+ cachedTextDecoder.decode();
241
+ function decodeText(ptr, len) {
242
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
243
+ }
244
+
245
+ const cachedTextEncoder = new TextEncoder();
246
+ if (!('encodeInto' in cachedTextEncoder)) {
247
+ cachedTextEncoder.encodeInto = function(arg, view) {
248
+ const buf = cachedTextEncoder.encode(arg);
249
+ view.set(buf);
250
+ return {read: arg.length, written: buf.length};
251
+ };
252
+ }
253
+
254
+ let WASM_VECTOR_LEN = 0;
255
+
256
+ /** Instantiate the immunum WASM module from raw bytes or an ArrayBuffer.
257
+ * Must be called once before constructing any Annotator. Safe to call multiple
258
+ * times — subsequent calls are no-ops. */
259
+ export async function initImmunum(wasmSource) {
260
+ if (wasm) return;
261
+ const imports = __wbg_get_imports();
262
+ let inst;
263
+ if (wasmSource instanceof WebAssembly.Module) {
264
+ inst = await WebAssembly.instantiate(wasmSource, imports);
265
+ } else {
266
+ const result = await WebAssembly.instantiate(wasmSource, imports);
267
+ inst = result.instance;
268
+ }
269
+ wasm = inst.exports;
270
+ wasm.__wbindgen_start();
271
+ }
272
+
273
+ export function isImmunumReady() {
274
+ return wasm !== null;
275
+ }
@@ -0,0 +1,159 @@
1
+ // @ts-ignore -- file-loader emits the .wasm file to the package's dist/ dir.
2
+ // The returned string is the path relative to the emitted bundle; we take only
3
+ // the basename because the worker already lives in the same dist/ directory.
4
+ import wasmEmittedPath from 'immunum/immunum_bg.wasm';
5
+ // @ts-ignore -- plain JS port of immunum.js
6
+ import {Annotator, initImmunum} from './immunum-glue.js';
7
+
8
+ import type {ImmunumNumberingRow, ImmunumWorkerRequest, ImmunumWorkerResponse} from './types';
9
+
10
+ const ctx: Worker = self as any;
11
+
12
+ const DEFAULT_CHAINS = ['H', 'K', 'L'];
13
+ let _annotatorCache: {key: string; annotator: any} | null = null;
14
+ let _initPromise: Promise<void> | null = null;
15
+
16
+ function resolveWasmUrl(): string {
17
+ // Worker script URL (e.g. http://host/packages/Bio/dist/NNN.js) is the right
18
+ // base — the wasm sits in the same dist/ folder. Stripping off any path
19
+ // prefix from file-loader's output (which may include a publicPath like
20
+ // 'dist/') keeps us at the basename regardless of how webpack resolved it.
21
+ const path = wasmEmittedPath as unknown as string;
22
+ const basename = path.substring(path.lastIndexOf('/') + 1);
23
+ return new URL(basename, self.location.href).toString();
24
+ }
25
+
26
+ async function ensureInit(): Promise<void> {
27
+ if (!_initPromise) {
28
+ _initPromise = (async () => {
29
+ const wasmUrl = resolveWasmUrl();
30
+ const resp = await fetch(wasmUrl);
31
+ if (!resp.ok) throw new Error(`Failed to fetch immunum WASM from ${wasmUrl} (HTTP ${resp.status})`);
32
+ const bytes = await resp.arrayBuffer();
33
+ await initImmunum(bytes);
34
+ })();
35
+ }
36
+ return _initPromise;
37
+ }
38
+
39
+ function getAnnotator(chains: string[], scheme: string, minConfidence: number | null): any {
40
+ const key = `${scheme}|${chains.join(',')}|${minConfidence ?? 'null'}`;
41
+ if (_annotatorCache && _annotatorCache.key === key) return _annotatorCache.annotator;
42
+ if (_annotatorCache) {
43
+ try { _annotatorCache.annotator.free(); } catch { /* ignore */ }
44
+ }
45
+ const annotator = new Annotator(chains, scheme, minConfidence ?? undefined);
46
+ _annotatorCache = {key, annotator};
47
+ return annotator;
48
+ }
49
+
50
+ const VALID_AA = new Set('ACDEFGHIKLMNPQRSTVWY');
51
+
52
+ /** Normalizes a raw input cell to a single-letter AA sequence: strips gaps, whitespace,
53
+ * non-standard characters, and uppercases. Matches the Python `extract_sequence` helper
54
+ * used by the antpack reference script so downstream char-index maps line up. */
55
+ function extractSequence(raw: string | null | undefined): string {
56
+ if (!raw || typeof raw !== 'string') return '';
57
+ let out = '';
58
+ const s = raw.trim();
59
+ for (let i = 0; i < s.length; i++) {
60
+ const ch = s[i].toUpperCase();
61
+ if (VALID_AA.has(ch)) out += ch;
62
+ }
63
+ return out;
64
+ }
65
+
66
+ function chainToGroup(chain: string | null): string {
67
+ if (!chain) return '';
68
+ if (chain === 'H') return 'Heavy';
69
+ if (chain === 'K' || chain === 'L') return 'Light';
70
+ return '';
71
+ }
72
+
73
+ function numberOne(annotator: any, rawSeq: string): ImmunumNumberingRow {
74
+ const seq = extractSequence(rawSeq);
75
+ if (!seq || seq.length < 10) {
76
+ return {
77
+ positionNames: '',
78
+ chainType: '',
79
+ chainCode: '',
80
+ numberingDetail: [],
81
+ numberingMap: {},
82
+ confidence: 0,
83
+ error: seq ? 'sequence too short' : 'empty sequence',
84
+ };
85
+ }
86
+
87
+ let result;
88
+ try {
89
+ result = annotator.number(seq);
90
+ } catch (e: any) {
91
+ return {
92
+ positionNames: '', chainType: '', chainCode: '',
93
+ numberingDetail: [], numberingMap: {}, confidence: 0,
94
+ error: (e && e.message) || String(e),
95
+ };
96
+ }
97
+
98
+ if (!result || result.error || !result.numbering) {
99
+ return {
100
+ positionNames: '', chainType: '', chainCode: '',
101
+ numberingDetail: [], numberingMap: {}, confidence: result?.confidence ?? 0,
102
+ error: result?.error ?? 'no alignment',
103
+ };
104
+ }
105
+
106
+ // numbering is a Map<posCode, residue>; iterate in insertion (IMGT) order.
107
+ // Each entry corresponds to a single residue at consecutive input indices
108
+ // starting at query_start (immunum semantics).
109
+ const posNames: string[] = [];
110
+ const numberingDetail: {position: string; aa: string}[] = [];
111
+ const numberingMap: Record<string, number> = {};
112
+
113
+ let charIdx = (result.query_start ?? 0) | 0;
114
+ const numbering: Map<string, string> = result.numbering;
115
+ for (const [posCode, residue] of numbering.entries()) {
116
+ posNames.push(posCode);
117
+ numberingDetail.push({position: posCode, aa: residue});
118
+ numberingMap[posCode] = charIdx;
119
+ charIdx++;
120
+ }
121
+
122
+ return {
123
+ positionNames: posNames.join(', '),
124
+ chainType: chainToGroup(result.chain),
125
+ chainCode: result.chain ?? '',
126
+ numberingDetail,
127
+ numberingMap,
128
+ confidence: result.confidence ?? 0,
129
+ error: '',
130
+ };
131
+ }
132
+
133
+ ctx.addEventListener('message', async (e: MessageEvent) => {
134
+ const req: ImmunumWorkerRequest = e.data.req;
135
+ const port: MessagePort = e.ports[0];
136
+
137
+ try {
138
+ await ensureInit();
139
+
140
+ if (req.op === 'init') {
141
+ port.postMessage({ok: true} satisfies ImmunumWorkerResponse);
142
+ return;
143
+ }
144
+
145
+ if (req.op === 'number') {
146
+ const chains = req.chains && req.chains.length > 0 ? req.chains : DEFAULT_CHAINS;
147
+ const annotator = getAnnotator(chains, req.scheme, req.minConfidence ?? null);
148
+ const rows: ImmunumNumberingRow[] = new Array(req.sequences.length);
149
+ for (let i = 0; i < req.sequences.length; i++)
150
+ rows[i] = numberOne(annotator, req.sequences[i]);
151
+ port.postMessage({ok: true, rows} satisfies ImmunumWorkerResponse);
152
+ return;
153
+ }
154
+
155
+ port.postMessage({ok: false, error: `Unknown op: ${(req as any).op}`} satisfies ImmunumWorkerResponse);
156
+ } catch (err: any) {
157
+ port.postMessage({ok: false, error: (err && err.message) || String(err)} satisfies ImmunumWorkerResponse);
158
+ }
159
+ });