harper-fabric-onnx 0.1.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 HarperDB, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # harper-fabric-onnx
2
+
3
+ ONNX Runtime embedding wrapper for Harper. Runs inference in a dedicated child process — one model instance, one thread pool, no global state races across Harper workers.
4
+
5
+ Same public API as `harper-fabric-embeddings` so `harper-kb` can swap backends by changing one import.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install harper-fabric-onnx
11
+ ```
12
+
13
+ Requires Node.js 22+.
14
+
15
+ ## Usage
16
+
17
+ ```js
18
+ import {
19
+ downloadModel,
20
+ init,
21
+ embed,
22
+ embedBatch,
23
+ dimensions,
24
+ dispose,
25
+ } from "harper-fabric-onnx";
26
+
27
+ // Download model files (one-time)
28
+ await downloadModel(".models");
29
+
30
+ // Initialize — spawns child process, loads ONNX model
31
+ await init({ modelsDir: ".models" });
32
+
33
+ // Single embedding (768-dim, L2-normalized)
34
+ const vec = await embed("Hello world");
35
+
36
+ // Query-optimized embedding (uses "search_query:" prefix)
37
+ const queryVec = await embed("What is Harper?", "query");
38
+
39
+ // Batch embedding
40
+ const vecs = await embedBatch(["First text", "Second text"]);
41
+
42
+ // Get model dimensions
43
+ dimensions(); // 768
44
+
45
+ // Cleanup
46
+ await dispose();
47
+ ```
48
+
49
+ ## How it works
50
+
51
+ ONNX Runtime has a global singleton and process-wide thread pool, so it can't safely run per-worker in Harper's multi-worker architecture. This package runs ONNX in a dedicated child process and routes all worker calls to it via a Unix domain socket.
52
+
53
+ - `init()` — spawns a child process (or connects to an existing one), loads the ONNX model and tokenizer
54
+ - `embed()` / `embedBatch()` — sends text over the socket, child tokenizes + runs inference, returns L2-normalized vectors
55
+ - One child process is shared across all Harper worker threads
56
+ - Stale process detection via PID files — auto-recovers from crashes
57
+
58
+ ## Supported models
59
+
60
+ | Model | Repo | Dimensions |
61
+ | ---------------------------- | -------------------------------- | ---------- |
62
+ | `nomic-embed-text` (default) | nomic-ai/nomic-embed-text-v1.5 | 768 |
63
+ | `nomic-embed-text-v2-moe` | nomic-ai/nomic-embed-text-v2-moe | 768 |
64
+
65
+ ## API
66
+
67
+ ### `downloadModel(dir: string, modelName?: string): Promise<string>`
68
+
69
+ Downloads model and tokenizer files from HuggingFace. Returns the model directory path.
70
+
71
+ ### `init(options: InitOptions): Promise<void>`
72
+
73
+ Spawns the child process and loads the model. Options:
74
+
75
+ - `modelsDir` — directory containing model subdirectories (e.g., `.models`)
76
+ - `modelPath` — direct path to a specific model directory
77
+ - `modelName` — model name from the registry (default: `nomic-embed-text`)
78
+
79
+ ### `embed(text: string, type?: 'document' | 'query'): Promise<number[]>`
80
+
81
+ Returns an L2-normalized embedding vector. Default type is `'document'`.
82
+
83
+ ### `embedBatch(texts: string[], type?: 'document' | 'query'): Promise<number[][]>`
84
+
85
+ Returns an array of L2-normalized embedding vectors.
86
+
87
+ ### `dimensions(): number`
88
+
89
+ Returns the dimensionality of the loaded model (e.g., 768).
90
+
91
+ ### `dispose(): Promise<void>`
92
+
93
+ Shuts down the child process and cleans up socket/PID files.
94
+
95
+ ## Harper component usage
96
+
97
+ ```js
98
+ // resources.js
99
+ const { Resource } = globalThis;
100
+
101
+ export class Embed extends Resource {
102
+ static loadAsInstance = false;
103
+
104
+ async post(_query, data) {
105
+ const { init, embed, embedBatch, dimensions } =
106
+ await import("harper-fabric-onnx");
107
+ await init({ modelsDir: process.env.ONNX_MODELS_DIR });
108
+
109
+ if (data.texts) {
110
+ const vecs = await embedBatch(data.texts);
111
+ return { dimensions: dimensions(), vectors: vecs };
112
+ }
113
+
114
+ const vec = await embed(data.text, data.type);
115
+ return { dimensions: dimensions(), vector: vec };
116
+ }
117
+ }
118
+ ```
119
+
120
+ ## License
121
+
122
+ MIT
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Child process entry point — owns the ONNX Runtime session and tokenizer.
3
+ *
4
+ * Spawned via fork() from the proxy (index.ts). Runs outside Harper's sandbox
5
+ * so it can require() native modules normally.
6
+ *
7
+ * Protocol:
8
+ * 1. Parent sends IPC message with { socketPath, pidPath } to configure.
9
+ * 2. Child creates a Unix domain socket server.
10
+ * 3. Child writes PID file and sends { ready: true } back via IPC.
11
+ * 4. All subsequent communication happens over the socket using
12
+ * length-prefixed JSON frames (see protocol.ts).
13
+ * 5. Requests are processed serially — ONNX session is not thread-safe.
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=child.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"child.d.ts","sourceRoot":"","sources":["../src/child.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG"}
package/dist/child.js ADDED
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Child process entry point — owns the ONNX Runtime session and tokenizer.
3
+ *
4
+ * Spawned via fork() from the proxy (index.ts). Runs outside Harper's sandbox
5
+ * so it can require() native modules normally.
6
+ *
7
+ * Protocol:
8
+ * 1. Parent sends IPC message with { socketPath, pidPath } to configure.
9
+ * 2. Child creates a Unix domain socket server.
10
+ * 3. Child writes PID file and sends { ready: true } back via IPC.
11
+ * 4. All subsequent communication happens over the socket using
12
+ * length-prefixed JSON frames (see protocol.ts).
13
+ * 5. Requests are processed serially — ONNX session is not thread-safe.
14
+ */
15
+ import { createServer } from "node:net";
16
+ import { writeFileSync, unlinkSync } from "node:fs";
17
+ import ort from "onnxruntime-node";
18
+ import { AutoTokenizer, } from "@huggingface/transformers";
19
+ import { MODELS, DEFAULT_MODEL, encodeMessage, FrameDecoder, } from "./protocol.js";
20
+ import { join } from "node:path";
21
+ // ---------------------------------------------------------------------------
22
+ // State
23
+ // ---------------------------------------------------------------------------
24
+ let session = null;
25
+ let tokenizer = null;
26
+ let modelName = DEFAULT_MODEL;
27
+ let cachedDimensions = 0;
28
+ let sockPath = "";
29
+ let pidPath = "";
30
+ // ---------------------------------------------------------------------------
31
+ // Inference helpers
32
+ // ---------------------------------------------------------------------------
33
+ function normalize(vec) {
34
+ let sumSq = 0;
35
+ for (let i = 0; i < vec.length; i++) {
36
+ sumSq += vec[i] * vec[i];
37
+ }
38
+ const norm = Math.sqrt(sumSq);
39
+ if (norm === 0)
40
+ return Array.from(vec);
41
+ const out = Array.from({ length: vec.length });
42
+ for (let i = 0; i < vec.length; i++) {
43
+ out[i] = vec[i] / norm;
44
+ }
45
+ return out;
46
+ }
47
+ function meanPool(hidden, mask, seqLen, hiddenSize) {
48
+ const accum = new Float64Array(hiddenSize);
49
+ let tokenCount = 0;
50
+ for (let t = 0; t < seqLen; t++) {
51
+ if (mask[t] === 0n)
52
+ continue;
53
+ tokenCount++;
54
+ const offset = t * hiddenSize;
55
+ for (let d = 0; d < hiddenSize; d++) {
56
+ accum[d] += hidden[offset + d];
57
+ }
58
+ }
59
+ if (tokenCount === 0)
60
+ return Array.from({ length: hiddenSize }).fill(0);
61
+ const result = Array.from({ length: hiddenSize });
62
+ for (let d = 0; d < hiddenSize; d++) {
63
+ result[d] = accum[d] / tokenCount;
64
+ }
65
+ return result;
66
+ }
67
+ async function embedOne(text, embedType = "document") {
68
+ if (!session || !tokenizer)
69
+ throw new Error("Not initialized");
70
+ const config = MODELS[modelName];
71
+ const prefix = embedType === "query" ? config.prefix.query : config.prefix.document;
72
+ const prefixed = prefix + text;
73
+ const encoded = tokenizer(prefixed, {
74
+ padding: false,
75
+ truncation: true,
76
+ return_tensor: false,
77
+ });
78
+ const inputIds = encoded.input_ids;
79
+ const attentionMask = encoded.attention_mask;
80
+ const seqLen = inputIds.length;
81
+ const idsBigInt = BigInt64Array.from(inputIds, (v) => BigInt(v));
82
+ const maskBigInt = BigInt64Array.from(attentionMask, (v) => BigInt(v));
83
+ const feeds = {
84
+ input_ids: new ort.Tensor("int64", idsBigInt, [1, seqLen]),
85
+ attention_mask: new ort.Tensor("int64", maskBigInt, [1, seqLen]),
86
+ };
87
+ // Some models also expect token_type_ids
88
+ if (session.inputNames.includes("token_type_ids")) {
89
+ feeds.token_type_ids = new ort.Tensor("int64", new BigInt64Array(seqLen), [
90
+ 1,
91
+ seqLen,
92
+ ]);
93
+ }
94
+ const output = await session.run(feeds);
95
+ // Try dedicated sentence embedding output first, fall back to mean pooling
96
+ if (output.sentence_embedding) {
97
+ return normalize(output.sentence_embedding.data);
98
+ }
99
+ const hidden = output.last_hidden_state;
100
+ if (!hidden)
101
+ throw new Error("Model output has no last_hidden_state");
102
+ const hiddenSize = hidden.dims[2];
103
+ return normalize(meanPool(hidden.data, Array.from(maskBigInt), seqLen, hiddenSize));
104
+ }
105
+ // ---------------------------------------------------------------------------
106
+ // Request handler
107
+ // ---------------------------------------------------------------------------
108
+ async function handleRequest(req) {
109
+ try {
110
+ switch (req.type) {
111
+ case "init": {
112
+ if (!session) {
113
+ modelName = req.modelName || DEFAULT_MODEL;
114
+ const config = MODELS[modelName];
115
+ if (!config) {
116
+ return {
117
+ id: req.id,
118
+ ok: false,
119
+ error: `Unknown model: ${modelName}`,
120
+ };
121
+ }
122
+ const modelPath = join(req.modelDir, "model.onnx");
123
+ session = await ort.InferenceSession.create(modelPath, {
124
+ executionProviders: ["cpu"],
125
+ // 'basic' avoids fusion passes that fail on fp16 models in ORT 1.24+
126
+ graphOptimizationLevel: "basic",
127
+ });
128
+ tokenizer = await AutoTokenizer.from_pretrained(req.modelDir, {
129
+ local_files_only: true,
130
+ });
131
+ cachedDimensions = config.dimensions;
132
+ }
133
+ return { id: req.id, ok: true, result: cachedDimensions };
134
+ }
135
+ case "embed": {
136
+ const vec = await embedOne(req.text, req.embedType);
137
+ return { id: req.id, ok: true, result: vec };
138
+ }
139
+ case "batch": {
140
+ const results = [];
141
+ for (const text of req.texts) {
142
+ results.push(await embedOne(text, req.embedType));
143
+ }
144
+ return { id: req.id, ok: true, result: results };
145
+ }
146
+ case "dimensions": {
147
+ return { id: req.id, ok: true, result: cachedDimensions };
148
+ }
149
+ case "dispose": {
150
+ await cleanup();
151
+ return { id: req.id, ok: true, result: null };
152
+ }
153
+ }
154
+ }
155
+ catch (err) {
156
+ return {
157
+ id: req.id,
158
+ ok: false,
159
+ error: err instanceof Error ? err.message : String(err),
160
+ };
161
+ }
162
+ }
163
+ // ---------------------------------------------------------------------------
164
+ // Request queue — processes requests serially
165
+ // ---------------------------------------------------------------------------
166
+ let queue = Promise.resolve();
167
+ function enqueue(socket, req) {
168
+ queue = queue.then(async () => {
169
+ const res = await handleRequest(req);
170
+ if (!socket.destroyed) {
171
+ socket.write(encodeMessage(res));
172
+ }
173
+ });
174
+ }
175
+ // ---------------------------------------------------------------------------
176
+ // Socket server
177
+ // ---------------------------------------------------------------------------
178
+ function startServer() {
179
+ const server = createServer((conn) => {
180
+ const decoder = new FrameDecoder();
181
+ conn.on("data", (chunk) => {
182
+ decoder.push(chunk);
183
+ for (const msg of decoder.drain()) {
184
+ enqueue(conn, msg);
185
+ }
186
+ });
187
+ });
188
+ // Clean up stale socket from a previous run
189
+ try {
190
+ unlinkSync(sockPath);
191
+ }
192
+ catch {
193
+ // not present — fine
194
+ }
195
+ server.listen(sockPath, () => {
196
+ writeFileSync(pidPath, String(process.pid));
197
+ process.send?.({ ready: true });
198
+ });
199
+ server.on("error", (err) => {
200
+ console.error("[harper-fabric-onnx child] server error:", err.message);
201
+ process.exit(1);
202
+ });
203
+ process.on("SIGTERM", () => {
204
+ server.close(() => cleanup().then(() => process.exit(0)));
205
+ });
206
+ }
207
+ // ---------------------------------------------------------------------------
208
+ // Cleanup
209
+ // ---------------------------------------------------------------------------
210
+ async function cleanup() {
211
+ try {
212
+ session?.release();
213
+ }
214
+ catch {
215
+ // ignore
216
+ }
217
+ session = null;
218
+ tokenizer = null;
219
+ try {
220
+ unlinkSync(sockPath);
221
+ }
222
+ catch {
223
+ // may already be gone
224
+ }
225
+ try {
226
+ unlinkSync(pidPath);
227
+ }
228
+ catch {
229
+ // may already be gone
230
+ }
231
+ }
232
+ // ---------------------------------------------------------------------------
233
+ // Entry point — wait for config from parent via IPC
234
+ // ---------------------------------------------------------------------------
235
+ process.once("message", (msg) => {
236
+ const config = msg;
237
+ sockPath = config.socketPath;
238
+ pidPath = config.pidPath;
239
+ startServer();
240
+ });
241
+ //# sourceMappingURL=child.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"child.js","sourceRoot":"","sources":["../src/child.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Model and tokenizer download from HuggingFace.
3
+ *
4
+ * Downloads ONNX model + tokenizer files to a model-specific subdirectory.
5
+ * Uses streaming download with atomic rename to prevent partial files.
6
+ */
7
+ /**
8
+ * Download an ONNX model and its tokenizer files from HuggingFace.
9
+ *
10
+ * @param dir - Parent directory for model storage
11
+ * @param modelName - Model name from the built-in registry
12
+ * @returns Path to the model directory containing model.onnx and tokenizer files
13
+ */
14
+ export declare function downloadModel(dir: string, modelName?: string): Promise<string>;
15
+ //# sourceMappingURL=download.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,SAAS,SAAgB,GACxB,OAAO,CAAC,MAAM,CAAC,CA4BjB"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Model and tokenizer download from HuggingFace.
3
+ *
4
+ * Downloads ONNX model + tokenizer files to a model-specific subdirectory.
5
+ * Uses streaming download with atomic rename to prevent partial files.
6
+ */
7
+ import { createWriteStream, existsSync, mkdirSync } from "node:fs";
8
+ import { rename, unlink } from "node:fs/promises";
9
+ import { pipeline } from "node:stream/promises";
10
+ import { join } from "node:path";
11
+ import { MODELS, DEFAULT_MODEL } from "./protocol.js";
12
+ /**
13
+ * Download an ONNX model and its tokenizer files from HuggingFace.
14
+ *
15
+ * @param dir - Parent directory for model storage
16
+ * @param modelName - Model name from the built-in registry
17
+ * @returns Path to the model directory containing model.onnx and tokenizer files
18
+ */
19
+ export async function downloadModel(dir, modelName = DEFAULT_MODEL) {
20
+ const config = MODELS[modelName];
21
+ if (!config) {
22
+ throw new Error(`Unknown model: ${modelName}. Available: ${Object.keys(MODELS).join(", ")}`);
23
+ }
24
+ const modelDir = join(dir, modelName);
25
+ mkdirSync(modelDir, { recursive: true });
26
+ // Download ONNX model file
27
+ const onnxDest = join(modelDir, "model.onnx");
28
+ if (!existsSync(onnxDest)) {
29
+ const onnxUrl = `https://huggingface.co/${config.repo}/resolve/main/${config.onnxFile}`;
30
+ await downloadFile(onnxUrl, onnxDest, config.onnxFile);
31
+ }
32
+ // Download tokenizer files
33
+ for (const file of config.tokenizerFiles) {
34
+ const dest = join(modelDir, file);
35
+ if (!existsSync(dest)) {
36
+ const url = `https://huggingface.co/${config.repo}/resolve/main/${file}`;
37
+ await downloadFile(url, dest, file);
38
+ }
39
+ }
40
+ return modelDir;
41
+ }
42
+ async function downloadFile(url, destPath, label) {
43
+ const tmpPath = destPath + ".downloading";
44
+ console.log(`[harper-fabric-onnx] Downloading ${label}...`);
45
+ const response = await fetch(url, { redirect: "follow" });
46
+ if (!response.ok) {
47
+ throw new Error(`Download failed: ${response.status} ${response.statusText} — ${url}`);
48
+ }
49
+ try {
50
+ const fileStream = createWriteStream(tmpPath);
51
+ await pipeline(response.body, fileStream);
52
+ }
53
+ catch (err) {
54
+ await unlink(tmpPath).catch(() => { });
55
+ throw err;
56
+ }
57
+ // Atomic rename on same filesystem
58
+ await rename(tmpPath, destPath);
59
+ console.log(`[harper-fabric-onnx] Downloaded ${label} to ${destPath}`);
60
+ }
61
+ //# sourceMappingURL=download.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download.js","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * harper-fabric-onnx
3
+ *
4
+ * ONNX Runtime embedding wrapper for Harper Fabric.
5
+ * Runs inference in a dedicated child process — one model instance,
6
+ * one thread pool, no global state races across Harper workers.
7
+ *
8
+ * Same public API as harper-fabric-embeddings so harper-kb can swap
9
+ * backends by changing one import.
10
+ */
11
+ import { type InitOptions } from "./protocol.js";
12
+ export type { InitOptions } from "./protocol.js";
13
+ export { downloadModel } from "./download.js";
14
+ export { MODELS, DEFAULT_MODEL } from "./protocol.js";
15
+ /**
16
+ * Initialize the embedding engine.
17
+ *
18
+ * Provide either `modelPath` (absolute path to an ONNX model directory) or
19
+ * `modelsDir` (directory to search/download into) + optional `modelName`.
20
+ *
21
+ * Safe to call concurrently — concurrent callers share the same initialization.
22
+ */
23
+ export declare function init(options: InitOptions): Promise<void>;
24
+ /**
25
+ * Generate an embedding vector for the given text.
26
+ *
27
+ * Returns an L2-normalized vector as number[].
28
+ */
29
+ export declare function embed(text: string, type?: "document" | "query"): Promise<number[]>;
30
+ /**
31
+ * Generate embedding vectors for multiple texts.
32
+ */
33
+ export declare function embedBatch(texts: string[], type?: "document" | "query"): Promise<number[][]>;
34
+ /**
35
+ * Get the embedding vector dimensionality.
36
+ */
37
+ export declare function dimensions(): number;
38
+ /**
39
+ * Shut down the child process and clean up resources.
40
+ */
41
+ export declare function dispose(): Promise<void>;
42
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAOH,OAAO,EAGL,KAAK,WAAW,EAOjB,MAAM,eAAe,CAAC;AAEvB,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAuBtD;;;;;;;GAOG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAS9D;AAED;;;;GAIG;AACH,wBAAsB,KAAK,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,UAAU,GAAG,OAAoB,GACtC,OAAO,CAAC,MAAM,EAAE,CAAC,CAQnB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EAAE,EACf,IAAI,GAAE,UAAU,GAAG,OAAoB,GACtC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAQrB;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAKnC;AAED;;GAEG;AACH,wBAAsB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAqC7C"}
package/dist/index.js ADDED
@@ -0,0 +1,305 @@
1
+ /**
2
+ * harper-fabric-onnx
3
+ *
4
+ * ONNX Runtime embedding wrapper for Harper Fabric.
5
+ * Runs inference in a dedicated child process — one model instance,
6
+ * one thread pool, no global state races across Harper workers.
7
+ *
8
+ * Same public API as harper-fabric-embeddings so harper-kb can swap
9
+ * backends by changing one import.
10
+ */
11
+ import { connect } from "node:net";
12
+ import { fork } from "node:child_process";
13
+ import { existsSync, readFileSync, unlinkSync } from "node:fs";
14
+ import { dirname, join } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ import { MODELS, DEFAULT_MODEL, socketPath, pidFilePath, encodeMessage, FrameDecoder, } from "./protocol.js";
17
+ export { downloadModel } from "./download.js";
18
+ export { MODELS, DEFAULT_MODEL } from "./protocol.js";
19
+ // ---------------------------------------------------------------------------
20
+ // State
21
+ // ---------------------------------------------------------------------------
22
+ let socket = null;
23
+ let child = null;
24
+ let decoder = null;
25
+ let nextId = 1;
26
+ let cachedDimensions = 0;
27
+ let disposed = false;
28
+ let initPromise = null;
29
+ let modelsDir = "";
30
+ const pending = new Map();
31
+ // ---------------------------------------------------------------------------
32
+ // Public API
33
+ // ---------------------------------------------------------------------------
34
+ /**
35
+ * Initialize the embedding engine.
36
+ *
37
+ * Provide either `modelPath` (absolute path to an ONNX model directory) or
38
+ * `modelsDir` (directory to search/download into) + optional `modelName`.
39
+ *
40
+ * Safe to call concurrently — concurrent callers share the same initialization.
41
+ */
42
+ export async function init(options) {
43
+ if (initPromise)
44
+ return initPromise;
45
+ initPromise = doInit(options);
46
+ try {
47
+ await initPromise;
48
+ }
49
+ catch (err) {
50
+ initPromise = null;
51
+ throw err;
52
+ }
53
+ }
54
+ /**
55
+ * Generate an embedding vector for the given text.
56
+ *
57
+ * Returns an L2-normalized vector as number[].
58
+ */
59
+ export async function embed(text, type = "document") {
60
+ assertReady();
61
+ return sendRequest({
62
+ id: nextId++,
63
+ type: "embed",
64
+ text,
65
+ embedType: type,
66
+ });
67
+ }
68
+ /**
69
+ * Generate embedding vectors for multiple texts.
70
+ */
71
+ export async function embedBatch(texts, type = "document") {
72
+ assertReady();
73
+ return sendRequest({
74
+ id: nextId++,
75
+ type: "batch",
76
+ texts,
77
+ embedType: type,
78
+ });
79
+ }
80
+ /**
81
+ * Get the embedding vector dimensionality.
82
+ */
83
+ export function dimensions() {
84
+ if (!cachedDimensions) {
85
+ throw new Error("Not initialized. Call init() first.");
86
+ }
87
+ return cachedDimensions;
88
+ }
89
+ /**
90
+ * Shut down the child process and clean up resources.
91
+ */
92
+ export async function dispose() {
93
+ disposed = true;
94
+ initPromise = null;
95
+ if (socket && !socket.destroyed) {
96
+ try {
97
+ await sendRequest({ id: nextId++, type: "dispose" });
98
+ }
99
+ catch {
100
+ // child may already be gone
101
+ }
102
+ }
103
+ socket?.destroy();
104
+ socket = null;
105
+ decoder = null;
106
+ if (child) {
107
+ child.kill("SIGTERM");
108
+ child = null;
109
+ }
110
+ // Clean up files
111
+ if (modelsDir) {
112
+ try {
113
+ unlinkSync(socketPath(modelsDir));
114
+ }
115
+ catch {
116
+ // ignore
117
+ }
118
+ try {
119
+ unlinkSync(pidFilePath(modelsDir));
120
+ }
121
+ catch {
122
+ // ignore
123
+ }
124
+ }
125
+ cachedDimensions = 0;
126
+ pending.clear();
127
+ }
128
+ // ---------------------------------------------------------------------------
129
+ // Internals
130
+ // ---------------------------------------------------------------------------
131
+ function assertReady() {
132
+ if (!socket || socket.destroyed) {
133
+ throw new Error("Not initialized. Call init() first.");
134
+ }
135
+ if (disposed) {
136
+ throw new Error("Engine has been disposed.");
137
+ }
138
+ }
139
+ async function doInit(options) {
140
+ if (socket && !socket.destroyed)
141
+ return;
142
+ const { modelPath, modelsDir: dir, modelName = DEFAULT_MODEL } = options;
143
+ if (!MODELS[modelName]) {
144
+ throw new Error(`Unknown model: ${modelName}. Available: ${Object.keys(MODELS).join(", ")}`);
145
+ }
146
+ // Resolve model directory
147
+ let modelDir;
148
+ if (modelPath) {
149
+ modelDir = modelPath;
150
+ }
151
+ else if (dir) {
152
+ modelDir = join(dir, modelName);
153
+ }
154
+ else {
155
+ throw new Error("Either modelPath or modelsDir is required");
156
+ }
157
+ modelsDir = dir || modelDir;
158
+ const sock = socketPath(modelsDir);
159
+ const pid = pidFilePath(modelsDir);
160
+ // Try connecting to an existing child process
161
+ const connected = await tryConnect(sock);
162
+ if (!connected) {
163
+ // Check for stale PID file
164
+ if (existsSync(pid)) {
165
+ const oldPid = parseInt(readFileSync(pid, "utf-8").trim(), 10);
166
+ if (!isProcessAlive(oldPid)) {
167
+ try {
168
+ unlinkSync(pid);
169
+ }
170
+ catch {
171
+ // ignore
172
+ }
173
+ try {
174
+ unlinkSync(sock);
175
+ }
176
+ catch {
177
+ // ignore
178
+ }
179
+ }
180
+ }
181
+ // Spawn child process
182
+ const childScript = join(dirname(fileURLToPath(import.meta.url)), "child.js");
183
+ child = fork(childScript, [], {
184
+ stdio: ["pipe", "inherit", "inherit", "ipc"],
185
+ // PR #210 (Spawn Wisely): Harper's sandboxed child_process requires
186
+ // a name option for all spawns. fork() is always allowed but still
187
+ // needs a name for singleton PID-file tracking.
188
+ name: "harper-fabric-onnx",
189
+ });
190
+ // Wait for child to signal ready
191
+ await new Promise((resolve, reject) => {
192
+ const timeout = setTimeout(() => {
193
+ reject(new Error("Child process startup timed out"));
194
+ }, 30_000);
195
+ child.once("message", (msg) => {
196
+ clearTimeout(timeout);
197
+ if (msg.ready) {
198
+ resolve();
199
+ }
200
+ else {
201
+ reject(new Error("Unexpected message from child"));
202
+ }
203
+ });
204
+ child.once("error", (err) => {
205
+ clearTimeout(timeout);
206
+ reject(err);
207
+ });
208
+ child.once("exit", (code) => {
209
+ clearTimeout(timeout);
210
+ reject(new Error(`Child process exited with code ${code}`));
211
+ });
212
+ // Send config to child
213
+ child.send({ socketPath: sock, pidPath: pid });
214
+ });
215
+ // Connect to the socket
216
+ const retryConnected = await tryConnect(sock);
217
+ if (!retryConnected) {
218
+ throw new Error("Failed to connect to child process socket");
219
+ }
220
+ }
221
+ // Send init request
222
+ const result = await sendRequest({
223
+ id: nextId++,
224
+ type: "init",
225
+ modelDir,
226
+ modelName,
227
+ });
228
+ cachedDimensions = result;
229
+ disposed = false;
230
+ }
231
+ function tryConnect(sock) {
232
+ return new Promise((resolve) => {
233
+ if (!existsSync(sock)) {
234
+ resolve(false);
235
+ return;
236
+ }
237
+ const conn = connect(sock);
238
+ const timeout = setTimeout(() => {
239
+ conn.destroy();
240
+ resolve(false);
241
+ }, 2_000);
242
+ conn.once("connect", () => {
243
+ clearTimeout(timeout);
244
+ setupSocket(conn);
245
+ resolve(true);
246
+ });
247
+ conn.once("error", () => {
248
+ clearTimeout(timeout);
249
+ conn.destroy();
250
+ resolve(false);
251
+ });
252
+ });
253
+ }
254
+ function setupSocket(conn) {
255
+ socket = conn;
256
+ decoder = new FrameDecoder();
257
+ conn.on("data", (chunk) => {
258
+ decoder.push(chunk);
259
+ for (const msg of decoder.drain()) {
260
+ const res = msg;
261
+ const entry = pending.get(res.id);
262
+ if (!entry)
263
+ continue;
264
+ pending.delete(res.id);
265
+ if (res.ok) {
266
+ entry.resolve(res.result);
267
+ }
268
+ else {
269
+ entry.reject(new Error(res.error));
270
+ }
271
+ }
272
+ });
273
+ conn.on("close", () => {
274
+ for (const [id, entry] of pending) {
275
+ entry.reject(new Error("Connection closed"));
276
+ pending.delete(id);
277
+ }
278
+ });
279
+ conn.on("error", (err) => {
280
+ for (const [id, entry] of pending) {
281
+ entry.reject(err);
282
+ pending.delete(id);
283
+ }
284
+ });
285
+ }
286
+ function sendRequest(req) {
287
+ return new Promise((resolve, reject) => {
288
+ if (!socket || socket.destroyed) {
289
+ reject(new Error("Not connected"));
290
+ return;
291
+ }
292
+ pending.set(req.id, { resolve, reject });
293
+ socket.write(encodeMessage(req));
294
+ });
295
+ }
296
+ function isProcessAlive(pid) {
297
+ try {
298
+ process.kill(pid, 0);
299
+ return true;
300
+ }
301
+ catch {
302
+ return false;
303
+ }
304
+ }
305
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ export interface InitOptions {
2
+ modelPath?: string;
3
+ modelsDir?: string;
4
+ modelName?: string;
5
+ }
6
+ export type Request = {
7
+ id: number;
8
+ type: "init";
9
+ modelDir: string;
10
+ modelName: string;
11
+ } | {
12
+ id: number;
13
+ type: "embed";
14
+ text: string;
15
+ embedType?: "document" | "query";
16
+ } | {
17
+ id: number;
18
+ type: "batch";
19
+ texts: string[];
20
+ embedType?: "document" | "query";
21
+ } | {
22
+ id: number;
23
+ type: "dimensions";
24
+ } | {
25
+ id: number;
26
+ type: "dispose";
27
+ };
28
+ export type Response = {
29
+ id: number;
30
+ ok: true;
31
+ result: number[] | number[][] | number | null;
32
+ } | {
33
+ id: number;
34
+ ok: false;
35
+ error: string;
36
+ };
37
+ export interface ModelConfig {
38
+ repo: string;
39
+ onnxFile: string;
40
+ tokenizerFiles: string[];
41
+ dimensions: number;
42
+ prefix: {
43
+ document: string;
44
+ query: string;
45
+ };
46
+ }
47
+ export declare const MODELS: Record<string, ModelConfig>;
48
+ export declare const DEFAULT_MODEL = "nomic-embed-text";
49
+ export declare function socketPath(modelsDir: string): string;
50
+ export declare function pidFilePath(modelsDir: string): string;
51
+ export declare function encodeMessage(msg: Request | Response): Buffer;
52
+ /**
53
+ * Streaming decoder: feed it chunks via `push()`, pull complete messages
54
+ * from `drain()`. Handles partial reads across TCP boundaries.
55
+ */
56
+ export declare class FrameDecoder {
57
+ private buffer;
58
+ push(chunk: Buffer): void;
59
+ drain(): (Request | Response)[];
60
+ }
61
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,OAAO,GACf;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACjE;IACE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;CAClC,GACD;IACE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC;CAClC,GACD;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE,GAClC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAEpC,MAAM,MAAM,QAAQ,GAChB;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,EAAE,GAAG,MAAM,GAAG,IAAI,CAAA;CAAE,GACvE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAM7C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAuB9C,CAAC;AAEF,eAAO,MAAM,aAAa,qBAAqB,CAAC;AAWhD,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAErD;AAOD,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAM7D;AAED;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAmB;IAEjC,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIzB,KAAK,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC,EAAE;CAWhC"}
@@ -0,0 +1,75 @@
1
+ import { createHash } from "node:crypto";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ export const MODELS = {
5
+ "nomic-embed-text": {
6
+ repo: "nomic-ai/nomic-embed-text-v1.5",
7
+ onnxFile: "onnx/model_fp16.onnx",
8
+ tokenizerFiles: [
9
+ "tokenizer.json",
10
+ "tokenizer_config.json",
11
+ "special_tokens_map.json",
12
+ ],
13
+ dimensions: 768,
14
+ prefix: { document: "search_document: ", query: "search_query: " },
15
+ },
16
+ "nomic-embed-text-v2-moe": {
17
+ repo: "nomic-ai/nomic-embed-text-v2-moe",
18
+ onnxFile: "onnx/model_fp16.onnx",
19
+ tokenizerFiles: [
20
+ "tokenizer.json",
21
+ "tokenizer_config.json",
22
+ "special_tokens_map.json",
23
+ ],
24
+ dimensions: 768,
25
+ prefix: { document: "search_document: ", query: "search_query: " },
26
+ },
27
+ };
28
+ export const DEFAULT_MODEL = "nomic-embed-text";
29
+ // ---------------------------------------------------------------------------
30
+ // Socket / PID path helpers — deterministic from modelsDir so multiple
31
+ // instances with different model directories don't collide.
32
+ // ---------------------------------------------------------------------------
33
+ function pathHash(input) {
34
+ return createHash("md5").update(input).digest("hex").slice(0, 8);
35
+ }
36
+ export function socketPath(modelsDir) {
37
+ return join(tmpdir(), `harper-onnx-${pathHash(modelsDir)}.sock`);
38
+ }
39
+ export function pidFilePath(modelsDir) {
40
+ return join(tmpdir(), `harper-onnx-${pathHash(modelsDir)}.pid`);
41
+ }
42
+ // ---------------------------------------------------------------------------
43
+ // Length-prefixed framing — 4-byte uint32 big-endian length + JSON payload.
44
+ // Handles TCP stream fragmentation so we always get complete messages.
45
+ // ---------------------------------------------------------------------------
46
+ export function encodeMessage(msg) {
47
+ const json = Buffer.from(JSON.stringify(msg));
48
+ const frame = Buffer.allocUnsafe(4 + json.length);
49
+ frame.writeUInt32BE(json.length, 0);
50
+ json.copy(frame, 4);
51
+ return frame;
52
+ }
53
+ /**
54
+ * Streaming decoder: feed it chunks via `push()`, pull complete messages
55
+ * from `drain()`. Handles partial reads across TCP boundaries.
56
+ */
57
+ export class FrameDecoder {
58
+ buffer = Buffer.alloc(0);
59
+ push(chunk) {
60
+ this.buffer = Buffer.concat([this.buffer, chunk]);
61
+ }
62
+ drain() {
63
+ const messages = [];
64
+ while (this.buffer.length >= 4) {
65
+ const len = this.buffer.readUInt32BE(0);
66
+ if (this.buffer.length < 4 + len)
67
+ break;
68
+ const json = this.buffer.subarray(4, 4 + len);
69
+ this.buffer = this.buffer.subarray(4 + len);
70
+ messages.push(JSON.parse(json.toString()));
71
+ }
72
+ return messages;
73
+ }
74
+ }
75
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "harper-fabric-onnx",
3
+ "version": "0.1.0",
4
+ "description": "ONNX Runtime embedding wrapper for Harper Fabric. Runs inference in a dedicated child process — one model instance, one thread pool, no global state races.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "test": "npm run build && node --test test/index.test.js",
17
+ "test:integration": "npm run build && node scripts/test-integration.js",
18
+ "test:harper": "npm run build && node --experimental-strip-types --test test/harper.test.mts",
19
+ "lint": "oxlint --deny-warnings .",
20
+ "lint:fix": "oxlint --deny-warnings --fix .",
21
+ "format": "prettier --write .",
22
+ "format:check": "prettier --check .",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "keywords": [
26
+ "onnx",
27
+ "embeddings",
28
+ "harper",
29
+ "fabric",
30
+ "vector"
31
+ ],
32
+ "author": "Nathan Heskew <nathan@heskew.dev> (https://heskew.dev)",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/heskew/harper-fabric-onnx"
37
+ },
38
+ "dependencies": {
39
+ "@huggingface/transformers": "^3.4.0",
40
+ "onnxruntime-node": "^1.21.0"
41
+ },
42
+ "devDependencies": {
43
+ "@harperdb/code-guidelines": "^0.0.6",
44
+ "@types/node": "^22.0.0",
45
+ "harper": "file:./harper-5.0.0-unreleased.tgz",
46
+ "oxlint": "^1.31.0",
47
+ "prettier": "^3.7.0",
48
+ "typescript": "^5.7.0"
49
+ },
50
+ "engines": {
51
+ "node": ">=22"
52
+ },
53
+ "files": [
54
+ "dist",
55
+ "README.md",
56
+ "LICENSE"
57
+ ]
58
+ }