ubersearch 0.0.0-development
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +374 -0
- package/package.json +76 -0
- package/src/app/index.ts +30 -0
- package/src/bootstrap/container.ts +157 -0
- package/src/cli.ts +380 -0
- package/src/config/defineConfig.ts +176 -0
- package/src/config/load.ts +368 -0
- package/src/config/types.ts +86 -0
- package/src/config/validation.ts +148 -0
- package/src/core/cache.ts +74 -0
- package/src/core/container.ts +268 -0
- package/src/core/credits/CreditManager.ts +158 -0
- package/src/core/credits/CreditStateProvider.ts +151 -0
- package/src/core/credits/FileCreditStateProvider.ts +137 -0
- package/src/core/credits/index.ts +3 -0
- package/src/core/docker/dockerComposeHelper.ts +177 -0
- package/src/core/docker/dockerLifecycleManager.ts +361 -0
- package/src/core/docker/index.ts +8 -0
- package/src/core/logger.ts +146 -0
- package/src/core/orchestrator.ts +103 -0
- package/src/core/paths.ts +157 -0
- package/src/core/provider/ILifecycleProvider.ts +120 -0
- package/src/core/provider/ProviderFactory.ts +120 -0
- package/src/core/provider.ts +61 -0
- package/src/core/serviceKeys.ts +45 -0
- package/src/core/strategy/AllProvidersStrategy.ts +245 -0
- package/src/core/strategy/FirstSuccessStrategy.ts +98 -0
- package/src/core/strategy/ISearchStrategy.ts +94 -0
- package/src/core/strategy/StrategyFactory.ts +204 -0
- package/src/core/strategy/index.ts +9 -0
- package/src/core/strategy/types.ts +56 -0
- package/src/core/types.ts +58 -0
- package/src/index.ts +1 -0
- package/src/plugin/PluginRegistry.ts +336 -0
- package/src/plugin/builtin.ts +130 -0
- package/src/plugin/index.ts +33 -0
- package/src/plugin/types.ts +212 -0
- package/src/providers/BaseProvider.ts +49 -0
- package/src/providers/brave.ts +66 -0
- package/src/providers/constants.ts +13 -0
- package/src/providers/helpers/index.ts +24 -0
- package/src/providers/helpers/lifecycleHelpers.ts +110 -0
- package/src/providers/helpers/resultMappers.ts +168 -0
- package/src/providers/index.ts +6 -0
- package/src/providers/linkup.ts +114 -0
- package/src/providers/retry.ts +95 -0
- package/src/providers/searchxng.ts +163 -0
- package/src/providers/tavily.ts +73 -0
- package/src/providers/types/index.ts +185 -0
- package/src/providers/utils.ts +182 -0
- package/src/tool/allSearchTool.ts +110 -0
- package/src/tool/interface.ts +71 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Search Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Coordinates multiple search providers and implements different search strategies
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { AllSearchConfig } from "../config/types";
|
|
8
|
+
import type { CreditManager, CreditSnapshot } from "./credits";
|
|
9
|
+
import type { ProviderRegistry } from "./provider";
|
|
10
|
+
import type { StrategyContext } from "./strategy/ISearchStrategy";
|
|
11
|
+
import { StrategyFactory } from "./strategy/StrategyFactory";
|
|
12
|
+
import type { EngineId, SearchResultItem } from "./types";
|
|
13
|
+
|
|
14
|
+
export interface AllSearchOptions {
|
|
15
|
+
/** Override the default engine order */
|
|
16
|
+
engineOrderOverride?: EngineId[];
|
|
17
|
+
|
|
18
|
+
/** Maximum results per provider */
|
|
19
|
+
limit?: number;
|
|
20
|
+
|
|
21
|
+
/** Include raw provider responses */
|
|
22
|
+
includeRaw?: boolean;
|
|
23
|
+
|
|
24
|
+
/** Search strategy */
|
|
25
|
+
strategy?: "all" | "first-success";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface EngineAttempt {
|
|
29
|
+
engineId: EngineId;
|
|
30
|
+
success: boolean;
|
|
31
|
+
reason?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface OrchestratorResult {
|
|
35
|
+
/** Original query */
|
|
36
|
+
query: string;
|
|
37
|
+
|
|
38
|
+
/** Combined results from all successful providers */
|
|
39
|
+
results: SearchResultItem[];
|
|
40
|
+
|
|
41
|
+
/** Metadata about engine attempts */
|
|
42
|
+
engineAttempts: EngineAttempt[];
|
|
43
|
+
|
|
44
|
+
/** Credit snapshots after the search */
|
|
45
|
+
credits: CreditSnapshot[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class AllSearchOrchestrator {
|
|
49
|
+
constructor(
|
|
50
|
+
private config: AllSearchConfig,
|
|
51
|
+
private credits: CreditManager,
|
|
52
|
+
private registry: ProviderRegistry,
|
|
53
|
+
) {}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Determine the engine order to use for this search
|
|
57
|
+
*/
|
|
58
|
+
private getEngineOrder(override?: EngineId[]): EngineId[] {
|
|
59
|
+
if (override?.length) {
|
|
60
|
+
return override;
|
|
61
|
+
}
|
|
62
|
+
return this.config.defaultEngineOrder;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Run a allsearch with the specified options
|
|
67
|
+
*/
|
|
68
|
+
async run(query: string, options: AllSearchOptions = {}): Promise<OrchestratorResult> {
|
|
69
|
+
const order = this.getEngineOrder(options.engineOrderOverride);
|
|
70
|
+
const strategyName = options.strategy ?? "all";
|
|
71
|
+
|
|
72
|
+
if (order.length === 0) {
|
|
73
|
+
throw new Error("No engines configured or selected");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Create strategy using factory
|
|
77
|
+
const strategy = StrategyFactory.createStrategy(strategyName);
|
|
78
|
+
|
|
79
|
+
// Create strategy context
|
|
80
|
+
const context: StrategyContext = {
|
|
81
|
+
creditManager: this.credits,
|
|
82
|
+
providerRegistry: this.registry,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Execute strategy
|
|
86
|
+
const { results, attempts } = await strategy.execute(query, order, options, context);
|
|
87
|
+
|
|
88
|
+
// For 'all' strategy, sort results by score (descending)
|
|
89
|
+
if (strategyName === "all") {
|
|
90
|
+
results.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Get credit snapshots
|
|
94
|
+
const credits = this.credits.listSnapshots();
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
query,
|
|
98
|
+
results,
|
|
99
|
+
engineAttempts: attempts,
|
|
100
|
+
credits,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XDG Base Directory utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides standard paths following XDG Base Directory Specification
|
|
5
|
+
* https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const APP_NAME = "allsearch";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get XDG config home directory
|
|
17
|
+
* Default: ~/.config
|
|
18
|
+
*/
|
|
19
|
+
export function getXdgConfigHome(): string {
|
|
20
|
+
return process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get XDG data home directory
|
|
25
|
+
* Default: ~/.local/share
|
|
26
|
+
*/
|
|
27
|
+
export function getXdgDataHome(): string {
|
|
28
|
+
return process.env.XDG_DATA_HOME || join(homedir(), ".local", "share");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get XDG state home directory
|
|
33
|
+
* Default: ~/.local/state
|
|
34
|
+
*/
|
|
35
|
+
export function getXdgStateHome(): string {
|
|
36
|
+
return process.env.XDG_STATE_HOME || join(homedir(), ".local", "state");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get app config directory
|
|
41
|
+
* ~/.config/allsearch
|
|
42
|
+
*/
|
|
43
|
+
export function getAppConfigDir(): string {
|
|
44
|
+
return join(getXdgConfigHome(), APP_NAME);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get app data directory
|
|
49
|
+
* ~/.local/share/allsearch
|
|
50
|
+
*/
|
|
51
|
+
export function getAppDataDir(): string {
|
|
52
|
+
return join(getXdgDataHome(), APP_NAME);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get SearXNG config directory
|
|
57
|
+
* ~/.config/allsearch/searxng/config
|
|
58
|
+
*/
|
|
59
|
+
export function getSearxngConfigDir(): string {
|
|
60
|
+
return join(getAppConfigDir(), "searxng", "config");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get SearXNG data directory
|
|
65
|
+
* ~/.local/share/allsearch/searxng/data
|
|
66
|
+
*/
|
|
67
|
+
export function getSearxngDataDir(): string {
|
|
68
|
+
return join(getAppDataDir(), "searxng", "data");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Ensure a directory exists, creating it if necessary
|
|
73
|
+
*/
|
|
74
|
+
export function ensureDir(dir: string): void {
|
|
75
|
+
if (!existsSync(dir)) {
|
|
76
|
+
mkdirSync(dir, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get all SearXNG paths and ensure directories exist
|
|
82
|
+
*/
|
|
83
|
+
export function getSearxngPaths(): { configDir: string; dataDir: string } {
|
|
84
|
+
const configDir = getSearxngConfigDir();
|
|
85
|
+
const dataDir = getSearxngDataDir();
|
|
86
|
+
|
|
87
|
+
ensureDir(configDir);
|
|
88
|
+
ensureDir(dataDir);
|
|
89
|
+
|
|
90
|
+
return { configDir, dataDir };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get package root directory
|
|
95
|
+
* Handles both development (src/) and bundled (dist/) environments
|
|
96
|
+
*/
|
|
97
|
+
export function getPackageRoot(): string {
|
|
98
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
99
|
+
const currentDir = dirname(currentFile);
|
|
100
|
+
|
|
101
|
+
// Check if we're in dist/ or src/
|
|
102
|
+
if (currentDir.includes("/dist")) {
|
|
103
|
+
// Bundled: dist/core/paths.js or dist/cli.js -> find dist root
|
|
104
|
+
const distIndex = currentDir.indexOf("/dist");
|
|
105
|
+
return currentDir.substring(0, distIndex + 5); // Include /dist
|
|
106
|
+
}
|
|
107
|
+
// Development: src/core/paths.ts -> go up 2 levels to package root
|
|
108
|
+
return dirname(dirname(currentDir));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the bundled default settings.yml path
|
|
113
|
+
*/
|
|
114
|
+
export function getDefaultSettingsPath(): string {
|
|
115
|
+
const packageRoot = getPackageRoot();
|
|
116
|
+
// In bundled mode, look in dist/providers/searxng/
|
|
117
|
+
// In dev mode, look in providers/searxng/
|
|
118
|
+
if (packageRoot.endsWith("/dist")) {
|
|
119
|
+
return join(packageRoot, "providers", "searxng", "config", "settings.yml");
|
|
120
|
+
}
|
|
121
|
+
return join(packageRoot, "providers", "searxng", "config", "settings.yml");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Bootstrap SearXNG config by copying default settings if not present
|
|
126
|
+
* @returns true if config was bootstrapped, false if already exists
|
|
127
|
+
*/
|
|
128
|
+
export function bootstrapSearxngConfig(): boolean {
|
|
129
|
+
const { configDir } = getSearxngPaths();
|
|
130
|
+
const targetSettings = join(configDir, "settings.yml");
|
|
131
|
+
|
|
132
|
+
// If settings already exist, don't overwrite
|
|
133
|
+
if (existsSync(targetSettings)) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const defaultSettings = getDefaultSettingsPath();
|
|
138
|
+
|
|
139
|
+
// If default settings don't exist, we can't bootstrap
|
|
140
|
+
if (!existsSync(defaultSettings)) {
|
|
141
|
+
console.warn(
|
|
142
|
+
`[SearXNG] Default settings not found at ${defaultSettings}. ` +
|
|
143
|
+
`Please create ${targetSettings} manually.`,
|
|
144
|
+
);
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Copy default settings to XDG config dir
|
|
149
|
+
try {
|
|
150
|
+
copyFileSync(defaultSettings, targetSettings);
|
|
151
|
+
console.log(`[SearXNG] Bootstrapped default config to ${targetSettings}`);
|
|
152
|
+
return true;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(`[SearXNG] Failed to bootstrap config:`, error);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lifecycle Provider Interface
|
|
3
|
+
*
|
|
4
|
+
* Separates lifecycle concerns (init, healthcheck, shutdown, validation) from search provider contract.
|
|
5
|
+
* Implemented by providers that manage their own infrastructure (Docker containers, local services).
|
|
6
|
+
* NOT implemented by external API providers (Tavily, Brave) that don't require lifecycle management.
|
|
7
|
+
*
|
|
8
|
+
* This interface follows the Interface Segregation Principle (ISP) by providing
|
|
9
|
+
* a focused contract for lifecycle-aware providers, separate from the core SearchProvider interface.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* class DockerSearchProvider implements SearchProvider, ILifecycleProvider {
|
|
14
|
+
* // SearchProvider methods
|
|
15
|
+
* search(query: SearchQuery): Promise<SearchResponse> { ... }
|
|
16
|
+
*
|
|
17
|
+
* // ILifecycleProvider methods
|
|
18
|
+
* async init(): Promise<void> { ... }
|
|
19
|
+
* async healthcheck(): Promise<boolean> { ... }
|
|
20
|
+
* async shutdown(): Promise<void> { ... }
|
|
21
|
+
* async validateConfig(): Promise<ValidationResult> { ... }
|
|
22
|
+
* isLifecycleManaged(): boolean { return true; }
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validation result for provider configuration
|
|
29
|
+
*/
|
|
30
|
+
export interface ValidationResult {
|
|
31
|
+
/** Whether the configuration is valid */
|
|
32
|
+
valid: boolean;
|
|
33
|
+
/** List of validation errors (empty if valid) */
|
|
34
|
+
errors: string[];
|
|
35
|
+
/** List of validation warnings (informational) */
|
|
36
|
+
warnings: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Interface for providers that manage their own lifecycle (Docker containers, local services)
|
|
41
|
+
*
|
|
42
|
+
* Separates lifecycle management from search functionality, allowing:
|
|
43
|
+
* - Clean separation of concerns (Single Responsibility Principle)
|
|
44
|
+
* - LSP compliance - SearchProvider contract remains pure
|
|
45
|
+
* - Flexibility - can use providers with or without lifecycle management
|
|
46
|
+
* - Better testability - lifecycle can be mocked/stubbed separately
|
|
47
|
+
*/
|
|
48
|
+
export interface ILifecycleProvider {
|
|
49
|
+
/**
|
|
50
|
+
* Initialize the provider
|
|
51
|
+
*
|
|
52
|
+
* Performs initialization tasks such as:
|
|
53
|
+
* - Starting Docker containers (if autoStart is enabled)
|
|
54
|
+
* - Establishing connections to external services
|
|
55
|
+
* - Performing health checks
|
|
56
|
+
* - Setting up required infrastructure
|
|
57
|
+
*
|
|
58
|
+
* Should be idempotent - safe to call multiple times.
|
|
59
|
+
* Should handle concurrent calls gracefully.
|
|
60
|
+
*
|
|
61
|
+
* @throws Error if initialization fails critically
|
|
62
|
+
*/
|
|
63
|
+
init(): Promise<void>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if the provider is healthy and ready to serve requests
|
|
67
|
+
*
|
|
68
|
+
* Performs health checks such as:
|
|
69
|
+
* - Container running status (for Docker providers)
|
|
70
|
+
* - Health endpoint responsiveness
|
|
71
|
+
* - Service availability checks
|
|
72
|
+
* - Resource availability validation
|
|
73
|
+
*
|
|
74
|
+
* @returns true if provider is healthy and ready, false otherwise
|
|
75
|
+
*/
|
|
76
|
+
healthcheck(): Promise<boolean>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Shutdown the provider cleanly
|
|
80
|
+
*
|
|
81
|
+
* Performs cleanup tasks such as:
|
|
82
|
+
* - Stopping Docker containers (if autoStop is enabled)
|
|
83
|
+
* - Closing connections
|
|
84
|
+
* - Releasing resources
|
|
85
|
+
* - Cleanup of temporary files/state
|
|
86
|
+
*
|
|
87
|
+
* Should be graceful - avoid throwing errors unless absolutely necessary.
|
|
88
|
+
* Should handle cases where provider is not running.
|
|
89
|
+
*
|
|
90
|
+
* @throws Error only for critical shutdown failures that need attention
|
|
91
|
+
*/
|
|
92
|
+
shutdown(): Promise<void>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Validate provider configuration
|
|
96
|
+
*
|
|
97
|
+
* Checks configuration for issues such as:
|
|
98
|
+
* - Docker availability and permissions
|
|
99
|
+
* - Compose file validity and existence
|
|
100
|
+
* - Container name validation
|
|
101
|
+
* - Health endpoint URL validity
|
|
102
|
+
* - Required environment variables
|
|
103
|
+
* - Port availability and conflicts
|
|
104
|
+
*
|
|
105
|
+
* @returns Validation result with errors and warnings
|
|
106
|
+
*/
|
|
107
|
+
validateConfig(): Promise<ValidationResult>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if this provider requires lifecycle management
|
|
111
|
+
*
|
|
112
|
+
* Returns true for providers that manage infrastructure (Docker containers, local services).
|
|
113
|
+
* Returns false for external API providers that don't require lifecycle management.
|
|
114
|
+
*
|
|
115
|
+
* This method allows consumers to determine whether lifecycle methods should be called.
|
|
116
|
+
*
|
|
117
|
+
* @returns true if provider manages its own lifecycle, false for external APIs
|
|
118
|
+
*/
|
|
119
|
+
isLifecycleManaged(): boolean;
|
|
120
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Factory
|
|
3
|
+
*
|
|
4
|
+
* Factory for creating search provider instances based on configuration.
|
|
5
|
+
* Delegates to PluginRegistry for provider creation, enabling new providers
|
|
6
|
+
* to be added without modifying this code.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { EngineConfig } from "../../config/types";
|
|
10
|
+
import { areBuiltInPluginsRegistered, PluginRegistry, registerBuiltInPlugins } from "../../plugin";
|
|
11
|
+
import type { Container } from "../container";
|
|
12
|
+
import type { SearchProvider } from "../provider";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Factory for creating provider instances based on configuration
|
|
16
|
+
*
|
|
17
|
+
* Uses the PluginRegistry to create providers, allowing new provider types
|
|
18
|
+
* to be added by registering plugins rather than modifying factory code.
|
|
19
|
+
*
|
|
20
|
+
* @class ProviderFactory
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Ensure plugins are registered first
|
|
24
|
+
* await ProviderFactory.ensurePluginsRegistered();
|
|
25
|
+
*
|
|
26
|
+
* // Create a provider
|
|
27
|
+
* const provider = ProviderFactory.createProvider(config, container);
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
let initialized = false;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Ensure built-in plugins are registered
|
|
34
|
+
* Call this before creating providers if not loading via loadConfig()
|
|
35
|
+
*/
|
|
36
|
+
async function ensurePluginsRegistered(): Promise<void> {
|
|
37
|
+
if (!initialized) {
|
|
38
|
+
const registry = PluginRegistry.getInstance();
|
|
39
|
+
if (!areBuiltInPluginsRegistered(registry)) {
|
|
40
|
+
await registerBuiltInPlugins(registry);
|
|
41
|
+
}
|
|
42
|
+
initialized = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Synchronously ensure built-in plugins are registered
|
|
48
|
+
* Uses sync registration (no lifecycle hooks called)
|
|
49
|
+
*/
|
|
50
|
+
function ensurePluginsRegisteredSync(): void {
|
|
51
|
+
if (!initialized) {
|
|
52
|
+
const registry = PluginRegistry.getInstance();
|
|
53
|
+
if (!areBuiltInPluginsRegistered(registry)) {
|
|
54
|
+
registerBuiltInPlugins(registry);
|
|
55
|
+
}
|
|
56
|
+
initialized = true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates a search provider instance based on the provided configuration
|
|
62
|
+
*
|
|
63
|
+
* @param {EngineConfig} config - The engine configuration (must include 'type' field)
|
|
64
|
+
* @param {Container} container - DI container for dependency injection
|
|
65
|
+
* @returns {SearchProvider} An instance of the requested provider
|
|
66
|
+
* @throws {Error} If the provider type is not registered
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```typescript
|
|
70
|
+
* const provider = ProviderFactory.createProvider(config, container);
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
function createProvider(config: EngineConfig, container: Container): SearchProvider {
|
|
74
|
+
// Ensure built-in plugins are registered (sync)
|
|
75
|
+
ensurePluginsRegisteredSync();
|
|
76
|
+
|
|
77
|
+
const registry = PluginRegistry.getInstance();
|
|
78
|
+
|
|
79
|
+
return registry.createProvider(config, { container });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create multiple providers from an array of configs
|
|
84
|
+
*/
|
|
85
|
+
function createProviders(configs: EngineConfig[], container: Container): SearchProvider[] {
|
|
86
|
+
return configs.map((config) => createProvider(config, container));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if a provider type is supported
|
|
91
|
+
*/
|
|
92
|
+
function isTypeSupported(type: string): boolean {
|
|
93
|
+
ensurePluginsRegisteredSync();
|
|
94
|
+
return PluginRegistry.getInstance().has(type);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get all supported provider types
|
|
99
|
+
*/
|
|
100
|
+
function getSupportedTypes(): string[] {
|
|
101
|
+
ensurePluginsRegisteredSync();
|
|
102
|
+
return PluginRegistry.getInstance().getTypes();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Reset initialization state (for testing)
|
|
107
|
+
*/
|
|
108
|
+
function reset(): void {
|
|
109
|
+
initialized = false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const ProviderFactory = {
|
|
113
|
+
ensurePluginsRegistered,
|
|
114
|
+
ensurePluginsRegisteredSync,
|
|
115
|
+
createProvider,
|
|
116
|
+
createProviders,
|
|
117
|
+
isTypeSupported,
|
|
118
|
+
getSupportedTypes,
|
|
119
|
+
reset,
|
|
120
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider abstraction layer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { EngineId, SearchQuery, SearchResponse } from "./types";
|
|
6
|
+
|
|
7
|
+
export interface ProviderMetadata {
|
|
8
|
+
id: EngineId;
|
|
9
|
+
displayName: string;
|
|
10
|
+
docsUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Abstract interface that all search providers must implement
|
|
15
|
+
*/
|
|
16
|
+
export interface SearchProvider {
|
|
17
|
+
readonly id: EngineId;
|
|
18
|
+
getMetadata(): ProviderMetadata;
|
|
19
|
+
search(query: SearchQuery): Promise<SearchResponse>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Interface for providers that manage lifecycle (init, healthcheck, shutdown)
|
|
24
|
+
*/
|
|
25
|
+
export interface ILifecycleProvider {
|
|
26
|
+
init(): Promise<void>;
|
|
27
|
+
healthcheck(): Promise<boolean>;
|
|
28
|
+
shutdown(): Promise<void>;
|
|
29
|
+
validateConfig(): Promise<{
|
|
30
|
+
valid: boolean;
|
|
31
|
+
errors: string[];
|
|
32
|
+
warnings: string[];
|
|
33
|
+
}>;
|
|
34
|
+
isLifecycleManaged(): boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Registry to manage all available providers
|
|
39
|
+
*/
|
|
40
|
+
export class ProviderRegistry {
|
|
41
|
+
private providers = new Map<EngineId, SearchProvider>();
|
|
42
|
+
|
|
43
|
+
register(provider: SearchProvider): void {
|
|
44
|
+
if (this.providers.has(provider.id)) {
|
|
45
|
+
throw new Error(`Provider already registered: ${provider.id}`);
|
|
46
|
+
}
|
|
47
|
+
this.providers.set(provider.id, provider);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get(id: EngineId): SearchProvider | undefined {
|
|
51
|
+
return this.providers.get(id);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
list(): SearchProvider[] {
|
|
55
|
+
return Array.from(this.providers.values());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
has(id: EngineId): boolean {
|
|
59
|
+
return this.providers.has(id);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Keys for Dependency Injection Container
|
|
3
|
+
*
|
|
4
|
+
* Centralized constants for all service identifiers used with the DI container.
|
|
5
|
+
* Using constants instead of magic strings prevents typos and enables IDE support.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { ServiceKeys } from '../core/serviceKeys';
|
|
10
|
+
*
|
|
11
|
+
* // Register service
|
|
12
|
+
* container.singleton(ServiceKeys.CONFIG, () => config);
|
|
13
|
+
*
|
|
14
|
+
* // Resolve service
|
|
15
|
+
* const config = container.get<AllSearchConfig>(ServiceKeys.CONFIG);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* All service keys used in the DI container
|
|
21
|
+
*/
|
|
22
|
+
export const ServiceKeys = {
|
|
23
|
+
/** Application configuration */
|
|
24
|
+
CONFIG: "config",
|
|
25
|
+
|
|
26
|
+
/** Credit state persistence provider */
|
|
27
|
+
CREDIT_STATE_PROVIDER: "creditStateProvider",
|
|
28
|
+
|
|
29
|
+
/** Credit manager for tracking usage */
|
|
30
|
+
CREDIT_MANAGER: "creditManager",
|
|
31
|
+
|
|
32
|
+
/** Registry of all search providers */
|
|
33
|
+
PROVIDER_REGISTRY: "providerRegistry",
|
|
34
|
+
|
|
35
|
+
/** Factory for creating search strategies */
|
|
36
|
+
STRATEGY_FACTORY: "strategyFactory",
|
|
37
|
+
|
|
38
|
+
/** Main search orchestrator */
|
|
39
|
+
ORCHESTRATOR: "orchestrator",
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Type representing any valid service key
|
|
44
|
+
*/
|
|
45
|
+
export type ServiceKey = (typeof ServiceKeys)[keyof typeof ServiceKeys];
|