2026-01-20 10:03:55 +02:00

176 lines
5.4 KiB
Go

// Package registry manages function metadata in RQLite and bytecode in IPFS.
package registry
import (
"context"
"fmt"
"strings"
"time"
"github.com/DeBrosOfficial/network/pkg/ipfs"
"github.com/DeBrosOfficial/network/pkg/rqlite"
"go.uber.org/zap"
)
// Ensure Registry implements FunctionRegistry interface.
var _ FunctionRegistry = (*Registry)(nil)
// Registry coordinates between function storage, IPFS storage, and logging.
type Registry struct {
functionStore *FunctionStore
ipfsStore *IPFSStore
invocationLogger *InvocationLogger
logger *zap.Logger
}
// NewRegistry creates a new function registry.
func NewRegistry(db rqlite.Client, ipfsClient ipfs.IPFSClient, cfg RegistryConfig, logger *zap.Logger) *Registry {
return &Registry{
functionStore: NewFunctionStore(db, logger),
ipfsStore: NewIPFSStore(ipfsClient, cfg.IPFSAPIURL, logger),
invocationLogger: NewInvocationLogger(db, logger),
logger: logger,
}
}
// Register deploys a new function or updates an existing one.
func (r *Registry) Register(ctx context.Context, fn *FunctionDefinition, wasmBytes []byte) (*Function, error) {
if fn == nil {
return nil, &ValidationError{Field: "definition", Message: "cannot be nil"}
}
fn.Name = strings.TrimSpace(fn.Name)
fn.Namespace = strings.TrimSpace(fn.Namespace)
if fn.Name == "" {
return nil, &ValidationError{Field: "name", Message: "cannot be empty"}
}
if fn.Namespace == "" {
return nil, &ValidationError{Field: "namespace", Message: "cannot be empty"}
}
if len(wasmBytes) == 0 {
return nil, &ValidationError{Field: "wasmBytes", Message: "cannot be empty"}
}
oldFn, err := r.functionStore.GetByNameInternal(ctx, fn.Namespace, fn.Name)
if err != nil && err != ErrFunctionNotFound {
return nil, &DeployError{FunctionName: fn.Name, Cause: err}
}
wasmCID, err := r.ipfsStore.Upload(ctx, wasmBytes, fn.Name)
if err != nil {
return nil, &DeployError{FunctionName: fn.Name, Cause: err}
}
savedFunc, err := r.functionStore.Save(ctx, fn, wasmCID, oldFn)
if err != nil {
return nil, &DeployError{FunctionName: fn.Name, Cause: err}
}
if err := r.functionStore.SaveEnvVars(ctx, savedFunc.ID, fn.EnvVars); err != nil {
return nil, &DeployError{FunctionName: fn.Name, Cause: err}
}
r.logger.Info("Function registered",
zap.String("id", savedFunc.ID),
zap.String("name", fn.Name),
zap.String("namespace", fn.Namespace),
zap.String("wasm_cid", wasmCID),
zap.Int("version", savedFunc.Version),
zap.Bool("updated", oldFn != nil),
)
return oldFn, nil
}
// Get retrieves a function by name and optional version.
func (r *Registry) Get(ctx context.Context, namespace, name string, version int) (*Function, error) {
return r.functionStore.Get(ctx, namespace, name, version)
}
// List returns all functions for a namespace.
func (r *Registry) List(ctx context.Context, namespace string) ([]*Function, error) {
return r.functionStore.List(ctx, namespace)
}
// Delete removes a function. If version is 0, removes all versions.
func (r *Registry) Delete(ctx context.Context, namespace, name string, version int) error {
return r.functionStore.Delete(ctx, namespace, name, version)
}
// GetWASMBytes retrieves the compiled WASM bytecode for a function.
func (r *Registry) GetWASMBytes(ctx context.Context, wasmCID string) ([]byte, error) {
if wasmCID == "" {
return nil, &ValidationError{Field: "wasmCID", Message: "cannot be empty"}
}
return r.ipfsStore.Get(ctx, wasmCID)
}
// GetLogs retrieves logs for a function.
func (r *Registry) GetLogs(ctx context.Context, namespace, name string, limit int) ([]LogEntry, error) {
return r.invocationLogger.GetLogs(ctx, namespace, name, limit)
}
// GetEnvVars retrieves environment variables for a function.
func (r *Registry) GetEnvVars(ctx context.Context, functionID string) (map[string]string, error) {
return r.functionStore.GetEnvVars(ctx, functionID)
}
// GetByID retrieves a function by its ID.
func (r *Registry) GetByID(ctx context.Context, id string) (*Function, error) {
return r.functionStore.GetByID(ctx, id)
}
// ListVersions returns all versions of a function.
func (r *Registry) ListVersions(ctx context.Context, namespace, name string) ([]*Function, error) {
return r.functionStore.ListVersions(ctx, namespace, name)
}
// LogInvocation records a function invocation and its logs to the database.
func (r *Registry) LogInvocation(ctx context.Context,
id, functionID, requestID string,
triggerType interface{},
callerWallet string,
inputSize, outputSize int,
startedAt, completedAt interface{},
durationMS int64,
status interface{},
errorMessage string,
memoryUsedMB float64,
logs []LogEntry) error {
var startTime, completeTime time.Time
if t, ok := startedAt.(time.Time); ok {
startTime = t
}
if t, ok := completedAt.(time.Time); ok {
completeTime = t
}
data := &InvocationRecordData{
ID: id,
FunctionID: functionID,
RequestID: requestID,
TriggerType: fmt.Sprintf("%v", triggerType),
CallerWallet: callerWallet,
InputSize: inputSize,
OutputSize: outputSize,
StartedAt: startTime,
CompletedAt: completeTime,
DurationMS: durationMS,
Status: fmt.Sprintf("%v", status),
ErrorMessage: errorMessage,
MemoryUsedMB: memoryUsedMB,
}
data.Logs = make([]LogData, len(logs))
for i, log := range logs {
data.Logs[i] = LogData{
Level: log.Level,
Message: log.Message,
Timestamp: log.Timestamp,
}
}
return r.invocationLogger.Log(ctx, data)
}