@rohirik/openltm-core 2.8.1 → 2.9.1
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/package.json +1 -1
- package/src/__tests__/events/crossProcess.test.ts +4 -0
- package/src/__tests__/events/index.test.ts +4 -0
- package/src/__tests__/extensions.test.ts +18 -4
- package/src/__tests__/queue/index.test.ts +5 -0
- package/src/__tests__/scheduler/index.test.ts +4 -0
- package/src/extensions.ts +50 -9
- package/src/lib/honker.ts +2 -2
- package/vendor/honker/darwin-arm64/libhonker_ext.dylib +0 -0
package/package.json
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
MEMORY_ADDED,
|
|
15
15
|
} from "../../events/index.js";
|
|
16
16
|
import { _resetHonkerForTesting } from "../../lib/honker.js";
|
|
17
|
+
import { resetCapabilitiesForTesting } from "../../extensions.js";
|
|
17
18
|
|
|
18
19
|
function seedDb(): Database {
|
|
19
20
|
const db = new Database(":memory:");
|
|
@@ -25,6 +26,9 @@ describe("cross-process sync — flag + dormant contract (no honker)", () => {
|
|
|
25
26
|
beforeEach(() => {
|
|
26
27
|
_resetHonkerForTesting();
|
|
27
28
|
delete process.env["LTM_HONKER_EXT"];
|
|
29
|
+
// Reset cached caps so a prior test's honker=true (vendored binary is
|
|
30
|
+
// discoverable here) does not leak in — keeps this the dormant contract.
|
|
31
|
+
resetCapabilitiesForTesting();
|
|
28
32
|
});
|
|
29
33
|
afterEach(() => {
|
|
30
34
|
_resetHonkerForTesting();
|
|
@@ -7,11 +7,15 @@
|
|
|
7
7
|
import { describe, it, expect, beforeEach } from "bun:test";
|
|
8
8
|
import { notifyLtm, startLtmListener, LTM_CHANNEL } from "../../events/index.js";
|
|
9
9
|
import { _resetHonkerForTesting } from "../../lib/honker.js";
|
|
10
|
+
import { resetCapabilitiesForTesting } from "../../extensions.js";
|
|
10
11
|
|
|
11
12
|
describe("events — dormant path (no honker)", () => {
|
|
12
13
|
beforeEach(() => {
|
|
13
14
|
_resetHonkerForTesting();
|
|
14
15
|
delete process.env["LTM_HONKER_EXT"];
|
|
16
|
+
// Reset cached caps so a prior test's honker=true (vendored binary is
|
|
17
|
+
// discoverable here) does not leak in — keeps this the dormant contract.
|
|
18
|
+
resetCapabilitiesForTesting();
|
|
15
19
|
});
|
|
16
20
|
|
|
17
21
|
it("exposes the stable channel name", () => {
|
|
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach } from "bun:test";
|
|
|
2
2
|
import { Database } from "bun:sqlite";
|
|
3
3
|
import {
|
|
4
4
|
locateSystemSqlite,
|
|
5
|
+
locateHonkerExt,
|
|
5
6
|
ensureCustomSqlite,
|
|
6
7
|
loadExtensions,
|
|
7
8
|
getCapabilities,
|
|
@@ -32,14 +33,27 @@ describe("extensions — capability probe", () => {
|
|
|
32
33
|
expect(typeof caps.honker).toBe("boolean");
|
|
33
34
|
});
|
|
34
35
|
|
|
35
|
-
it("honker is false when
|
|
36
|
-
const prev = process.env["
|
|
37
|
-
|
|
36
|
+
it("honker is false when force-disabled via LTM_DISABLE_HONKER", () => {
|
|
37
|
+
const prev = process.env["LTM_DISABLE_HONKER"];
|
|
38
|
+
process.env["LTM_DISABLE_HONKER"] = "1";
|
|
38
39
|
ensureCustomSqlite();
|
|
39
40
|
const db = new Database(":memory:");
|
|
40
41
|
const caps = loadExtensions(db);
|
|
41
42
|
expect(caps.honker).toBe(false);
|
|
42
|
-
if (prev) process.env["
|
|
43
|
+
if (prev === undefined) delete process.env["LTM_DISABLE_HONKER"];
|
|
44
|
+
else process.env["LTM_DISABLE_HONKER"] = prev;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("honker auto-loads when a vendored libhonker_ext binary is discoverable", () => {
|
|
48
|
+
// Mirrors the vec test: activation needs customSqlite (process-global, only
|
|
49
|
+
// before the first DB open) AND a discoverable binary. Skip when either is
|
|
50
|
+
// unavailable — that is the graceful-degradation path, not a failure.
|
|
51
|
+
if (locateHonkerExt() === null || process.env["LTM_DISABLE_HONKER"]) return;
|
|
52
|
+
ensureCustomSqlite();
|
|
53
|
+
const db = new Database(":memory:");
|
|
54
|
+
const caps = loadExtensions(db);
|
|
55
|
+
if (!caps.customSqlite) return;
|
|
56
|
+
expect(caps.honker).toBe(true);
|
|
43
57
|
});
|
|
44
58
|
|
|
45
59
|
it("respects LTM_DISABLE_VEC — vec capability is false when force-disabled", () => {
|
|
@@ -15,10 +15,15 @@ import {
|
|
|
15
15
|
} from "../../queue/index.js";
|
|
16
16
|
import { startEmbeddingWorker } from "../../queue/worker.js";
|
|
17
17
|
import { isHonkerAvailable, _resetHonkerForTesting } from "../../lib/honker.js";
|
|
18
|
+
import { resetCapabilitiesForTesting } from "../../extensions.js";
|
|
18
19
|
|
|
19
20
|
describe("queue — dormant path (no honker)", () => {
|
|
20
21
|
beforeEach(() => {
|
|
21
22
|
_resetHonkerForTesting();
|
|
23
|
+
// Force the dormant contract: a prior test may have cached caps.honker=true
|
|
24
|
+
// process-wide (the vendored binary is discoverable here). Reset so
|
|
25
|
+
// getCapabilities() returns the all-false default and honker stays dormant.
|
|
26
|
+
resetCapabilitiesForTesting();
|
|
22
27
|
});
|
|
23
28
|
|
|
24
29
|
it("EMBED_QUEUE has a stable name", () => {
|
|
@@ -13,11 +13,15 @@ import {
|
|
|
13
13
|
DEFAULT_JANITOR_CRON,
|
|
14
14
|
} from "../../scheduler/index.js";
|
|
15
15
|
import { _resetHonkerForTesting } from "../../lib/honker.js";
|
|
16
|
+
import { resetCapabilitiesForTesting } from "../../extensions.js";
|
|
16
17
|
|
|
17
18
|
describe("scheduler — dormant path (no honker)", () => {
|
|
18
19
|
beforeEach(() => {
|
|
19
20
|
_resetHonkerForTesting();
|
|
20
21
|
delete process.env["LTM_HONKER_EXT"];
|
|
22
|
+
// Reset cached caps so a prior test's honker=true (vendored binary is
|
|
23
|
+
// discoverable here) does not leak in — keeps this the dormant contract.
|
|
24
|
+
resetCapabilitiesForTesting();
|
|
21
25
|
});
|
|
22
26
|
|
|
23
27
|
it("exposes stable names + default cron", () => {
|
package/src/extensions.ts
CHANGED
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { Database } from "bun:sqlite";
|
|
15
15
|
import { existsSync } from "fs";
|
|
16
|
+
import { dirname, join } from "path";
|
|
17
|
+
import { fileURLToPath } from "url";
|
|
18
|
+
|
|
19
|
+
/** Directory of this module — used to resolve the vendored Honker binary. */
|
|
20
|
+
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
16
21
|
|
|
17
22
|
export interface Capabilities {
|
|
18
23
|
/** A system extension-enabled SQLite is active for this process. */
|
|
@@ -48,9 +53,9 @@ function envDisabled(name: string): boolean {
|
|
|
48
53
|
return v === "1" || v === "true";
|
|
49
54
|
}
|
|
50
55
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
for (const p of
|
|
56
|
+
/** First candidate path that exists on disk, or null. Never throws. */
|
|
57
|
+
function firstExisting(candidates: string[]): string | null {
|
|
58
|
+
for (const p of candidates) {
|
|
54
59
|
try {
|
|
55
60
|
if (existsSync(p)) return p;
|
|
56
61
|
} catch {
|
|
@@ -60,6 +65,32 @@ export function locateSystemSqlite(): string | null {
|
|
|
60
65
|
return null;
|
|
61
66
|
}
|
|
62
67
|
|
|
68
|
+
/** Locate a system extension-enabled libsqlite3, or null if none found. */
|
|
69
|
+
export function locateSystemSqlite(): string | null {
|
|
70
|
+
return firstExisting(systemSqliteCandidates());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Probe order: explicit override first, then the vendored binary for this
|
|
74
|
+
// platform (packages/openltm-core/vendor/honker/<platform>/libhonker_ext.<ext>).
|
|
75
|
+
function honkerExtCandidates(): string[] {
|
|
76
|
+
const out: string[] = [];
|
|
77
|
+
const override = process.env["LTM_HONKER_EXT"];
|
|
78
|
+
if (override) out.push(override);
|
|
79
|
+
const platform = `${process.platform}-${process.arch}`;
|
|
80
|
+
const suffix = process.platform === "darwin" ? "dylib" : "so";
|
|
81
|
+
out.push(join(MODULE_DIR, "..", "vendor", "honker", platform, `libhonker_ext.${suffix}`));
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Locate the Honker loadable extension, or null if none is available for this
|
|
87
|
+
* platform. Mirrors locateSystemSqlite(): LTM_HONKER_EXT override wins, then the
|
|
88
|
+
* vendored per-platform binary. A null result means Honker stays dormant.
|
|
89
|
+
*/
|
|
90
|
+
export function locateHonkerExt(): string | null {
|
|
91
|
+
return firstExisting(honkerExtCandidates());
|
|
92
|
+
}
|
|
93
|
+
|
|
63
94
|
/**
|
|
64
95
|
* Switch the process to a system extension-enabled SQLite. Idempotent and
|
|
65
96
|
* never throws. Must run before the first Database is constructed to take
|
|
@@ -74,8 +105,16 @@ export function ensureCustomSqlite(): boolean {
|
|
|
74
105
|
const lib = locateSystemSqlite();
|
|
75
106
|
if (!lib) return false;
|
|
76
107
|
try {
|
|
77
|
-
|
|
108
|
+
const DB = Database as unknown as { setCustomSQLite: (p: string) => void };
|
|
109
|
+
DB.setCustomSQLite(lib);
|
|
78
110
|
_customSqliteApplied = true;
|
|
111
|
+
// setCustomSQLite is a once-per-process global (Bun throws "SQLite already
|
|
112
|
+
// loaded" on a second call). honker-bun's open() calls it again on its own
|
|
113
|
+
// connection and would throw — silently killing the honker handle even
|
|
114
|
+
// though the extension-enabled SQLite is already active process-wide. Now
|
|
115
|
+
// that we've loaded it, neutralise further calls so honker-bun (and any
|
|
116
|
+
// other consumer) no-ops instead of throwing.
|
|
117
|
+
DB.setCustomSQLite = () => {};
|
|
79
118
|
return true;
|
|
80
119
|
} catch {
|
|
81
120
|
return false;
|
|
@@ -99,8 +138,8 @@ function loadVec(db: Database): boolean {
|
|
|
99
138
|
}
|
|
100
139
|
|
|
101
140
|
function loadHonker(db: Database): boolean {
|
|
102
|
-
const ext =
|
|
103
|
-
if (!ext
|
|
141
|
+
const ext = locateHonkerExt();
|
|
142
|
+
if (!ext) return false;
|
|
104
143
|
try {
|
|
105
144
|
db.loadExtension(ext);
|
|
106
145
|
return true;
|
|
@@ -114,12 +153,14 @@ function loadHonker(db: Database): boolean {
|
|
|
114
153
|
* capabilities for the process. Never throws.
|
|
115
154
|
*
|
|
116
155
|
* @param opts.vec attempt sqlite-vec (default true; env LTM_DISABLE_VEC wins)
|
|
117
|
-
* @param opts.honker attempt Honker (default
|
|
118
|
-
*
|
|
156
|
+
* @param opts.honker attempt Honker (default: auto-on when a libhonker_ext binary
|
|
157
|
+
* is discoverable via locateHonkerExt(); env LTM_DISABLE_HONKER
|
|
158
|
+
* force-disables, and LTM_HONKER_EXT overrides the binary path)
|
|
119
159
|
*/
|
|
120
160
|
export function loadExtensions(db: Database, opts?: { vec?: boolean; honker?: boolean }): Capabilities {
|
|
121
161
|
const wantVec = (opts?.vec ?? true) && !envDisabled("LTM_DISABLE_VEC");
|
|
122
|
-
const wantHonker =
|
|
162
|
+
const wantHonker =
|
|
163
|
+
(opts?.honker ?? locateHonkerExt() !== null) && !envDisabled("LTM_DISABLE_HONKER");
|
|
123
164
|
|
|
124
165
|
const customSqlite = ensureCustomSqlite();
|
|
125
166
|
const vec = customSqlite && wantVec ? loadVec(db) : false;
|
package/src/lib/honker.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* pre-Honker behaviour (inline embed, HTTP poll, file-watcher).
|
|
14
14
|
*/
|
|
15
15
|
import type { HonkerDb, HonkerModule } from "./honkerTypes.js";
|
|
16
|
-
import { getCapabilities, locateSystemSqlite } from "../extensions.js";
|
|
16
|
+
import { getCapabilities, locateSystemSqlite, locateHonkerExt } from "../extensions.js";
|
|
17
17
|
import { DB_PATH } from "../shared-db.js";
|
|
18
18
|
|
|
19
19
|
let _handle: HonkerDb | null | undefined = undefined;
|
|
@@ -31,7 +31,7 @@ export function getHonker(): HonkerDb | null {
|
|
|
31
31
|
|
|
32
32
|
function openHonker(): HonkerDb | null {
|
|
33
33
|
if (!getCapabilities().honker) return null;
|
|
34
|
-
const ext =
|
|
34
|
+
const ext = locateHonkerExt();
|
|
35
35
|
if (!ext) return null;
|
|
36
36
|
try {
|
|
37
37
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
Binary file
|