Files
fn_registry/registry/db.go
T
egutierrez 5ac4c52e67 feat: WAL mode explícito con PRAGMA para acceso concurrente
Cambia de _journal_mode=WAL en connection string a PRAGMA journal_mode=WAL
explícito. Esto persiste en el archivo y cualquier cliente externo (sqlite3
CLI, DuckDB) hereda WAL automáticamente sin configuración adicional.
Permite leer mientras el agente escribe simultáneamente sin bloqueos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 02:22:58 +01:00

161 lines
5.1 KiB
Go

package registry
import (
"database/sql"
"fmt"
"os"
"path/filepath"
_ "github.com/mattn/go-sqlite3"
)
const schemaSQL = `
CREATE TABLE IF NOT EXISTS functions (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
kind TEXT NOT NULL CHECK(kind IN ('function','pipeline','component')),
lang TEXT NOT NULL,
domain TEXT NOT NULL,
version TEXT NOT NULL DEFAULT '1.0.0',
purity TEXT NOT NULL CHECK(purity IN ('pure','impure')),
signature TEXT NOT NULL DEFAULT '',
description TEXT NOT NULL,
tags TEXT NOT NULL DEFAULT '[]',
uses_functions TEXT NOT NULL DEFAULT '[]',
uses_types TEXT NOT NULL DEFAULT '[]',
returns TEXT NOT NULL DEFAULT '[]',
returns_optional INTEGER NOT NULL DEFAULT 0,
error_type TEXT NOT NULL DEFAULT '',
imports TEXT NOT NULL DEFAULT '[]',
example TEXT NOT NULL DEFAULT '',
tested INTEGER NOT NULL DEFAULT 0,
tests TEXT NOT NULL DEFAULT '[]',
test_file_path TEXT NOT NULL DEFAULT '',
file_path TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
-- Component fields
props TEXT NOT NULL DEFAULT '[]',
emits TEXT NOT NULL DEFAULT '[]',
has_state INTEGER,
framework TEXT NOT NULL DEFAULT '',
variant TEXT NOT NULL DEFAULT '[]'
);
CREATE TABLE IF NOT EXISTS types (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
lang TEXT NOT NULL,
domain TEXT NOT NULL,
version TEXT NOT NULL DEFAULT '1.0.0',
algebraic TEXT NOT NULL CHECK(algebraic IN ('product','sum')),
definition TEXT NOT NULL DEFAULT '',
description TEXT NOT NULL,
tags TEXT NOT NULL DEFAULT '[]',
uses_types TEXT NOT NULL DEFAULT '[]',
file_path TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE VIRTUAL TABLE IF NOT EXISTS functions_fts USING fts5(
id,
name,
description,
tags,
signature,
domain,
content='functions',
content_rowid='rowid'
);
CREATE VIRTUAL TABLE IF NOT EXISTS types_fts USING fts5(
id,
name,
description,
tags,
domain,
content='types',
content_rowid='rowid'
);
-- Triggers to keep FTS in sync
CREATE TRIGGER IF NOT EXISTS functions_ai AFTER INSERT ON functions BEGIN
INSERT INTO functions_fts(rowid, id, name, description, tags, signature, domain)
VALUES (new.rowid, new.id, new.name, new.description, new.tags, new.signature, new.domain);
END;
CREATE TRIGGER IF NOT EXISTS functions_ad AFTER DELETE ON functions BEGIN
INSERT INTO functions_fts(functions_fts, rowid, id, name, description, tags, signature, domain)
VALUES ('delete', old.rowid, old.id, old.name, old.description, old.tags, old.signature, old.domain);
END;
CREATE TRIGGER IF NOT EXISTS functions_au AFTER UPDATE ON functions BEGIN
INSERT INTO functions_fts(functions_fts, rowid, id, name, description, tags, signature, domain)
VALUES ('delete', old.rowid, old.id, old.name, old.description, old.tags, old.signature, old.domain);
INSERT INTO functions_fts(rowid, id, name, description, tags, signature, domain)
VALUES (new.rowid, new.id, new.name, new.description, new.tags, new.signature, new.domain);
END;
CREATE TRIGGER IF NOT EXISTS types_ai AFTER INSERT ON types BEGIN
INSERT INTO types_fts(rowid, id, name, description, tags, domain)
VALUES (new.rowid, new.id, new.name, new.description, new.tags, new.domain);
END;
CREATE TRIGGER IF NOT EXISTS types_ad AFTER DELETE ON types BEGIN
INSERT INTO types_fts(types_fts, rowid, id, name, description, tags, domain)
VALUES ('delete', old.rowid, old.id, old.name, old.description, old.tags, old.domain);
END;
CREATE TRIGGER IF NOT EXISTS types_au AFTER UPDATE ON types BEGIN
INSERT INTO types_fts(types_fts, rowid, id, name, description, tags, domain)
VALUES ('delete', old.rowid, old.id, old.name, old.description, old.tags, old.domain);
INSERT INTO types_fts(rowid, id, name, description, tags, domain)
VALUES (new.rowid, new.id, new.name, new.description, new.tags, new.domain);
END;
`
// DB wraps a SQLite connection for the registry.
type DB struct {
conn *sql.DB
path string
}
// Open opens or creates the registry database at the given path.
func Open(path string) (*DB, error) {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0o755); err != nil {
return nil, fmt.Errorf("creating db directory: %w", err)
}
conn, err := sql.Open("sqlite3", path+"?_foreign_keys=on")
if err != nil {
return nil, fmt.Errorf("opening database: %w", err)
}
// WAL mode: enables concurrent reads while writing.
// Persists in the file — any client opening the DB inherits it.
if _, err := conn.Exec("PRAGMA journal_mode=WAL"); err != nil {
conn.Close()
return nil, fmt.Errorf("setting WAL mode: %w", err)
}
if _, err := conn.Exec(schemaSQL); err != nil {
conn.Close()
return nil, fmt.Errorf("applying schema: %w", err)
}
return &DB{conn: conn, path: path}, nil
}
// Close closes the database connection.
func (db *DB) Close() error {
return db.conn.Close()
}
// Drop removes the database file. Used by `fn index` to regenerate.
func (db *DB) Drop() error {
db.Close()
return os.Remove(db.path)
}