factory.go raw

   1  //go:build !(js && wasm)
   2  
   3  package database
   4  
   5  import (
   6  	"context"
   7  	"fmt"
   8  	"strings"
   9  	"time"
  10  )
  11  
  12  // DatabaseConfig holds all database configuration options that can be passed
  13  // to any database backend. Each backend uses the relevant fields for its type.
  14  // This centralizes configuration instead of having each backend read env vars directly.
  15  type DatabaseConfig struct {
  16  	// Common settings for all backends
  17  	DataDir  string
  18  	LogLevel string
  19  
  20  	// Badger-specific settings
  21  	BlockCacheMB       int           // ORLY_DB_BLOCK_CACHE_MB
  22  	IndexCacheMB       int           // ORLY_DB_INDEX_CACHE_MB
  23  	QueryCacheDisabled bool          // ORLY_QUERY_CACHE_DISABLED - disable query cache to reduce memory usage
  24  	QueryCacheSizeMB   int           // ORLY_QUERY_CACHE_SIZE_MB
  25  	QueryCacheMaxAge   time.Duration // ORLY_QUERY_CACHE_MAX_AGE
  26  
  27  	// Serial cache settings for compact event storage
  28  	SerialCachePubkeys  int // ORLY_SERIAL_CACHE_PUBKEYS - max pubkeys to cache (default: 100000)
  29  	SerialCacheEventIds int // ORLY_SERIAL_CACHE_EVENT_IDS - max event IDs to cache (default: 500000)
  30  
  31  	// Compression settings
  32  	ZSTDLevel int // ORLY_DB_ZSTD_LEVEL - ZSTD compression level (0=none, 1=fast, 3=default, 9=best)
  33  
  34  	// Neo4j-specific settings
  35  	Neo4jURI      string // ORLY_NEO4J_URI
  36  	Neo4jUser     string // ORLY_NEO4J_USER
  37  	Neo4jPassword string // ORLY_NEO4J_PASSWORD
  38  
  39  	// Neo4j driver tuning (memory and connection management)
  40  	Neo4jMaxConnPoolSize   int // ORLY_NEO4J_MAX_CONN_POOL - max connection pool size (default: 25)
  41  	Neo4jFetchSize         int // ORLY_NEO4J_FETCH_SIZE - max records per fetch batch (default: 1000)
  42  	Neo4jMaxTxRetrySeconds int // ORLY_NEO4J_MAX_TX_RETRY_SEC - max transaction retry time (default: 30)
  43  	Neo4jQueryResultLimit  int // ORLY_NEO4J_QUERY_RESULT_LIMIT - max results per query (default: 10000, 0=unlimited)
  44  
  45  	// gRPC client settings (for remote database)
  46  	GRPCServerAddress  string        // ORLY_GRPC_SERVER - address of remote gRPC database server
  47  	GRPCConnectTimeout time.Duration // ORLY_GRPC_CONNECT_TIMEOUT - connection timeout (default: 10s)
  48  }
  49  
  50  // NewDatabase creates a database instance based on the specified type.
  51  // Supported types: "badger", "neo4j"
  52  func NewDatabase(
  53  	ctx context.Context,
  54  	cancel context.CancelFunc,
  55  	dbType string,
  56  	dataDir string,
  57  	logLevel string,
  58  ) (Database, error) {
  59  	// Create a default config for backward compatibility with existing callers
  60  	cfg := &DatabaseConfig{
  61  		DataDir:  dataDir,
  62  		LogLevel: logLevel,
  63  	}
  64  	return NewDatabaseWithConfig(ctx, cancel, dbType, cfg)
  65  }
  66  
  67  // NewDatabaseWithConfig creates a database instance with full configuration.
  68  // This is the preferred method when you have access to the app config.
  69  func NewDatabaseWithConfig(
  70  	ctx context.Context,
  71  	cancel context.CancelFunc,
  72  	dbType string,
  73  	cfg *DatabaseConfig,
  74  ) (Database, error) {
  75  	switch strings.ToLower(dbType) {
  76  	case "badger", "":
  77  		// Use the existing badger implementation
  78  		return NewWithConfig(ctx, cancel, cfg)
  79  	case "neo4j":
  80  		// Use the neo4j implementation
  81  		if newNeo4jDatabase == nil {
  82  			return nil, fmt.Errorf("neo4j database backend not available (import _ \"next.orly.dev/pkg/neo4j\")")
  83  		}
  84  		return newNeo4jDatabase(ctx, cancel, cfg)
  85  	case "wasmdb", "indexeddb", "wasm":
  86  		// Use the wasmdb implementation (IndexedDB backend for WebAssembly)
  87  		if newWasmDBDatabase == nil {
  88  			return nil, fmt.Errorf("wasmdb database backend not available (import _ \"next.orly.dev/pkg/wasmdb\")")
  89  		}
  90  		return newWasmDBDatabase(ctx, cancel, cfg)
  91  	case "grpc":
  92  		// Use the gRPC client to connect to a remote database server
  93  		if newGRPCDatabase == nil {
  94  			return nil, fmt.Errorf("grpc database backend not available (import _ \"next.orly.dev/pkg/database/grpc\")")
  95  		}
  96  		return newGRPCDatabase(ctx, cancel, cfg)
  97  	default:
  98  		return nil, fmt.Errorf("unsupported database type: %s (supported: badger, neo4j, wasmdb, grpc)", dbType)
  99  	}
 100  }
 101  
 102  // newNeo4jDatabase creates a neo4j database instance
 103  // This is defined here to avoid import cycles
 104  var newNeo4jDatabase func(context.Context, context.CancelFunc, *DatabaseConfig) (Database, error)
 105  
 106  // RegisterNeo4jFactory registers the neo4j database factory
 107  // This is called from the neo4j package's init() function
 108  func RegisterNeo4jFactory(factory func(context.Context, context.CancelFunc, *DatabaseConfig) (Database, error)) {
 109  	newNeo4jDatabase = factory
 110  }
 111  
 112  // newWasmDBDatabase creates a wasmdb database instance (IndexedDB backend for WebAssembly)
 113  // This is defined here to avoid import cycles
 114  var newWasmDBDatabase func(context.Context, context.CancelFunc, *DatabaseConfig) (Database, error)
 115  
 116  // RegisterWasmDBFactory registers the wasmdb database factory
 117  // This is called from the wasmdb package's init() function
 118  func RegisterWasmDBFactory(factory func(context.Context, context.CancelFunc, *DatabaseConfig) (Database, error)) {
 119  	newWasmDBDatabase = factory
 120  }
 121  
 122  // newGRPCDatabase creates a gRPC client database instance
 123  // This is defined here to avoid import cycles
 124  var newGRPCDatabase func(context.Context, context.CancelFunc, *DatabaseConfig) (Database, error)
 125  
 126  // RegisterGRPCFactory registers the gRPC database factory
 127  // This is called from the grpc package's init() function
 128  func RegisterGRPCFactory(factory func(context.Context, context.CancelFunc, *DatabaseConfig) (Database, error)) {
 129  	newGRPCDatabase = factory
 130  }
 131