@rohirik/openltm-core 2.8.1 → 2.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rohirik/openltm-core",
3
- "version": "2.8.1",
3
+ "version": "2.9.0",
4
4
  "description": "Shared LTM storage engine — path-agnostic SQLite core used by Claude Code, OpenCode, and Pi adapters",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -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 no LTM_HONKER_EXT binary is configured", () => {
36
- const prev = process.env["LTM_HONKER_EXT"];
37
- delete process.env["LTM_HONKER_EXT"];
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["LTM_HONKER_EXT"] = prev;
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", () => {
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
- /** Locate a system extension-enabled libsqlite3, or null if none found. */
52
- export function locateSystemSqlite(): string | null {
53
- for (const p of systemSqliteCandidates()) {
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
@@ -99,8 +130,8 @@ function loadVec(db: Database): boolean {
99
130
  }
100
131
 
101
132
  function loadHonker(db: Database): boolean {
102
- const ext = process.env["LTM_HONKER_EXT"];
103
- if (!ext || !existsSync(ext)) return false;
133
+ const ext = locateHonkerExt();
134
+ if (!ext) return false;
104
135
  try {
105
136
  db.loadExtension(ext);
106
137
  return true;
@@ -114,12 +145,14 @@ function loadHonker(db: Database): boolean {
114
145
  * capabilities for the process. Never throws.
115
146
  *
116
147
  * @param opts.vec attempt sqlite-vec (default true; env LTM_DISABLE_VEC wins)
117
- * @param opts.honker attempt Honker (default false; env LTM_DISABLE_HONKER wins,
118
- * and a libhonker_ext path must be set via LTM_HONKER_EXT)
148
+ * @param opts.honker attempt Honker (default: auto-on when a libhonker_ext binary
149
+ * is discoverable via locateHonkerExt(); env LTM_DISABLE_HONKER
150
+ * force-disables, and LTM_HONKER_EXT overrides the binary path)
119
151
  */
120
152
  export function loadExtensions(db: Database, opts?: { vec?: boolean; honker?: boolean }): Capabilities {
121
153
  const wantVec = (opts?.vec ?? true) && !envDisabled("LTM_DISABLE_VEC");
122
- const wantHonker = (opts?.honker ?? false) && !envDisabled("LTM_DISABLE_HONKER");
154
+ const wantHonker =
155
+ (opts?.honker ?? locateHonkerExt() !== null) && !envDisabled("LTM_DISABLE_HONKER");
123
156
 
124
157
  const customSqlite = ensureCustomSqlite();
125
158
  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 = process.env["LTM_HONKER_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