Files
wehub-resource-sync 23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:02:19 +08:00

1271 lines
31 KiB
Markdown

# sql.js Implementation Guide for Claude-Flow
**Implementation Date**: 2026-01-03
**Target Version**: Claude-Flow v3.0
**Author**: Research Agent
---
## Overview
This guide provides step-by-step implementation instructions for integrating sql.js as a cross-platform SQLite provider alongside better-sqlite3.
---
## Architecture Diagram
```
┌─────────────────────────────────────────────────────────┐
│ Application Layer │
│ (DatabaseService, MemoryManager, PersistenceManager) │
└────────────────────┬────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ DatabaseManager / sqlite-wrapper │
│ (Provider Selection & Abstraction) │
└─────────────┬───────────────┬───────────────┬───────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ SQLiteProvider│ │SqlJsProvider│ │ JSONProvider │
│(better-sqlite3)│ │ (sql.js) │ │ (Fallback) │
└─────────────┘ └─────────────┘ └─────────────┘
(Linux/macOS) (Windows/All) (Ultimate FB)
```
---
## File Modifications Plan
### New Files
1. **`src/memory/backends/sqljs.ts`** - sql.js backend implementation
2. **`src/memory/providers/sqljs-provider.ts`** - Provider wrapper
3. **`src/utils/sqljs-loader.ts`** - WASM loader utility
4. **`tests/unit/memory/sqljs-backend.test.ts`** - Unit tests
5. **`tests/integration/sqljs-integration.test.ts`** - Integration tests
### Modified Files
1. **`src/memory/sqlite-wrapper.js`** - Add sql.js detection and wrapper
2. **`src/core/DatabaseManager.ts`** - Add SqlJsProvider support
3. **`package.json`** - Add sql.js dependency
4. **`tsconfig.json`** - Configure WASM file handling
5. **`.swcrc`** - Update build configuration
---
## Step-by-Step Implementation
### Step 1: Install sql.js Dependency
**File**: `/home/user/claude-flow/package.json`
```json
{
"dependencies": {
"sql.js": "^1.13.0",
"existing-deps": "..."
},
"optionalDependencies": {
"better-sqlite3": "^12.2.0",
"agentdb": "^1.6.1"
}
}
```
**Command**:
```bash
npm install sql.js@^1.13.0 --save
```
---
### Step 2: Create sql.js Backend Implementation
**File**: `/home/user/claude-flow/src/memory/backends/sqljs.ts`
```typescript
/**
* sql.js Backend Implementation
* Cross-platform SQLite backend using WebAssembly
*/
import initSqlJs, { Database as SqlJsDatabase } from 'sql.js';
import { promises as fs } from 'fs';
import path from 'path';
import type { IMemoryBackend } from './base.js';
import type { MemoryEntry, MemoryQuery } from '../../utils/types.js';
import type { ILogger } from '../../core/logger.js';
import { MemoryBackendError } from '../../utils/errors.js';
/**
* Configuration for sql.js backend
*/
export interface SqlJsBackendConfig {
dbPath: string;
autoSave?: boolean;
saveInterval?: number;
wasmPath?: string;
}
/**
* sql.js-based memory backend with file persistence
*/
export class SqlJsBackend implements IMemoryBackend {
private SQL: any;
private db?: SqlJsDatabase;
private dbPath: string;
private dirty: boolean = false;
private saveTimer?: NodeJS.Timeout;
private autoSave: boolean;
private saveInterval: number;
constructor(
config: SqlJsBackendConfig,
private logger: ILogger
) {
this.dbPath = config.dbPath;
this.autoSave = config.autoSave ?? true;
this.saveInterval = config.saveInterval ?? 30000; // 30 seconds default
}
async initialize(): Promise<void> {
this.logger.info('Initializing sql.js backend', {
dbPath: this.dbPath,
autoSave: this.autoSave
});
try {
// Load SQL.js WASM module
this.SQL = await initSqlJs({
locateFile: (file) => {
// Try multiple paths for WASM file
const paths = [
path.join(__dirname, '../../../node_modules/sql.js/dist', file),
path.join(process.cwd(), 'node_modules/sql.js/dist', file),
path.join(__dirname, '../../..', 'node_modules/sql.js/dist', file)
];
for (const testPath of paths) {
try {
if (require('fs').existsSync(testPath)) {
return testPath;
}
} catch {}
}
// Fallback to default
return file;
}
});
// Load existing database or create new one
const dir = path.dirname(this.dbPath);
await fs.mkdir(dir, { recursive: true });
if (await this.fileExists(this.dbPath)) {
await this.loadFromFile();
} else {
this.db = new this.SQL.Database();
this.logger.info('Created new sql.js database');
}
// Create schema
this.createTables();
this.createIndexes();
// Setup auto-save if enabled
if (this.autoSave) {
this.setupAutoSave();
}
this.logger.info('sql.js backend initialized successfully');
} catch (error) {
throw new MemoryBackendError('Failed to initialize sql.js backend', { error });
}
}
private async fileExists(filePath: string): Promise<boolean> {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
private async loadFromFile(): Promise<void> {
try {
const buffer = await fs.readFile(this.dbPath);
this.db = new this.SQL.Database(new Uint8Array(buffer));
this.logger.info('Loaded existing database from file', {
dbPath: this.dbPath,
size: buffer.length
});
} catch (error) {
this.logger.warn('Failed to load database file, creating new', { error });
this.db = new this.SQL.Database();
}
}
private createTables(): void {
const sql = `
CREATE TABLE IF NOT EXISTS memory_entries (
id TEXT PRIMARY KEY,
agent_id TEXT NOT NULL,
session_id TEXT NOT NULL,
type TEXT NOT NULL,
content TEXT NOT NULL,
context TEXT NOT NULL,
timestamp TEXT NOT NULL,
tags TEXT NOT NULL,
version INTEGER NOT NULL,
parent_id TEXT,
metadata TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`;
try {
this.db!.run(sql);
} catch (error) {
throw new MemoryBackendError('Failed to create tables', { error });
}
}
private createIndexes(): void {
const indexes = [
'CREATE INDEX IF NOT EXISTS idx_agent_id ON memory_entries(agent_id)',
'CREATE INDEX IF NOT EXISTS idx_session_id ON memory_entries(session_id)',
'CREATE INDEX IF NOT EXISTS idx_type ON memory_entries(type)',
'CREATE INDEX IF NOT EXISTS idx_timestamp ON memory_entries(timestamp)',
'CREATE INDEX IF NOT EXISTS idx_parent_id ON memory_entries(parent_id)',
];
for (const sql of indexes) {
try {
this.db!.run(sql);
} catch (error) {
this.logger.warn('Failed to create index', { sql, error });
}
}
}
private setupAutoSave(): void {
this.logger.debug('Setting up auto-save', { interval: this.saveInterval });
}
private markDirty(): void {
this.dirty = true;
if (this.autoSave) {
if (this.saveTimer) clearTimeout(this.saveTimer);
this.saveTimer = setTimeout(() => {
this.saveToDisk().catch(err => {
this.logger.error('Auto-save failed', err);
});
}, this.saveInterval);
}
}
private async saveToDisk(): Promise<void> {
if (!this.dirty || !this.db) return;
try {
const data = this.db.export();
await fs.writeFile(this.dbPath, data);
this.dirty = false;
this.logger.debug('Database saved to disk', {
dbPath: this.dbPath,
size: data.length
});
} catch (error) {
this.logger.error('Failed to save database', { error });
throw new MemoryBackendError('Failed to save database', { error });
}
}
async store(entry: MemoryEntry): Promise<void> {
if (!this.db) {
throw new MemoryBackendError('Database not initialized');
}
const sql = `
INSERT OR REPLACE INTO memory_entries (
id, agent_id, session_id, type, content,
context, timestamp, tags, version, parent_id, metadata
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`;
const params = [
entry.id,
entry.agentId,
entry.sessionId,
entry.type,
entry.content,
JSON.stringify(entry.context),
entry.timestamp.toISOString(),
JSON.stringify(entry.tags),
entry.version,
entry.parentId || null,
entry.metadata ? JSON.stringify(entry.metadata) : null,
];
try {
this.db.run(sql, params);
this.markDirty();
} catch (error) {
throw new MemoryBackendError('Failed to store entry', { error, entryId: entry.id });
}
}
async retrieve(id: string): Promise<MemoryEntry | undefined> {
if (!this.db) {
throw new MemoryBackendError('Database not initialized');
}
const sql = 'SELECT * FROM memory_entries WHERE id = ?';
try {
const result = this.db.exec(sql, [id]);
if (result.length === 0 || result[0].values.length === 0) {
return undefined;
}
return this.rowToEntry(result[0]);
} catch (error) {
throw new MemoryBackendError('Failed to retrieve entry', { error, entryId: id });
}
}
async update(id: string, entry: MemoryEntry): Promise<void> {
// sql.js INSERT OR REPLACE handles updates
await this.store(entry);
}
async delete(id: string): Promise<void> {
if (!this.db) {
throw new MemoryBackendError('Database not initialized');
}
const sql = 'DELETE FROM memory_entries WHERE id = ?';
try {
this.db.run(sql, [id]);
this.markDirty();
} catch (error) {
throw new MemoryBackendError('Failed to delete entry', { error, entryId: id });
}
}
async query(query: MemoryQuery): Promise<MemoryEntry[]> {
if (!this.db) {
throw new MemoryBackendError('Database not initialized');
}
const conditions: string[] = [];
const params: unknown[] = [];
if (query.agentId) {
conditions.push('agent_id = ?');
params.push(query.agentId);
}
if (query.sessionId) {
conditions.push('session_id = ?');
params.push(query.sessionId);
}
if (query.type) {
conditions.push('type = ?');
params.push(query.type);
}
if (query.startTime) {
conditions.push('timestamp >= ?');
params.push(query.startTime.toISOString());
}
if (query.endTime) {
conditions.push('timestamp <= ?');
params.push(query.endTime.toISOString());
}
if (query.search) {
conditions.push('(content LIKE ? OR tags LIKE ?)');
params.push(`%${query.search}%`, `%${query.search}%`);
}
if (query.tags && query.tags.length > 0) {
const tagConditions = query.tags.map(() => 'tags LIKE ?');
conditions.push(`(${tagConditions.join(' OR ')})`);
query.tags.forEach((tag: string) => params.push(`%"${tag}"%`));
}
let sql = 'SELECT * FROM memory_entries';
if (conditions.length > 0) {
sql += ' WHERE ' + conditions.join(' AND ');
}
sql += ' ORDER BY timestamp DESC';
if (query.limit) {
sql += ' LIMIT ?';
params.push(query.limit);
}
if (query.offset) {
sql += ' OFFSET ?';
params.push(query.offset);
}
try {
const result = this.db.exec(sql, params);
if (result.length === 0) {
return [];
}
return this.rowsToEntries(result[0]);
} catch (error) {
throw new MemoryBackendError('Failed to query entries', { error, query });
}
}
async getAllEntries(): Promise<MemoryEntry[]> {
if (!this.db) {
throw new MemoryBackendError('Database not initialized');
}
const sql = 'SELECT * FROM memory_entries ORDER BY timestamp DESC';
try {
const result = this.db.exec(sql);
if (result.length === 0) {
return [];
}
return this.rowsToEntries(result[0]);
} catch (error) {
throw new MemoryBackendError('Failed to get all entries', { error });
}
}
async getHealthStatus(): Promise<{
healthy: boolean;
error?: string;
metrics?: Record<string, number>;
}> {
if (!this.db) {
return {
healthy: false,
error: 'Database not initialized',
};
}
try {
// Test query
this.db.exec('SELECT 1');
// Get metrics
const countResult = this.db.exec('SELECT COUNT(*) as count FROM memory_entries');
const entryCount = countResult[0]?.values[0]?.[0] || 0;
const data = this.db.export();
const dbSize = data.length;
return {
healthy: true,
metrics: {
entryCount: Number(entryCount),
dbSizeBytes: dbSize,
},
};
} catch (error) {
return {
healthy: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}
async shutdown(): Promise<void> {
this.logger.info('Shutting down sql.js backend');
if (this.saveTimer) {
clearTimeout(this.saveTimer);
}
// Final save
if (this.dirty) {
await this.saveToDisk();
}
if (this.db) {
this.db.close();
delete this.db;
}
}
private rowToEntry(result: any): MemoryEntry {
const columns = result.columns;
const values = result.values[0];
const row: Record<string, any> = {};
columns.forEach((col: string, i: number) => {
row[col] = values[i];
});
return this.mapRowToEntry(row);
}
private rowsToEntries(result: any): MemoryEntry[] {
const columns = result.columns;
const values = result.values;
return values.map((rowValues: any[]) => {
const row: Record<string, any> = {};
columns.forEach((col: string, i: number) => {
row[col] = rowValues[i];
});
return this.mapRowToEntry(row);
});
}
private mapRowToEntry(row: Record<string, unknown>): MemoryEntry {
const entry: MemoryEntry = {
id: row.id as string,
agentId: row.agent_id as string,
sessionId: row.session_id as string,
type: row.type as MemoryEntry['type'],
content: row.content as string,
context: JSON.parse(row.context as string),
timestamp: new Date(row.timestamp as string),
tags: JSON.parse(row.tags as string),
version: row.version as number,
};
if (row.parent_id) {
entry.parentId = row.parent_id as string;
}
if (row.metadata) {
entry.metadata = JSON.parse(row.metadata as string);
}
return entry;
}
/**
* Force immediate save to disk
*/
async flush(): Promise<void> {
await this.saveToDisk();
}
/**
* Export database as binary
*/
export(): Uint8Array {
if (!this.db) {
throw new MemoryBackendError('Database not initialized');
}
return this.db.export();
}
/**
* Get database size in bytes
*/
getSize(): number {
if (!this.db) return 0;
return this.db.export().length;
}
}
```
---
### Step 3: Update sqlite-wrapper.js
**File**: `/home/user/claude-flow/src/memory/sqlite-wrapper.js`
Add sql.js detection and provider creation:
```javascript
// Add after existing imports
let SqlJs = null;
let sqlJsAvailable = false;
let sqlJsLoadError = null;
/**
* Try to load sql.js
*/
async function tryLoadSqlJs() {
try {
const module = await import('sql.js');
SqlJs = module.default;
sqlJsAvailable = true;
console.log('✅ sql.js loaded successfully (cross-platform SQLite mode)');
return true;
} catch (error) {
sqlJsLoadError = error;
console.warn('⚠️ sql.js not available:', error.message);
return false;
}
}
/**
* Check if sql.js is available
*/
export async function isSqlJsAvailable() {
if (sqlJsAvailable !== null) {
return sqlJsAvailable;
}
await tryLoadSqlJs();
return sqlJsAvailable;
}
/**
* Get sql.js constructor
*/
export async function getSqlJs() {
if (!sqlJsAvailable && sqlJsLoadError === null) {
await tryLoadSqlJs();
}
return SqlJs;
}
/**
* Create database with automatic provider selection
*/
export async function createDatabase(dbPath, options = {}) {
const preferredProvider = options.provider || getRecommendedProvider();
// Try preferred provider first
if (preferredProvider === 'better-sqlite3' && await isSQLiteAvailable()) {
return createBetterSqlite3Database(dbPath);
}
if (preferredProvider === 'sql.js' && await isSqlJsAvailable()) {
return createSqlJsDatabase(dbPath);
}
// Fallback chain
if (await isSQLiteAvailable()) {
return createBetterSqlite3Database(dbPath);
}
if (await isSqlJsAvailable()) {
return createSqlJsDatabase(dbPath);
}
throw new Error('No SQLite provider available (tried better-sqlite3 and sql.js)');
}
/**
* Create sql.js database instance
*/
async function createSqlJsDatabase(dbPath) {
const SQL = await getSqlJs();
if (!SQL) {
throw new Error('sql.js is not available');
}
// Initialize SQL.js
const initSql = await SQL({
locateFile: file => {
const path = require('path');
return path.join(process.cwd(), 'node_modules/sql.js/dist', file);
}
});
// Load or create database
const fs = require('fs');
let db;
if (fs.existsSync(dbPath)) {
const buffer = fs.readFileSync(dbPath);
db = new initSql.Database(new Uint8Array(buffer));
} else {
db = new initSql.Database();
}
// Wrap to match better-sqlite3 API
return wrapSqlJsDatabase(db, dbPath);
}
/**
* Wrap sql.js database to match better-sqlite3 API
*/
function wrapSqlJsDatabase(db, dbPath) {
let dirty = false;
let saveTimer = null;
const markDirty = () => {
dirty = true;
if (saveTimer) clearTimeout(saveTimer);
saveTimer = setTimeout(saveToDisk, 5000);
};
const saveToDisk = () => {
if (!dirty) return;
const fs = require('fs');
const data = db.export();
fs.writeFileSync(dbPath, data);
dirty = false;
};
return {
// Mimic better-sqlite3 API
prepare: (sql) => {
const stmt = db.prepare(sql);
return {
run: (...params) => {
stmt.bind(params);
stmt.step();
stmt.reset();
markDirty();
return { changes: 1 }; // Simplified
},
get: (...params) => {
stmt.bind(params);
const result = stmt.step() ? stmt.getAsObject() : null;
stmt.reset();
return result;
},
all: (...params) => {
stmt.bind(params);
const results = [];
while (stmt.step()) {
results.push(stmt.getAsObject());
}
stmt.reset();
return results;
}
};
},
exec: (sql) => {
db.run(sql);
markDirty();
},
pragma: (pragma) => {
// sql.js doesn't support pragma in the same way
// Silently ignore for compatibility
},
close: () => {
if (saveTimer) clearTimeout(saveTimer);
saveToDisk();
db.close();
},
// sql.js specific methods
export: () => db.export(),
_sqljs: true // Marker for detection
};
}
/**
* Get recommended provider based on platform
*/
function getRecommendedProvider() {
if (process.platform === 'win32') {
return 'sql.js'; // Windows: prefer sql.js (no compilation)
}
if (process.platform === 'darwin' && process.arch === 'arm64') {
return 'better-sqlite3'; // macOS ARM: try native first
}
return 'better-sqlite3'; // Linux/Unix: native is usually best
}
// Pre-load both providers on module import
(async () => {
await tryLoadSQLite();
await tryLoadSqlJs();
})();
export default {
isSQLiteAvailable,
isSqlJsAvailable,
getSQLiteDatabase,
getSqlJs,
createDatabase,
isWindows,
getStorageRecommendations
};
```
---
### Step 4: Update DatabaseManager.ts
**File**: `/home/user/claude-flow/src/core/DatabaseManager.ts`
Add SqlJsProvider option:
```typescript
// Add import
import { SqlJsBackend } from '../memory/backends/sqljs.js';
export class DatabaseManager implements IDatabaseProvider {
// ... existing code ...
private async initializeSQLiteWithRecovery(): Promise<IDatabaseProvider> {
try {
// Try better-sqlite3 first
return new SQLiteProvider(this.dbPath);
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
// Check for native module issues
if (error instanceof NativeModuleError || isNativeModuleVersionError(error)) {
console.warn('\n' + (error instanceof NativeModuleError ? error.message : getNativeModuleRecoveryMessage(error)));
console.warn(' Attempting fallback to sql.js (cross-platform SQLite)...\n');
// Try sql.js before JSON
try {
return new SqlJsProvider(this.dbPath, this.logger);
} catch (sqlJsError) {
console.warn(' sql.js not available, falling back to JSON storage.\n');
}
}
// Final fallback to JSON
this.provider = new JSONProvider(this.dbPath.replace('.sqlite', '.json'));
this.dbType = 'json';
return this.provider;
}
}
}
/**
* sql.js Provider implementation
*/
class SqlJsProvider implements IDatabaseProvider {
private backend: SqlJsBackend;
private dbPath: string;
constructor(dbPath: string, logger: ILogger) {
this.dbPath = dbPath;
this.backend = new SqlJsBackend(
{
dbPath,
autoSave: true,
saveInterval: 30000
},
logger
);
}
async initialize(): Promise<void> {
await this.backend.initialize();
}
async store(key: string, value: any, namespace: string = 'default'): Promise<void> {
// Adapt to MemoryEntry format
const entry = {
id: `${namespace}:${key}`,
agentId: namespace,
sessionId: 'default',
type: 'data' as const,
content: typeof value === 'string' ? value : JSON.stringify(value),
context: { namespace },
timestamp: new Date(),
tags: [],
version: 1
};
await this.backend.store(entry);
}
async retrieve(key: string, namespace: string = 'default'): Promise<any> {
const entry = await this.backend.retrieve(`${namespace}:${key}`);
if (!entry) return null;
try {
return JSON.parse(entry.content);
} catch {
return entry.content;
}
}
async delete(key: string, namespace: string = 'default'): Promise<boolean> {
try {
await this.backend.delete(`${namespace}:${key}`);
return true;
} catch {
return false;
}
}
async list(namespace: string = 'default'): Promise<string[]> {
const entries = await this.backend.query({ agentId: namespace });
return entries.map(e => e.id.replace(`${namespace}:`, ''));
}
async close(): Promise<void> {
await this.backend.shutdown();
}
}
```
---
### Step 5: Update Build Configuration
**File**: `/.swcrc`
```json
{
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": false,
"decorators": true,
"dynamicImport": true
},
"target": "es2020",
"externalHelpers": false,
"keepClassNames": true
},
"module": {
"type": "es6",
"strict": false,
"strictMode": true,
"lazy": false,
"noInterop": false
},
"copyFiles": {
"enable": true,
"files": {
"**/*.wasm": true
}
}
}
```
**File**: `/home/user/claude-flow/package.json` (files section)
```json
{
"files": [
"dist/",
"src/",
"node_modules/sql.js/dist/sql-wasm.wasm",
"node_modules/sql.js/dist/sql-wasm.js"
]
}
```
---
### Step 6: Add Tests
**File**: `/home/user/claude-flow/tests/unit/memory/sqljs-backend.test.ts`
```typescript
import { SqlJsBackend } from '../../../src/memory/backends/sqljs';
import { MemoryEntry } from '../../../src/utils/types';
import { createMockLogger } from '../../helpers/logger';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as os from 'os';
describe('SqlJsBackend', () => {
let backend: SqlJsBackend;
let dbPath: string;
let logger: any;
beforeEach(async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'sqljs-test-'));
dbPath = path.join(tmpDir, 'test.db');
logger = createMockLogger();
backend = new SqlJsBackend(
{
dbPath,
autoSave: false // Disable for tests
},
logger
);
await backend.initialize();
});
afterEach(async () => {
await backend.shutdown();
await fs.remove(path.dirname(dbPath));
});
describe('store and retrieve', () => {
it('should store and retrieve an entry', async () => {
const entry: MemoryEntry = {
id: 'test-1',
agentId: 'agent-1',
sessionId: 'session-1',
type: 'observation',
content: 'Test content',
context: { key: 'value' },
timestamp: new Date(),
tags: ['test', 'demo'],
version: 1
};
await backend.store(entry);
const retrieved = await backend.retrieve('test-1');
expect(retrieved).toBeDefined();
expect(retrieved?.id).toBe('test-1');
expect(retrieved?.content).toBe('Test content');
expect(retrieved?.tags).toEqual(['test', 'demo']);
});
it('should return undefined for non-existent entry', async () => {
const retrieved = await backend.retrieve('non-existent');
expect(retrieved).toBeUndefined();
});
it('should update existing entry', async () => {
const entry: MemoryEntry = {
id: 'test-1',
agentId: 'agent-1',
sessionId: 'session-1',
type: 'observation',
content: 'Original content',
context: {},
timestamp: new Date(),
tags: [],
version: 1
};
await backend.store(entry);
const updated = { ...entry, content: 'Updated content' };
await backend.update('test-1', updated);
const retrieved = await backend.retrieve('test-1');
expect(retrieved?.content).toBe('Updated content');
});
});
describe('query', () => {
beforeEach(async () => {
const entries: MemoryEntry[] = [
{
id: 'entry-1',
agentId: 'agent-1',
sessionId: 'session-1',
type: 'observation',
content: 'Content 1',
context: {},
timestamp: new Date('2026-01-01'),
tags: ['tag1'],
version: 1
},
{
id: 'entry-2',
agentId: 'agent-1',
sessionId: 'session-1',
type: 'action',
content: 'Content 2',
context: {},
timestamp: new Date('2026-01-02'),
tags: ['tag2'],
version: 1
},
{
id: 'entry-3',
agentId: 'agent-2',
sessionId: 'session-2',
type: 'observation',
content: 'Content 3',
context: {},
timestamp: new Date('2026-01-03'),
tags: ['tag1'],
version: 1
}
];
for (const entry of entries) {
await backend.store(entry);
}
});
it('should query by agentId', async () => {
const results = await backend.query({ agentId: 'agent-1' });
expect(results).toHaveLength(2);
expect(results[0].agentId).toBe('agent-1');
});
it('should query by type', async () => {
const results = await backend.query({ type: 'observation' });
expect(results).toHaveLength(2);
expect(results.every(r => r.type === 'observation')).toBe(true);
});
it('should support pagination', async () => {
const results = await backend.query({ limit: 2, offset: 1 });
expect(results).toHaveLength(2);
});
});
describe('file persistence', () => {
it('should save to disk and reload', async () => {
const entry: MemoryEntry = {
id: 'persist-test',
agentId: 'agent-1',
sessionId: 'session-1',
type: 'observation',
content: 'Persistent data',
context: {},
timestamp: new Date(),
tags: [],
version: 1
};
await backend.store(entry);
await backend.flush(); // Force save
await backend.shutdown();
// Create new instance with same path
const backend2 = new SqlJsBackend({ dbPath }, logger);
await backend2.initialize();
const retrieved = await backend2.retrieve('persist-test');
expect(retrieved?.content).toBe('Persistent data');
await backend2.shutdown();
});
});
describe('health status', () => {
it('should return healthy status', async () => {
const health = await backend.getHealthStatus();
expect(health.healthy).toBe(true);
expect(health.metrics).toBeDefined();
expect(health.metrics?.entryCount).toBeGreaterThanOrEqual(0);
});
});
});
```
---
## Testing Checklist
### Unit Tests
- [ ] SqlJsBackend store/retrieve
- [ ] SqlJsBackend query operations
- [ ] File persistence
- [ ] WASM loading
- [ ] Error handling
### Integration Tests
- [ ] Provider selection logic
- [ ] Fallback chain (better-sqlite3 → sql.js → JSON)
- [ ] Database migration between providers
- [ ] Concurrent access
### Platform Tests
- [ ] Windows 10/11 (npm install, runtime)
- [ ] macOS (Intel and ARM64)
- [ ] Linux (Ubuntu, Alpine)
- [ ] WSL (Windows Subsystem for Linux)
### Performance Tests
- [ ] Benchmark sql.js vs better-sqlite3
- [ ] Memory usage comparison
- [ ] Large dataset handling (100K+ records)
- [ ] Bulk insert performance
---
## Deployment Checklist
### Pre-release
- [ ] Update CHANGELOG.md with sql.js integration
- [ ] Update README.md with Windows installation instructions
- [ ] Create migration guide for existing users
- [ ] Add troubleshooting section
### Release
- [ ] Publish to npm with `@alpha` tag for testing
- [ ] Test in real Windows environment
- [ ] Monitor issue tracker for reports
- [ ] Collect performance feedback
### Post-release
- [ ] Update documentation based on feedback
- [ ] Optimize performance bottlenecks
- [ ] Consider sql.js as default on all platforms (if successful)
---
## Troubleshooting Guide
### WASM File Not Found
**Error**: `Cannot find sql-wasm.wasm`
**Solution**:
```javascript
// Update locateFile in SqlJsBackend
locateFile: (file) => {
if (process.env.SQLJS_WASM_PATH) {
return path.join(process.env.SQLJS_WASM_PATH, file);
}
return path.join(__dirname, '../../../node_modules/sql.js/dist', file);
}
```
### Performance Issues
**Symptom**: Slow queries (>100ms)
**Solutions**:
1. Enable prepared statements caching
2. Use batch transactions
3. Reduce auto-save frequency
4. Consider switching to better-sqlite3 if available
### Memory Leaks
**Symptom**: Increasing memory usage
**Solutions**:
1. Call `stmt.free()` after prepared statements
2. Limit result set sizes
3. Implement periodic `db.export()` and recreate
4. Monitor with `process.memoryUsage()`
---
## Next Steps
1. Implement SqlJsBackend (Step 2)
2. Update sqlite-wrapper.js (Step 3)
3. Run unit tests
4. Test on Windows
5. Collect feedback
6. Iterate and optimize
---
**Document Version**: 1.0
**Last Updated**: 2026-01-03
**Status**: Ready for implementation