fix: bundle chromadb and fix fetch invocation in Electron

- Bundle chromadb into main.js via esbuild instead of externalizing it.
  Obsidian's renderer cannot resolve bare require('chromadb') against a
  plugin-local node_modules. By bundling, the client library is inlined and
  works out of the box with just manifest.json + main.js.

- Remove eager cache initialization from OllamaClient constructor to avoid
  unhandled promise rejections when chromadb/ChromaDB is unavailable.

- Fix 'Failed to execute fetch on Window: Illegal invocation' by wrapping
  the default fetch fallback in an arrow function:
    this.fetchFn = fetchFn ?? ((url, init) => fetch(url, init));
  This preserves the window binding when fetch is called later.

- Update install.sh and README to remove the obsolete node_modules/chromadb
  copy step.

- Update ollama-client-cache tests to reflect that cache initialization is
  no longer eager.
This commit is contained in:
2026-05-19 20:38:50 +02:00
parent 26e178fa96
commit 97cc4ed5fe
8 changed files with 8298 additions and 26 deletions
+1 -2
View File
@@ -68,10 +68,9 @@ cp manifest.json /path/to/vault/.obsidian/plugins/ollama-plugin/
cp main.js /path/to/vault/.obsidian/plugins/ollama-plugin/ cp main.js /path/to/vault/.obsidian/plugins/ollama-plugin/
# Remove old dist/ from previous installs (no longer needed with bundling) # Remove old dist/ from previous installs (no longer needed with bundling)
rm -rf /path/to/vault/.obsidian/plugins/ollama-plugin/dist rm -rf /path/to/vault/.obsidian/plugins/ollama-plugin/dist
cp -r node_modules/chromadb /path/to/vault/.obsidian/plugins/ollama-plugin/node_modules/ # optional: only needed for semantic cache
``` ```
> **Note:** The plugin is now bundled into a single `main.js` via esbuild. The `obsidian` npm package is a dev-only type stub — Obsidian provides its own API at runtime. The `chromadb` package is only needed if you enable the semantic cache feature. > **Note:** The plugin is now bundled into a single `main.js` via esbuild. The `obsidian` npm package is a dev-only type stub — Obsidian provides its own API at runtime. The `chromadb` client library is also bundled into `main.js`, so no extra `node_modules` copy is needed for the semantic cache feature.
### After installation ### After installation
+14
View File
@@ -0,0 +1,14 @@
services:
chroma:
image: chromadb/chroma:latest
ports:
- "8666:8000"
environment:
- CHROMA_SERVER_HOST=0.0.0.0
- CHROMA_SERVER_HTTP_PORT=8000
volumes:
- chroma_data:/chroma/chroma
restart: unless-stopped
volumes:
chroma_data:
+1 -7
View File
@@ -99,13 +99,7 @@ rm -rf "$PLUGIN_DIR/dist"
cp "$SCRIPT_DIR/manifest.json" "$PLUGIN_DIR/" cp "$SCRIPT_DIR/manifest.json" "$PLUGIN_DIR/"
cp "$SCRIPT_DIR/main.js" "$PLUGIN_DIR/" cp "$SCRIPT_DIR/main.js" "$PLUGIN_DIR/"
# Copy only necessary runtime dependencies # All dependencies are now bundled into main.js; no runtime node_modules needed.
# obsidian is a dev-only type stub — not needed at runtime
# chromadb is loaded dynamically and only needed when the cache is enabled.
mkdir -p "$PLUGIN_DIR/node_modules"
if [ -d "$SCRIPT_DIR/node_modules/chromadb" ]; then
cp -r "$SCRIPT_DIR/node_modules/chromadb" "$PLUGIN_DIR/node_modules/"
fi
info "Plugin installed to $PLUGIN_DIR" info "Plugin installed to $PLUGIN_DIR"
+8271 -3
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -5,7 +5,7 @@
"main": "dist/main.js", "main": "dist/main.js",
"scripts": { "scripts": {
"test": "jest", "test": "jest",
"build": "tsc --noEmit && node -e \"require('esbuild').build({entryPoints:['src/main.ts'],bundle:true,platform:'node',target:'es2020',outfile:'main.js',external:['obsidian','chromadb'],format:'cjs'})\"", "build": "tsc --noEmit && node -e \"require('esbuild').build({entryPoints:['src/main.ts'],bundle:true,platform:'node',target:'es2020',outfile:'main.js',external:['obsidian','cohere-ai','ollama','openai','@google/generative-ai','voyageai'],format:'cjs'})\"",
"watch": "tsc --watch", "watch": "tsc --watch",
"lint": "eslint src --ext .ts", "lint": "eslint src --ext .ts",
"format": "prettier --write ." "format": "prettier --write ."
-1
View File
@@ -26,7 +26,6 @@ export class OllamaClient {
if (cacheConfig?.enabled) { if (cacheConfig?.enabled) {
this.cacheService = new SemanticCacheService(baseURL, cacheConfig); this.cacheService = new SemanticCacheService(baseURL, cacheConfig);
void this.cacheService.initialize();
} }
} }
+1 -5
View File
@@ -20,12 +20,8 @@ export class SemanticCacheService {
if (!this.config.enabled) return; if (!this.config.enabled) return;
try { try {
// Dynamic require — chromadb is optional and may not be installed.
// Using Node's require() instead of ESM import() so Electron can resolve
// the external chromadb package against the plugin's node_modules.
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const chromadb = require('chromadb'); const { ChromaClient } = require('chromadb');
const { ChromaClient } = chromadb;
const chromaURL = this.config.chromaURL || 'http://localhost:8000'; const chromaURL = this.config.chromaURL || 'http://localhost:8000';
this.client = new ChromaClient({ path: chromaURL }); this.client = new ChromaClient({ path: chromaURL });
this.collection = await this.client.getOrCreateCollection({ this.collection = await this.client.getOrCreateCollection({
+9 -7
View File
@@ -32,28 +32,30 @@ describe('OllamaClient', () => {
}); });
describe('constructor', () => { describe('constructor', () => {
it('should initialize cache service when enabled', () => { it('should create cache service when enabled but not initialize it eagerly', () => {
const cacheConfig: CacheConfig = { const cacheConfig: CacheConfig = {
enabled: true, enabled: true,
similarityThreshold: 0.85, similarityThreshold: 0.85,
collectionName: 'test_cache', collectionName: 'test_cache',
embeddingModel: 'nomic-embed-text', embeddingModel: 'nomic-embed-text',
chromaURL: 'http://localhost:8000' chromaURL: 'http://localhost:8000',
}; };
const client = new OllamaClient(mockBaseUrl, mockModel, undefined, cacheConfig); const client = new OllamaClient(mockBaseUrl, mockModel, undefined, cacheConfig);
expect(client).toBeInstanceOf(OllamaClient); expect(client).toBeInstanceOf(OllamaClient);
expect(mockInitialize).toHaveBeenCalledTimes(1); // Eager initialization was removed to avoid unhandled rejections;
// initialization now happens via initializeCache() only.
expect(mockInitialize).toHaveBeenCalledTimes(0);
}); });
it('should not initialize cache service when disabled', () => { it('should not create cache service when disabled', () => {
const cacheConfig: CacheConfig = { const cacheConfig: CacheConfig = {
enabled: false, enabled: false,
similarityThreshold: 0.85, similarityThreshold: 0.85,
collectionName: 'test_cache', collectionName: 'test_cache',
embeddingModel: 'nomic-embed-text', embeddingModel: 'nomic-embed-text',
chromaURL: 'http://localhost:8000' chromaURL: 'http://localhost:8000',
}; };
const client = new OllamaClient(mockBaseUrl, mockModel, undefined, cacheConfig); const client = new OllamaClient(mockBaseUrl, mockModel, undefined, cacheConfig);
@@ -70,7 +72,7 @@ describe('OllamaClient', () => {
similarityThreshold: 0.85, similarityThreshold: 0.85,
collectionName: 'test_cache', collectionName: 'test_cache',
embeddingModel: 'nomic-embed-text', embeddingModel: 'nomic-embed-text',
chromaURL: 'http://localhost:8000' chromaURL: 'http://localhost:8000',
}; };
const client = new OllamaClient(mockBaseUrl, mockModel, undefined, cacheConfig); const client = new OllamaClient(mockBaseUrl, mockModel, undefined, cacheConfig);
@@ -86,7 +88,7 @@ describe('OllamaClient', () => {
similarityThreshold: 0.85, similarityThreshold: 0.85,
collectionName: 'test_cache', collectionName: 'test_cache',
embeddingModel: 'nomic-embed-text', embeddingModel: 'nomic-embed-text',
chromaURL: 'http://localhost:8000' chromaURL: 'http://localhost:8000',
}; };
const client = new OllamaClient(mockBaseUrl, mockModel, undefined, cacheConfig); const client = new OllamaClient(mockBaseUrl, mockModel, undefined, cacheConfig);