feat: operaciones CRUD y busqueda FTS del registry

Implementa el acceso a datos completo:
- InsertFunction/InsertType con generacion automatica de ID y timestamps
- GetFunction/GetType por ID
- SearchFunctions con FTS + filtros por kind, purity, lang, domain
- SearchTypes con FTS + filtros por lang, domain
- DeleteFunction/DeleteType por ID
- Purge para limpiar antes de re-indexar
- Serialization JSON para campos array (tags, uses_functions, props, etc)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-28 02:04:19 +01:00
parent 11d1dea6e0
commit e7b4e3eb81
+307
View File
@@ -0,0 +1,307 @@
package registry
import (
"encoding/json"
"fmt"
"strings"
"time"
)
func marshalStrings(ss []string) string {
if ss == nil {
ss = []string{}
}
b, _ := json.Marshal(ss)
return string(b)
}
func unmarshalStrings(s string) []string {
var out []string
json.Unmarshal([]byte(s), &out)
if out == nil {
out = []string{}
}
return out
}
func marshalProps(ps []PropDef) string {
if ps == nil {
ps = []PropDef{}
}
b, _ := json.Marshal(ps)
return string(b)
}
func unmarshalProps(s string) []PropDef {
var out []PropDef
json.Unmarshal([]byte(s), &out)
return out
}
// InsertFunction inserts or replaces a function entry.
func (db *DB) InsertFunction(f *Function) error {
now := time.Now().UTC().Format(time.RFC3339)
if f.CreatedAt.IsZero() {
f.CreatedAt = time.Now().UTC()
}
f.UpdatedAt = time.Now().UTC()
if f.ID == "" {
f.ID = GenerateID(f.Name, f.Lang, f.Domain)
}
var hasState *int
if f.HasState != nil {
v := 0
if *f.HasState {
v = 1
}
hasState = &v
}
_, err := db.conn.Exec(`
INSERT OR REPLACE INTO functions (
id, name, kind, lang, domain, version, purity, signature,
description, tags, uses_functions, uses_types, returns,
returns_optional, error_type, imports, example, tested,
tests, test_file_path, file_path, created_at, updated_at,
props, emits, has_state, framework, variant
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?, ?,
?, ?, ?, ?, ?,
?, ?, ?, ?, ?,
?, ?, ?, ?, ?
)`,
f.ID, f.Name, string(f.Kind), f.Lang, f.Domain, f.Version, string(f.Purity), f.Signature,
f.Description, marshalStrings(f.Tags), marshalStrings(f.UsesFunctions), marshalStrings(f.UsesTypes), marshalStrings(f.Returns),
f.ReturnsOptional, f.ErrorType, marshalStrings(f.Imports), f.Example, f.Tested,
marshalStrings(f.Tests), f.TestFilePath, f.FilePath, f.CreatedAt.Format(time.RFC3339), now,
marshalProps(f.Props), marshalStrings(f.Emits), hasState, f.Framework, marshalStrings(f.Variant),
)
return err
}
// InsertType inserts or replaces a type entry.
func (db *DB) InsertType(t *Type) error {
now := time.Now().UTC().Format(time.RFC3339)
if t.CreatedAt.IsZero() {
t.CreatedAt = time.Now().UTC()
}
t.UpdatedAt = time.Now().UTC()
if t.ID == "" {
t.ID = GenerateID(t.Name, t.Lang, t.Domain)
}
_, err := db.conn.Exec(`
INSERT OR REPLACE INTO types (
id, name, lang, domain, version, algebraic,
definition, description, tags, uses_types,
file_path, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
t.ID, t.Name, t.Lang, t.Domain, t.Version, string(t.Algebraic),
t.Definition, t.Description, marshalStrings(t.Tags), marshalStrings(t.UsesTypes),
t.FilePath, t.CreatedAt.Format(time.RFC3339), now,
)
return err
}
// SearchFunctions performs FTS search on functions with optional filters.
func (db *DB) SearchFunctions(query string, kind Kind, purity Purity, lang, domain string) ([]Function, error) {
where := []string{}
args := []any{}
if query != "" {
where = append(where, "f.id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH ?)")
args = append(args, query)
}
if kind != "" {
where = append(where, "f.kind = ?")
args = append(args, string(kind))
}
if purity != "" {
where = append(where, "f.purity = ?")
args = append(args, string(purity))
}
if lang != "" {
where = append(where, "f.lang = ?")
args = append(args, lang)
}
if domain != "" {
where = append(where, "f.domain = ?")
args = append(args, domain)
}
sql := "SELECT * FROM functions f"
if len(where) > 0 {
sql += " WHERE " + strings.Join(where, " AND ")
}
sql += " ORDER BY f.name"
rows, err := db.conn.Query(sql, args...)
if err != nil {
return nil, fmt.Errorf("search functions: %w", err)
}
defer rows.Close()
return scanFunctions(rows)
}
// SearchTypes performs FTS search on types with optional filters.
func (db *DB) SearchTypes(query string, lang, domain string) ([]Type, error) {
where := []string{}
args := []any{}
if query != "" {
where = append(where, "t.id IN (SELECT id FROM types_fts WHERE types_fts MATCH ?)")
args = append(args, query)
}
if lang != "" {
where = append(where, "t.lang = ?")
args = append(args, lang)
}
if domain != "" {
where = append(where, "t.domain = ?")
args = append(args, domain)
}
sql := "SELECT * FROM types t"
if len(where) > 0 {
sql += " WHERE " + strings.Join(where, " AND ")
}
sql += " ORDER BY t.name"
rows, err := db.conn.Query(sql, args...)
if err != nil {
return nil, fmt.Errorf("search types: %w", err)
}
defer rows.Close()
return scanTypes(rows)
}
// GetFunction returns a single function by ID.
func (db *DB) GetFunction(id string) (*Function, error) {
rows, err := db.conn.Query("SELECT * FROM functions WHERE id = ?", id)
if err != nil {
return nil, err
}
defer rows.Close()
fns, err := scanFunctions(rows)
if err != nil {
return nil, err
}
if len(fns) == 0 {
return nil, fmt.Errorf("function %q not found", id)
}
return &fns[0], nil
}
// GetType returns a single type by ID.
func (db *DB) GetType(id string) (*Type, error) {
rows, err := db.conn.Query("SELECT * FROM types WHERE id = ?", id)
if err != nil {
return nil, err
}
defer rows.Close()
ts, err := scanTypes(rows)
if err != nil {
return nil, err
}
if len(ts) == 0 {
return nil, fmt.Errorf("type %q not found", id)
}
return &ts[0], nil
}
// DeleteFunction removes a function by ID.
func (db *DB) DeleteFunction(id string) error {
_, err := db.conn.Exec("DELETE FROM functions WHERE id = ?", id)
return err
}
// DeleteType removes a type by ID.
func (db *DB) DeleteType(id string) error {
_, err := db.conn.Exec("DELETE FROM types WHERE id = ?", id)
return err
}
// Purge deletes all data from both tables. Used before re-indexing.
func (db *DB) Purge() error {
if _, err := db.conn.Exec("DELETE FROM functions"); err != nil {
return err
}
_, err := db.conn.Exec("DELETE FROM types")
return err
}
func scanFunctions(rows interface{ Next() bool; Scan(...any) error }) ([]Function, error) {
var result []Function
for rows.Next() {
var f Function
var tagsJSON, usesFnJSON, usesTypJSON, returnsJSON, importsJSON, testsJSON string
var propsJSON, emitsJSON, variantJSON string
var hasState *int
var createdAt, updatedAt string
err := rows.Scan(
&f.ID, &f.Name, &f.Kind, &f.Lang, &f.Domain, &f.Version, &f.Purity, &f.Signature,
&f.Description, &tagsJSON, &usesFnJSON, &usesTypJSON, &returnsJSON,
&f.ReturnsOptional, &f.ErrorType, &importsJSON, &f.Example, &f.Tested,
&testsJSON, &f.TestFilePath, &f.FilePath, &createdAt, &updatedAt,
&propsJSON, &emitsJSON, &hasState, &f.Framework, &variantJSON,
)
if err != nil {
return nil, fmt.Errorf("scanning function: %w", err)
}
f.Tags = unmarshalStrings(tagsJSON)
f.UsesFunctions = unmarshalStrings(usesFnJSON)
f.UsesTypes = unmarshalStrings(usesTypJSON)
f.Returns = unmarshalStrings(returnsJSON)
f.Imports = unmarshalStrings(importsJSON)
f.Tests = unmarshalStrings(testsJSON)
f.Props = unmarshalProps(propsJSON)
f.Emits = unmarshalStrings(emitsJSON)
f.Variant = unmarshalStrings(variantJSON)
f.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
f.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)
if hasState != nil {
v := *hasState == 1
f.HasState = &v
}
result = append(result, f)
}
return result, nil
}
func scanTypes(rows interface{ Next() bool; Scan(...any) error }) ([]Type, error) {
var result []Type
for rows.Next() {
var t Type
var tagsJSON, usesTypJSON string
var createdAt, updatedAt string
err := rows.Scan(
&t.ID, &t.Name, &t.Lang, &t.Domain, &t.Version, &t.Algebraic,
&t.Definition, &t.Description, &tagsJSON, &usesTypJSON,
&t.FilePath, &createdAt, &updatedAt,
)
if err != nil {
return nil, fmt.Errorf("scanning type: %w", err)
}
t.Tags = unmarshalStrings(tagsJSON)
t.UsesTypes = unmarshalStrings(usesTypJSON)
t.CreatedAt, _ = time.Parse(time.RFC3339, createdAt)
t.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt)
result = append(result, t)
}
return result, nil
}