ruvector 0.2.31 → 0.2.33

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 (42) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2303 -2270
  3. package/bin/cli.js +10245 -10106
  4. package/bin/mcp-policy.js +95 -0
  5. package/bin/mcp-server.js +4054 -4035
  6. package/dist/core/onnx/bundled-parallel.mjs +169 -169
  7. package/dist/core/onnx/embed-worker.mjs +67 -67
  8. package/dist/core/onnx/loader.js +485 -461
  9. package/dist/core/onnx/package.json +3 -3
  10. package/dist/core/onnx/pkg/LICENSE +21 -21
  11. package/dist/core/onnx/pkg/loader.js +348 -348
  12. package/dist/core/onnx/pkg/package.json +3 -3
  13. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm.d.ts +112 -112
  14. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm.js +5 -5
  15. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm_bg.js +638 -638
  16. package/dist/core/onnx/pkg/ruvector_onnx_embeddings_wasm_bg.wasm.d.ts +29 -29
  17. package/dist/core/parallel-workers.js +439 -439
  18. package/dist/workers/benchmark.js +15 -15
  19. package/package.json +124 -123
  20. package/src/decompiler/api-prober.js +302 -302
  21. package/src/decompiler/index.js +463 -463
  22. package/src/decompiler/metrics.js +86 -86
  23. package/src/decompiler/model-decompiler.js +423 -423
  24. package/src/decompiler/module-splitter.js +498 -498
  25. package/src/decompiler/module-tree.js +142 -142
  26. package/src/decompiler/name-predictor.js +400 -400
  27. package/src/decompiler/npm-fetch.js +176 -176
  28. package/src/decompiler/reconstructor.js +499 -499
  29. package/src/decompiler/reference-tracker.js +285 -285
  30. package/src/decompiler/statement-parser.js +285 -285
  31. package/src/decompiler/style-improver.js +438 -438
  32. package/src/decompiler/subcategories.js +339 -339
  33. package/src/decompiler/validator.js +379 -379
  34. package/src/decompiler/witness.js +140 -140
  35. package/src/optimizer/context.js +149 -0
  36. package/src/optimizer/index.js +177 -0
  37. package/src/optimizer/settings-generator.js +176 -0
  38. package/src/optimizer/tool-schemas.js +190 -0
  39. package/wasm/package.json +26 -26
  40. package/wasm/ruvector_decompiler_wasm.d.ts +27 -27
  41. package/wasm/ruvector_decompiler_wasm.js +220 -220
  42. package/wasm/ruvector_decompiler_wasm_bg.wasm.d.ts +16 -16
@@ -1,169 +1,169 @@
1
- /**
2
- * Bundled-WASM parallel embedder (issue #523 SOTA).
3
- *
4
- * A self-contained worker_threads pool — NO external dependency — that shards
5
- * batches of text across CPU cores, each worker running the bundled ONNX WASM
6
- * embedder over the SAME model bytes (shared via SharedArrayBuffer) and config.
7
- * Output vectors are identical to the single-thread path (cosine-equivalent),
8
- * so this is a pure throughput optimization with no quality change.
9
- *
10
- * Drop-in shape compatible with the optional `ruvector-onnx-embeddings-wasm/parallel`
11
- * package: { numWorkers, dimension, init(), embedBatch(texts) -> number[][], shutdown() }.
12
- */
13
- import { Worker } from 'node:worker_threads';
14
- import * as os from 'node:os';
15
- import { fileURLToPath } from 'node:url';
16
- import * as path from 'node:path';
17
-
18
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
-
20
- export class ParallelEmbedder {
21
- /**
22
- * @param {object} opts
23
- * @param {Uint8Array} opts.modelBytes raw ONNX model bytes (loaded once by caller)
24
- * @param {string} opts.tokenizerJson
25
- * @param {number} [opts.maxLength=256]
26
- * @param {number} [opts.dimension=384]
27
- * @param {number} [opts.numWorkers] defaults to min(cpus-2, 16), >=2
28
- */
29
- constructor(opts = {}) {
30
- this.numWorkers = opts.numWorkers || Math.max(2, Math.min((os.cpus().length || 4) - 2, 16));
31
- this.dimension = opts.dimension || 384;
32
- this._modelBytes = opts.modelBytes;
33
- this._tokenizerJson = opts.tokenizerJson;
34
- this._maxLength = opts.maxLength || 256;
35
- this._requestTimeoutMs = opts.requestTimeoutMs ?? 30000;
36
- this._workers = [];
37
- this._pending = new Map(); // id -> { resolve, reject, worker, timer }
38
- this._seq = 0;
39
- this._shuttingDown = false;
40
- }
41
-
42
- async init() {
43
- if (!this._modelBytes || !this._tokenizerJson) {
44
- throw new Error('ParallelEmbedder requires modelBytes and tokenizerJson');
45
- }
46
- // Share model bytes across all workers via a single SharedArrayBuffer.
47
- const sab = new SharedArrayBuffer(this._modelBytes.length);
48
- new Uint8Array(sab).set(this._modelBytes);
49
-
50
- const workerUrl = new URL('./embed-worker.mjs', import.meta.url);
51
- const readies = [];
52
-
53
- for (let i = 0; i < this.numWorkers; i++) {
54
- const w = new Worker(workerUrl, {
55
- workerData: { modelSab: sab, tokenizerJson: this._tokenizerJson, maxLength: this._maxLength },
56
- });
57
- w.on('message', (m) => this._onMessage(m));
58
- // If a worker dies (uncaught error or unexpected exit), fail every request
59
- // currently routed to it instead of letting those promises hang forever.
60
- w.on('error', (e) => this._failWorker(w, e instanceof Error ? e : new Error(String(e))));
61
- w.on('exit', (code) => {
62
- if (!this._shuttingDown && code !== 0) {
63
- this._failWorker(w, new Error(`embed worker exited unexpectedly (code ${code})`));
64
- }
65
- });
66
- this._workers.push(w);
67
- readies.push(new Promise((resolve, reject) => {
68
- const onReady = (m) => {
69
- if (m.type === 'ready') { cleanup(); resolve(); }
70
- else if (m.type === 'init-error') { cleanup(); reject(new Error('worker init failed: ' + m.error)); }
71
- };
72
- const onErr = (e) => { cleanup(); reject(e); };
73
- const cleanup = () => { w.off('message', onReady); w.off('error', onErr); };
74
- w.on('message', onReady);
75
- w.once('error', onErr);
76
- }));
77
- }
78
-
79
- await Promise.all(readies);
80
- // Drop the main-thread reference; the SAB keeps the shared copy alive.
81
- this._modelBytes = null;
82
- }
83
-
84
- _settle(id, fn) {
85
- const p = this._pending.get(id);
86
- if (!p) return;
87
- this._pending.delete(id);
88
- if (p.timer) clearTimeout(p.timer);
89
- fn(p);
90
- }
91
-
92
- _onMessage(m) {
93
- if (m.type !== 'result' && m.type !== 'error') return;
94
- this._settle(m.id, (p) => {
95
- if (m.type === 'error') p.reject(new Error(m.error));
96
- else p.resolve({ dim: m.dim, count: m.count, flat: new Float32Array(m.buffer) });
97
- });
98
- }
99
-
100
- /** Reject every in-flight request routed to a dead worker. */
101
- _failWorker(worker, err) {
102
- for (const [id, p] of this._pending) {
103
- if (p.worker === worker) this._settle(id, () => p.reject(err));
104
- }
105
- }
106
-
107
- _send(worker, texts) {
108
- const id = ++this._seq;
109
- return new Promise((resolve, reject) => {
110
- const entry = { resolve, reject, worker, timer: null };
111
- if (this._requestTimeoutMs > 0) {
112
- entry.timer = setTimeout(() => {
113
- this._settle(id, () =>
114
- reject(new Error(`embed request timed out after ${this._requestTimeoutMs}ms`)));
115
- }, this._requestTimeoutMs);
116
- // Don't keep the event loop alive solely for this timer.
117
- if (typeof entry.timer.unref === 'function') entry.timer.unref();
118
- }
119
- this._pending.set(id, entry);
120
- worker.postMessage({ type: 'embed', id, texts });
121
- });
122
- }
123
-
124
- /**
125
- * Embed many texts across workers. Returns number[][] in input order.
126
- *
127
- * Texts are dispatched in bounded chunks (default 8) that workers pull as
128
- * they finish (work-stealing), rather than one giant shard per worker:
129
- * a large bulk batch (ADR-210 D3 ingest) would otherwise exceed the
130
- * per-request timeout (~400ms/text in WASM x hundreds of texts), and a
131
- * single slow worker would gate the whole batch.
132
- */
133
- async embedBatch(texts, opts = {}) {
134
- if (!texts || texts.length === 0) return [];
135
- const chunkSize = Math.max(1, opts.chunkSize ?? 8);
136
- const chunks = [];
137
- for (let start = 0; start < texts.length; start += chunkSize) {
138
- chunks.push({ start, texts: texts.slice(start, start + chunkSize) });
139
- }
140
- const out = new Array(texts.length);
141
- let next = 0;
142
- const drain = async (worker) => {
143
- for (;;) {
144
- const idx = next++;
145
- if (idx >= chunks.length) return;
146
- const { start, texts: chunkTexts } = chunks[idx];
147
- const { dim, count, flat } = await this._send(worker, chunkTexts);
148
- for (let j = 0; j < count; j++) {
149
- out[start + j] = Array.from(flat.subarray(j * dim, (j + 1) * dim));
150
- }
151
- }
152
- };
153
- await Promise.all(this._workers.map(drain));
154
- return out;
155
- }
156
-
157
- async shutdown() {
158
- this._shuttingDown = true;
159
- // Reject anything still in flight so callers don't hang on shutdown.
160
- for (const [id, p] of this._pending) {
161
- this._settle(id, () => p.reject(new Error('ParallelEmbedder shut down')));
162
- }
163
- const ws = this._workers;
164
- this._workers = [];
165
- await Promise.all(ws.map((w) => w.terminate()));
166
- }
167
- }
168
-
169
- export default ParallelEmbedder;
1
+ /**
2
+ * Bundled-WASM parallel embedder (issue #523 SOTA).
3
+ *
4
+ * A self-contained worker_threads pool — NO external dependency — that shards
5
+ * batches of text across CPU cores, each worker running the bundled ONNX WASM
6
+ * embedder over the SAME model bytes (shared via SharedArrayBuffer) and config.
7
+ * Output vectors are identical to the single-thread path (cosine-equivalent),
8
+ * so this is a pure throughput optimization with no quality change.
9
+ *
10
+ * Drop-in shape compatible with the optional `ruvector-onnx-embeddings-wasm/parallel`
11
+ * package: { numWorkers, dimension, init(), embedBatch(texts) -> number[][], shutdown() }.
12
+ */
13
+ import { Worker } from 'node:worker_threads';
14
+ import * as os from 'node:os';
15
+ import { fileURLToPath } from 'node:url';
16
+ import * as path from 'node:path';
17
+
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+
20
+ export class ParallelEmbedder {
21
+ /**
22
+ * @param {object} opts
23
+ * @param {Uint8Array} opts.modelBytes raw ONNX model bytes (loaded once by caller)
24
+ * @param {string} opts.tokenizerJson
25
+ * @param {number} [opts.maxLength=256]
26
+ * @param {number} [opts.dimension=384]
27
+ * @param {number} [opts.numWorkers] defaults to min(cpus-2, 16), >=2
28
+ */
29
+ constructor(opts = {}) {
30
+ this.numWorkers = opts.numWorkers || Math.max(2, Math.min((os.cpus().length || 4) - 2, 16));
31
+ this.dimension = opts.dimension || 384;
32
+ this._modelBytes = opts.modelBytes;
33
+ this._tokenizerJson = opts.tokenizerJson;
34
+ this._maxLength = opts.maxLength || 256;
35
+ this._requestTimeoutMs = opts.requestTimeoutMs ?? 30000;
36
+ this._workers = [];
37
+ this._pending = new Map(); // id -> { resolve, reject, worker, timer }
38
+ this._seq = 0;
39
+ this._shuttingDown = false;
40
+ }
41
+
42
+ async init() {
43
+ if (!this._modelBytes || !this._tokenizerJson) {
44
+ throw new Error('ParallelEmbedder requires modelBytes and tokenizerJson');
45
+ }
46
+ // Share model bytes across all workers via a single SharedArrayBuffer.
47
+ const sab = new SharedArrayBuffer(this._modelBytes.length);
48
+ new Uint8Array(sab).set(this._modelBytes);
49
+
50
+ const workerUrl = new URL('./embed-worker.mjs', import.meta.url);
51
+ const readies = [];
52
+
53
+ for (let i = 0; i < this.numWorkers; i++) {
54
+ const w = new Worker(workerUrl, {
55
+ workerData: { modelSab: sab, tokenizerJson: this._tokenizerJson, maxLength: this._maxLength },
56
+ });
57
+ w.on('message', (m) => this._onMessage(m));
58
+ // If a worker dies (uncaught error or unexpected exit), fail every request
59
+ // currently routed to it instead of letting those promises hang forever.
60
+ w.on('error', (e) => this._failWorker(w, e instanceof Error ? e : new Error(String(e))));
61
+ w.on('exit', (code) => {
62
+ if (!this._shuttingDown && code !== 0) {
63
+ this._failWorker(w, new Error(`embed worker exited unexpectedly (code ${code})`));
64
+ }
65
+ });
66
+ this._workers.push(w);
67
+ readies.push(new Promise((resolve, reject) => {
68
+ const onReady = (m) => {
69
+ if (m.type === 'ready') { cleanup(); resolve(); }
70
+ else if (m.type === 'init-error') { cleanup(); reject(new Error('worker init failed: ' + m.error)); }
71
+ };
72
+ const onErr = (e) => { cleanup(); reject(e); };
73
+ const cleanup = () => { w.off('message', onReady); w.off('error', onErr); };
74
+ w.on('message', onReady);
75
+ w.once('error', onErr);
76
+ }));
77
+ }
78
+
79
+ await Promise.all(readies);
80
+ // Drop the main-thread reference; the SAB keeps the shared copy alive.
81
+ this._modelBytes = null;
82
+ }
83
+
84
+ _settle(id, fn) {
85
+ const p = this._pending.get(id);
86
+ if (!p) return;
87
+ this._pending.delete(id);
88
+ if (p.timer) clearTimeout(p.timer);
89
+ fn(p);
90
+ }
91
+
92
+ _onMessage(m) {
93
+ if (m.type !== 'result' && m.type !== 'error') return;
94
+ this._settle(m.id, (p) => {
95
+ if (m.type === 'error') p.reject(new Error(m.error));
96
+ else p.resolve({ dim: m.dim, count: m.count, flat: new Float32Array(m.buffer) });
97
+ });
98
+ }
99
+
100
+ /** Reject every in-flight request routed to a dead worker. */
101
+ _failWorker(worker, err) {
102
+ for (const [id, p] of this._pending) {
103
+ if (p.worker === worker) this._settle(id, () => p.reject(err));
104
+ }
105
+ }
106
+
107
+ _send(worker, texts) {
108
+ const id = ++this._seq;
109
+ return new Promise((resolve, reject) => {
110
+ const entry = { resolve, reject, worker, timer: null };
111
+ if (this._requestTimeoutMs > 0) {
112
+ entry.timer = setTimeout(() => {
113
+ this._settle(id, () =>
114
+ reject(new Error(`embed request timed out after ${this._requestTimeoutMs}ms`)));
115
+ }, this._requestTimeoutMs);
116
+ // Don't keep the event loop alive solely for this timer.
117
+ if (typeof entry.timer.unref === 'function') entry.timer.unref();
118
+ }
119
+ this._pending.set(id, entry);
120
+ worker.postMessage({ type: 'embed', id, texts });
121
+ });
122
+ }
123
+
124
+ /**
125
+ * Embed many texts across workers. Returns number[][] in input order.
126
+ *
127
+ * Texts are dispatched in bounded chunks (default 8) that workers pull as
128
+ * they finish (work-stealing), rather than one giant shard per worker:
129
+ * a large bulk batch (ADR-210 D3 ingest) would otherwise exceed the
130
+ * per-request timeout (~400ms/text in WASM x hundreds of texts), and a
131
+ * single slow worker would gate the whole batch.
132
+ */
133
+ async embedBatch(texts, opts = {}) {
134
+ if (!texts || texts.length === 0) return [];
135
+ const chunkSize = Math.max(1, opts.chunkSize ?? 8);
136
+ const chunks = [];
137
+ for (let start = 0; start < texts.length; start += chunkSize) {
138
+ chunks.push({ start, texts: texts.slice(start, start + chunkSize) });
139
+ }
140
+ const out = new Array(texts.length);
141
+ let next = 0;
142
+ const drain = async (worker) => {
143
+ for (;;) {
144
+ const idx = next++;
145
+ if (idx >= chunks.length) return;
146
+ const { start, texts: chunkTexts } = chunks[idx];
147
+ const { dim, count, flat } = await this._send(worker, chunkTexts);
148
+ for (let j = 0; j < count; j++) {
149
+ out[start + j] = Array.from(flat.subarray(j * dim, (j + 1) * dim));
150
+ }
151
+ }
152
+ };
153
+ await Promise.all(this._workers.map(drain));
154
+ return out;
155
+ }
156
+
157
+ async shutdown() {
158
+ this._shuttingDown = true;
159
+ // Reject anything still in flight so callers don't hang on shutdown.
160
+ for (const [id, p] of this._pending) {
161
+ this._settle(id, () => p.reject(new Error('ParallelEmbedder shut down')));
162
+ }
163
+ const ws = this._workers;
164
+ this._workers = [];
165
+ await Promise.all(ws.map((w) => w.terminate()));
166
+ }
167
+ }
168
+
169
+ export default ParallelEmbedder;
@@ -1,67 +1,67 @@
1
- /**
2
- * Worker-thread entry for the bundled-WASM parallel embedder (issue #523 SOTA).
3
- *
4
- * Each worker loads its own instance of the bundled ONNX WASM embedder from the
5
- * SAME model bytes (shared via SharedArrayBuffer — no per-worker download) and
6
- * the SAME config, so the vectors it produces are identical to the single-thread
7
- * path (cosine-equivalent by construction).
8
- *
9
- * Protocol:
10
- * workerData: { modelSab: SharedArrayBuffer, tokenizerJson: string, maxLength: number }
11
- * → posts { type: 'ready' } once the WASM embedder is constructed
12
- * message { type: 'embed', id, texts: string[] }
13
- * → posts { type: 'result', id, dim, count, buffer } (Float32Array buffer, transferred)
14
- * errors → { type: 'error', id, error }
15
- */
16
- import { parentPort, workerData } from 'node:worker_threads';
17
- import { pathToFileURL, fileURLToPath } from 'node:url';
18
- import * as path from 'node:path';
19
- import * as fs from 'node:fs';
20
-
21
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
-
23
- let embedder = null;
24
-
25
- async function init() {
26
- const bgJsPath = path.join(__dirname, 'pkg', 'ruvector_onnx_embeddings_wasm_bg.js');
27
- const wasmPath = path.join(__dirname, 'pkg', 'ruvector_onnx_embeddings_wasm_bg.wasm');
28
-
29
- const wasmModule = await import(pathToFileURL(bgJsPath).href);
30
- const wasmBytes = fs.readFileSync(wasmPath);
31
- const wasmResult = await WebAssembly.instantiate(wasmBytes, {
32
- './ruvector_onnx_embeddings_wasm_bg.js': wasmModule,
33
- });
34
- const wasmExports = wasmResult.instance.exports;
35
- if (typeof wasmModule.__wbg_set_wasm === 'function') wasmModule.__wbg_set_wasm(wasmExports);
36
- if (typeof wasmExports.__wbindgen_start === 'function') wasmExports.__wbindgen_start();
37
-
38
- // Reconstruct model bytes from the shared buffer (zero-copy view, then handed
39
- // to wasm-bindgen which copies into WASM linear memory).
40
- const modelBytes = new Uint8Array(workerData.modelSab);
41
-
42
- const cfg = new wasmModule.WasmEmbedderConfig()
43
- .setMaxLength(workerData.maxLength || 256)
44
- .setNormalize(true)
45
- .setPooling(0); // Mean pooling — matches the single-thread path.
46
-
47
- embedder = wasmModule.WasmEmbedder.withConfig(modelBytes, workerData.tokenizerJson, cfg);
48
- }
49
-
50
- parentPort.on('message', (msg) => {
51
- if (msg.type !== 'embed') return;
52
- try {
53
- const dim = embedder.dimension();
54
- const flat = embedder.embedBatch(msg.texts); // length = texts.length * dim
55
- const arr = Float32Array.from(flat);
56
- parentPort.postMessage(
57
- { type: 'result', id: msg.id, dim, count: msg.texts.length, buffer: arr.buffer },
58
- [arr.buffer],
59
- );
60
- } catch (e) {
61
- parentPort.postMessage({ type: 'error', id: msg.id, error: e?.message || String(e) });
62
- }
63
- });
64
-
65
- init()
66
- .then(() => parentPort.postMessage({ type: 'ready' }))
67
- .catch((e) => parentPort.postMessage({ type: 'init-error', error: e?.message || String(e) }));
1
+ /**
2
+ * Worker-thread entry for the bundled-WASM parallel embedder (issue #523 SOTA).
3
+ *
4
+ * Each worker loads its own instance of the bundled ONNX WASM embedder from the
5
+ * SAME model bytes (shared via SharedArrayBuffer — no per-worker download) and
6
+ * the SAME config, so the vectors it produces are identical to the single-thread
7
+ * path (cosine-equivalent by construction).
8
+ *
9
+ * Protocol:
10
+ * workerData: { modelSab: SharedArrayBuffer, tokenizerJson: string, maxLength: number }
11
+ * → posts { type: 'ready' } once the WASM embedder is constructed
12
+ * message { type: 'embed', id, texts: string[] }
13
+ * → posts { type: 'result', id, dim, count, buffer } (Float32Array buffer, transferred)
14
+ * errors → { type: 'error', id, error }
15
+ */
16
+ import { parentPort, workerData } from 'node:worker_threads';
17
+ import { pathToFileURL, fileURLToPath } from 'node:url';
18
+ import * as path from 'node:path';
19
+ import * as fs from 'node:fs';
20
+
21
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
+
23
+ let embedder = null;
24
+
25
+ async function init() {
26
+ const bgJsPath = path.join(__dirname, 'pkg', 'ruvector_onnx_embeddings_wasm_bg.js');
27
+ const wasmPath = path.join(__dirname, 'pkg', 'ruvector_onnx_embeddings_wasm_bg.wasm');
28
+
29
+ const wasmModule = await import(pathToFileURL(bgJsPath).href);
30
+ const wasmBytes = fs.readFileSync(wasmPath);
31
+ const wasmResult = await WebAssembly.instantiate(wasmBytes, {
32
+ './ruvector_onnx_embeddings_wasm_bg.js': wasmModule,
33
+ });
34
+ const wasmExports = wasmResult.instance.exports;
35
+ if (typeof wasmModule.__wbg_set_wasm === 'function') wasmModule.__wbg_set_wasm(wasmExports);
36
+ if (typeof wasmExports.__wbindgen_start === 'function') wasmExports.__wbindgen_start();
37
+
38
+ // Reconstruct model bytes from the shared buffer (zero-copy view, then handed
39
+ // to wasm-bindgen which copies into WASM linear memory).
40
+ const modelBytes = new Uint8Array(workerData.modelSab);
41
+
42
+ const cfg = new wasmModule.WasmEmbedderConfig()
43
+ .setMaxLength(workerData.maxLength || 256)
44
+ .setNormalize(true)
45
+ .setPooling(0); // Mean pooling — matches the single-thread path.
46
+
47
+ embedder = wasmModule.WasmEmbedder.withConfig(modelBytes, workerData.tokenizerJson, cfg);
48
+ }
49
+
50
+ parentPort.on('message', (msg) => {
51
+ if (msg.type !== 'embed') return;
52
+ try {
53
+ const dim = embedder.dimension();
54
+ const flat = embedder.embedBatch(msg.texts); // length = texts.length * dim
55
+ const arr = Float32Array.from(flat);
56
+ parentPort.postMessage(
57
+ { type: 'result', id: msg.id, dim, count: msg.texts.length, buffer: arr.buffer },
58
+ [arr.buffer],
59
+ );
60
+ } catch (e) {
61
+ parentPort.postMessage({ type: 'error', id: msg.id, error: e?.message || String(e) });
62
+ }
63
+ });
64
+
65
+ init()
66
+ .then(() => parentPort.postMessage({ type: 'ready' }))
67
+ .catch((e) => parentPort.postMessage({ type: 'init-error', error: e?.message || String(e) }));