@lincoln504/pi-research 0.1.12 → 1.0.0
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 +1 -1
- package/README.md +64 -115
- package/dist/cli.mjs +23211 -0
- package/dist/prompts/research-knowledge-relevance-triage.md +35 -0
- package/dist/prompts/research-knowledge-search-extractor.md +41 -0
- package/dist/prompts/research-tool-usage.md +92 -0
- package/dist/prompts/researcher.md +89 -0
- package/dist/prompts/system-coordinator.md +75 -0
- package/dist/prompts/system-lead-evaluator.md +118 -0
- package/dist/thread-worker.mjs +2611 -0
- package/dist/webgpu-probe.mjs +80 -0
- package/docs/AGENT-SKILL.md +102 -0
- package/docs/ARCHITECTURE.md +306 -80
- package/docs/CONFIGURATION.md +249 -0
- package/docs/KNOWLEDGE-STORE.md +213 -0
- package/docs/PI-EXTENSION.md +91 -0
- package/docs/SDK.md +113 -0
- package/docs/THIRD-PARTY-NOTICES.md +64 -0
- package/package.json +102 -36
- package/scripts/build.cjs +140 -0
- package/scripts/cleanup.cjs +149 -0
- package/scripts/prepare.cjs +28 -0
- package/scripts/setup.cjs +134 -0
- package/skills/pi-research/README.md +90 -0
- package/skills/pi-research/SKILL.md +102 -0
- package/skills/pi-research/references/configuration.md +77 -0
- package/skills/pi-research/scripts/run.mjs +237 -0
- package/src/cleanup/research-cleanup.ts +140 -0
- package/src/cli.ts +1044 -0
- package/src/config.ts +1036 -147
- package/src/constants.ts +115 -84
- package/src/core/health-check-service.ts +265 -0
- package/src/core/interfaces/health-check-interfaces.ts +50 -0
- package/src/core/interfaces/knowledge-interfaces.ts +190 -0
- package/src/core/interfaces/observer-interfaces.ts +67 -0
- package/src/core/interfaces/orchestration-interfaces.ts +86 -0
- package/src/core/interfaces/planning-interfaces.ts +100 -0
- package/src/core/interfaces/process-interfaces.ts +37 -0
- package/src/core/interfaces/research-plan-types.ts +52 -0
- package/src/core/interfaces/scheduler-interfaces.ts +91 -0
- package/src/core/interfaces/service-names.ts +39 -0
- package/src/core/interfaces/state-manager-interfaces.ts +29 -0
- package/src/core/interfaces/steering-interfaces.ts +31 -0
- package/src/core/llm/agentic-repair.ts +169 -0
- package/src/core/llm/inject-date.ts +52 -0
- package/src/core/llm/llm-timeout.ts +64 -0
- package/src/core/llm/llm-utils.ts +86 -0
- package/src/core/llm/model-registry-factory.ts +287 -0
- package/src/core/llm/pi-ai-completion.ts +29 -0
- package/src/core/llm/prompts.ts +50 -0
- package/src/core/llm/research-model-resolver.ts +91 -0
- package/src/core/planning-constants.ts +13 -0
- package/src/core/planning-service.ts +649 -0
- package/src/core/planning-utils.ts +299 -0
- package/src/core/scheduler-factory.ts +48 -0
- package/src/core/scheduler-service.ts +123 -0
- package/src/core/service-initialization.ts +194 -0
- package/src/core/service-interfaces.ts +48 -0
- package/src/core/service-registry.ts +659 -0
- package/src/healthcheck/index.ts +271 -265
- package/src/healthcheck/registry.ts +130 -0
- package/src/index.ts +639 -50
- package/src/infrastructure/browser/browser-cleanup.ts +309 -0
- package/src/infrastructure/browser/browser-client.ts +199 -0
- package/src/infrastructure/browser/browser-error-utils.ts +160 -0
- package/src/infrastructure/browser/browser-lifecycle.ts +120 -0
- package/src/infrastructure/browser/browser-server.ts +174 -0
- package/src/infrastructure/browser/browser-task-scheduler.ts +461 -0
- package/src/infrastructure/browser/cleanup-utils.ts +155 -0
- package/src/infrastructure/browser/client-agent.ts +31 -0
- package/src/infrastructure/browser/config.ts +288 -0
- package/src/infrastructure/browser/ensure-browser.ts +205 -0
- package/src/infrastructure/browser/index.ts +37 -0
- package/src/infrastructure/browser/priority-task-queue.ts +226 -0
- package/src/infrastructure/browser/scheduler-factory.ts +354 -0
- package/src/infrastructure/browser/task-execution-service.ts +209 -0
- package/src/infrastructure/browser/thread-worker-browser.ts +282 -0
- package/src/infrastructure/browser/thread-worker-lifecycle.ts +323 -0
- package/src/infrastructure/browser/thread-worker-messaging.ts +662 -0
- package/src/infrastructure/browser/thread-worker-types.ts +25 -0
- package/src/infrastructure/browser/thread-worker.mjs +2611 -0
- package/src/infrastructure/browser/thread-worker.ts +168 -0
- package/src/infrastructure/browser/worker-pool-manager.ts +449 -0
- package/src/infrastructure/embedding/embedding-client.ts +205 -0
- package/src/infrastructure/embedding/embedding-factory.ts +324 -0
- package/src/infrastructure/embedding/embedding-server.ts +535 -0
- package/src/infrastructure/file-lock-service.ts +592 -0
- package/src/infrastructure/gpu-resource-service.ts +191 -0
- package/src/infrastructure/knowledge-store-service.ts +475 -0
- package/src/infrastructure/metrics-service.ts +129 -0
- package/src/infrastructure/process-lifecycle-service.ts +253 -0
- package/src/infrastructure/scheduler-factory-service.ts +58 -0
- package/src/infrastructure/service-initialization.ts +292 -0
- package/src/infrastructure/state/state-backup-manager.ts +290 -0
- package/src/infrastructure/state/state-browser-api.ts +62 -0
- package/src/infrastructure/state/state-browser-manager.ts +70 -0
- package/src/infrastructure/state/state-manager-service.ts +264 -0
- package/src/infrastructure/state/state-manager.ts +547 -0
- package/src/infrastructure/state/state-metrics.ts +92 -0
- package/src/infrastructure/state/state-migration.ts +103 -0
- package/src/infrastructure/state/state-path-configuration.ts +119 -0
- package/src/infrastructure/state/state-session-api.ts +94 -0
- package/src/infrastructure/state/state-session-manager.ts +127 -0
- package/src/infrastructure/state/state-validator.ts +93 -0
- package/src/infrastructure/types/state-types.ts +85 -0
- package/src/knowledge/chunker.ts +157 -0
- package/src/knowledge/embedder-init.ts +413 -0
- package/src/knowledge/embedder-utils.ts +296 -0
- package/src/knowledge/embedder.ts +592 -0
- package/src/knowledge/index.ts +126 -0
- package/src/knowledge/lancedb-loader.ts +35 -0
- package/src/knowledge/migration.ts +17 -0
- package/src/knowledge/model-config.ts +132 -0
- package/src/knowledge/onnx-env.ts +48 -0
- package/src/knowledge/store-operations.ts +291 -0
- package/src/knowledge/store-schema.ts +79 -0
- package/src/knowledge/store.ts +1251 -0
- package/src/knowledge/transformers-loader.ts +31 -0
- package/src/knowledge/webgpu-probe.mjs +80 -0
- package/src/knowledge/webgpu-viability.ts +255 -0
- package/src/knowledge/writer-queue.ts +252 -0
- package/src/logger.ts +316 -122
- package/src/observers/research-observer-impl.ts +459 -0
- package/src/orchestration/deep-research-orchestrator.ts +524 -773
- package/src/orchestration/deep-research-types.ts +47 -35
- package/src/orchestration/headless-observer.ts +114 -0
- package/src/orchestration/orchestration-types.ts +33 -0
- package/src/orchestration/quick-research-orchestrator.ts +463 -0
- package/src/orchestration/research-orchestration-service.ts +549 -0
- package/src/orchestration/research-session-service.ts +178 -0
- package/src/orchestration/research-synthesis-service.ts +348 -0
- package/src/orchestration/researcher-executor.ts +394 -0
- package/src/orchestration/researcher.ts +115 -42
- package/src/orchestration/service-initialization.ts +109 -0
- package/src/orchestration/session-context.ts +7 -3
- package/src/orchestration/session-state.ts +672 -0
- package/src/prompts/research-knowledge-relevance-triage.md +35 -0
- package/src/prompts/research-knowledge-search-extractor.md +41 -0
- package/src/prompts/research-tool-usage.md +63 -16
- package/src/prompts/researcher.md +59 -136
- package/src/prompts/system-coordinator.md +64 -13
- package/src/prompts/system-lead-evaluator.md +82 -189
- package/src/research-config.ts +872 -0
- package/src/sdk.ts +847 -0
- package/src/security/cisa-kev.ts +72 -9
- package/src/security/cvss.ts +92 -0
- package/src/security/ecosystem.ts +45 -0
- package/src/security/github-advisories.ts +168 -428
- package/src/security/github-advisory-types.ts +282 -0
- package/src/security/index.ts +63 -177
- package/src/security/nvd-client.ts +550 -0
- package/src/security/nvd-types.ts +179 -0
- package/src/security/osv-client.ts +246 -0
- package/src/security/osv-types.ts +285 -0
- package/src/security/types.ts +67 -53
- package/src/skill-install/skill-installer.ts +519 -0
- package/src/stackexchange/index.ts +81 -27
- package/src/stackexchange/output/compact.ts +7 -6
- package/src/stackexchange/output/table.ts +11 -7
- package/src/stackexchange/queries.ts +0 -10
- package/src/stackexchange/rest-client.ts +141 -66
- package/src/stackexchange/types.ts +0 -17
- package/src/tool.ts +15 -423
- package/src/tools/grep.ts +32 -310
- package/src/tools/health-tool-definition.ts +101 -0
- package/src/tools/index.ts +38 -9
- package/src/tools/research-knowledge-search.ts +974 -0
- package/src/tools/research-knowledge-types.ts +77 -0
- package/src/tools/research-tool-definition.ts +494 -0
- package/src/tools/scrape.ts +286 -388
- package/src/tools/search.ts +98 -153
- package/src/tools/security.ts +107 -80
- package/src/tools/stackexchange.ts +58 -35
- package/src/tools/youtube-transcript.ts +163 -0
- package/src/tui/knowledge-search-panel.ts +86 -0
- package/src/tui/pulse.ts +86 -0
- package/src/tui/research-health.ts +124 -0
- package/src/tui/research-panel-color-utils.ts +340 -0
- package/src/tui/research-panel-format.ts +54 -0
- package/src/tui/research-panel-state.ts +404 -0
- package/src/tui/research-panel-wave.ts +280 -0
- package/src/tui/research-panel.ts +339 -435
- package/src/tui/research-tui-manager.ts +233 -0
- package/src/tui/tui-controller.ts +137 -0
- package/src/types/extension-context.ts +18 -49
- package/src/types/index.ts +61 -0
- package/src/types/llm.ts +68 -29
- package/src/types/research-panel-types.ts +61 -0
- package/src/utils/circuit-breaker.ts +146 -0
- package/src/utils/citation-utils.ts +97 -0
- package/src/utils/disk-space-checker.ts +55 -0
- package/src/utils/error-tracker.ts +265 -0
- package/src/utils/host-config.ts +45 -0
- package/src/utils/input-validation.ts +8 -10
- package/src/utils/json-utils.ts +150 -16
- package/src/utils/llm-usage.ts +54 -0
- package/src/utils/log-rotation.ts +125 -0
- package/src/utils/log-utils.ts +159 -0
- package/src/utils/make-resource-loader.ts +3 -1
- package/src/utils/metrics-summary.ts +459 -0
- package/src/utils/metrics.ts +302 -0
- package/src/utils/research-export.ts +28 -12
- package/src/utils/safe-unref.ts +31 -0
- package/src/utils/shared-links.ts +195 -90
- package/src/utils/shutdown-manager.ts +65 -0
- package/src/utils/stdio-capture.ts +407 -0
- package/src/utils/text-utils.ts +264 -7
- package/src/utils/tool-usage-tracker.ts +83 -16
- package/src/utils/url-utils.ts +255 -0
- package/src/utils/user-agent.ts +28 -0
- package/src/web-research/browser-search.ts +179 -0
- package/src/web-research/retry-utils.ts +173 -77
- package/src/web-research/scraper-types.ts +72 -0
- package/src/web-research/scraper-utils.ts +434 -0
- package/src/web-research/search.ts +78 -218
- package/src/web-research/types.ts +9 -24
- package/src/web-research/utils.ts +2 -143
- package/src/web-research/web-scraper.ts +477 -0
- package/src/youtube/index.ts +154 -0
- package/src/youtube/potoken.ts +259 -0
- package/src/youtube/transcript-client.ts +390 -0
- package/src/youtube/video-id.ts +118 -0
- package/config/searxng/default-settings.yml +0 -189
- package/config/searxng/limiter.toml +0 -34
- package/config/tooling/eslint.config.js +0 -73
- package/config/tooling/vitest.config.integration.ts +0 -17
- package/config/tooling/vitest.config.ts +0 -40
- package/config/tooling/vitest.config.unit.ts +0 -12
- package/docs/assets/README-banner.jpg +0 -0
- package/scripts/setup.js +0 -174
- package/src/infrastructure/searxng-lifecycle.ts +0 -577
- package/src/infrastructure/searxng-manager.ts +0 -1183
- package/src/infrastructure/state-manager.ts +0 -918
- package/src/orchestration/deep-research-reducer.ts +0 -129
- package/src/orchestration/id-utils.ts +0 -63
- package/src/security/nvd.ts +0 -532
- package/src/security/osv.ts +0 -487
- package/src/stackexchange/cache.ts +0 -111
- package/src/stackexchange/output/index.ts +0 -7
- package/src/stackexchange/output/json.ts +0 -21
- package/src/tui/searxng-status.ts +0 -80
- package/src/utils/inject-date.ts +0 -29
- package/src/utils/research-error.ts +0 -205
- package/src/utils/searxng-config.ts +0 -94
- package/src/utils/session-state.ts +0 -295
- package/src/web-research/dependencies.ts +0 -20
- package/src/web-research/scrapers.ts +0 -935
- package/tsconfig.json +0 -40
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Interfaces for pi-research
|
|
3
|
+
*
|
|
4
|
+
* Central re-export barrel for all service interfaces.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Scheduler interfaces
|
|
8
|
+
export type { SearchResult, ScrapeResult, IScheduler, SchedulerMetadata, ISchedulerInstance, ISchedulerInternals, ISchedulerService } from './interfaces/scheduler-interfaces.ts';
|
|
9
|
+
export type { ISchedulerFactory } from './scheduler-factory.ts';
|
|
10
|
+
|
|
11
|
+
// Health check interfaces
|
|
12
|
+
export type { HealthCheckResult, HealthCheckCache, IHealthCheckService, IHealthRegistryService } from './interfaces/health-check-interfaces.ts';
|
|
13
|
+
|
|
14
|
+
// Knowledge store interfaces
|
|
15
|
+
export type {
|
|
16
|
+
IEmbedder,
|
|
17
|
+
IKnowledgeStore,
|
|
18
|
+
IKnowledgeStoreService,
|
|
19
|
+
IWriterQueue,
|
|
20
|
+
IMetricsSnapshot,
|
|
21
|
+
IMetricHistogram,
|
|
22
|
+
IngestionItem,
|
|
23
|
+
StoreUrlEntry,
|
|
24
|
+
StoreDocument,
|
|
25
|
+
EmbedderOptions,
|
|
26
|
+
EmbedderState,
|
|
27
|
+
HFEnv,
|
|
28
|
+
ONNXRuntimeEnv,
|
|
29
|
+
DisposablePipeline
|
|
30
|
+
} from './interfaces/knowledge-interfaces.ts';
|
|
31
|
+
|
|
32
|
+
// State manager interfaces
|
|
33
|
+
export type { IStateManager } from './interfaces/state-manager-interfaces.ts';
|
|
34
|
+
|
|
35
|
+
// Process interfaces
|
|
36
|
+
export type { IProcessLifecycle, IGPUResourceService } from './interfaces/process-interfaces.ts';
|
|
37
|
+
|
|
38
|
+
// Observer interfaces
|
|
39
|
+
export type { ResearchObserver } from './interfaces/observer-interfaces.ts';
|
|
40
|
+
|
|
41
|
+
// Orchestration interfaces
|
|
42
|
+
export type { IResearchOrchestration, RunResearchersOptions, IResearchSynthesisService, ResearchOptions } from './interfaces/orchestration-interfaces.ts';
|
|
43
|
+
|
|
44
|
+
// Planning interfaces
|
|
45
|
+
export type { ResearchPlan, ResearcherConfig, SessionContext, GeneratePlanOptions, GenerateQueriesOptions, UpdatePlanOptions, IPlanningService } from './interfaces/planning-interfaces.ts';
|
|
46
|
+
|
|
47
|
+
// Service names
|
|
48
|
+
export { ServiceNames, type ServiceName } from './interfaces/service-names.ts';
|
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Registry - Centralized Dependency Injection Container
|
|
3
|
+
*
|
|
4
|
+
* This module provides a service registry pattern to manage singleton services
|
|
5
|
+
* throughout the pi-research application. It replaces global state management
|
|
6
|
+
* with a proper dependency injection system.
|
|
7
|
+
*
|
|
8
|
+
* Benefits:
|
|
9
|
+
* - Explicit dependency management
|
|
10
|
+
* - Testability (easy to mock/replace services)
|
|
11
|
+
* - Lifecycle management (init, cleanup)
|
|
12
|
+
* - Type safety
|
|
13
|
+
* - No more globalThis pollution
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { logger } from '../logger.ts';
|
|
17
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Service lifecycle stages
|
|
22
|
+
*/
|
|
23
|
+
export enum ServiceLifecycle {
|
|
24
|
+
UNINITIALIZED = 'uninitialized',
|
|
25
|
+
INITIALIZING = 'initializing',
|
|
26
|
+
INITIALIZED = 'initialized',
|
|
27
|
+
DISABLED = 'disabled',
|
|
28
|
+
DISPOSING = 'disposing',
|
|
29
|
+
DISPOSED = 'disposed',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Base interface for all services
|
|
34
|
+
*/
|
|
35
|
+
export interface IService {
|
|
36
|
+
/**
|
|
37
|
+
* Unique identifier for this service
|
|
38
|
+
*/
|
|
39
|
+
readonly name: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Current lifecycle state
|
|
43
|
+
*/
|
|
44
|
+
lifecycle: ServiceLifecycle;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Initialize the service (called before first use)
|
|
48
|
+
*/
|
|
49
|
+
initialize?(ctx?: any): Promise<void> | void;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Dispose the service (called during shutdown)
|
|
53
|
+
*/
|
|
54
|
+
dispose?(): Promise<void> | void;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Service factory function type
|
|
59
|
+
*/
|
|
60
|
+
export type ServiceFactory<T extends IService> = () => T | Promise<T>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Service container options
|
|
64
|
+
*/
|
|
65
|
+
export interface ServiceContainerOptions {
|
|
66
|
+
/**
|
|
67
|
+
* Whether to allow overwriting existing services
|
|
68
|
+
* @default false (service replacement is not allowed)
|
|
69
|
+
*/
|
|
70
|
+
allowOverwrite?: boolean;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Whether to log service lifecycle events
|
|
74
|
+
* @default true
|
|
75
|
+
*/
|
|
76
|
+
enableLogging?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Service registration metadata
|
|
81
|
+
*/
|
|
82
|
+
interface ServiceRegistration<T extends IService> {
|
|
83
|
+
factory: ServiceFactory<T>;
|
|
84
|
+
instance: T | null;
|
|
85
|
+
initializationPromise: Promise<T> | null;
|
|
86
|
+
options: ServiceContainerOptions;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* AsyncLocalStorage to track which service is currently being initialized.
|
|
91
|
+
* Used to build a dependency graph for safe teardown.
|
|
92
|
+
*/
|
|
93
|
+
const initializationContext = new AsyncLocalStorage<string>();
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Centralized service container for dependency injection
|
|
97
|
+
*/
|
|
98
|
+
export class ServiceContainer {
|
|
99
|
+
private services: Map<string, ServiceRegistration<any>> = new Map();
|
|
100
|
+
private dependencies: Map<string, Set<string>> = new Map();
|
|
101
|
+
public isDisposing: boolean = false;
|
|
102
|
+
// The single in-flight disposeAll() run, so concurrent disposeAll()/reset() calls
|
|
103
|
+
// coalesce onto (and AWAIT) it rather than a second caller observing "disposed" while
|
|
104
|
+
// teardown is still running, or reset() throwing mid-disposal.
|
|
105
|
+
private _disposalPromise: Promise<void> | null = null;
|
|
106
|
+
public isReady: boolean = false;
|
|
107
|
+
public cwd: string = process.cwd();
|
|
108
|
+
public config: any = null;
|
|
109
|
+
private readonly defaultOptions: Required<ServiceContainerOptions>;
|
|
110
|
+
|
|
111
|
+
constructor(options: ServiceContainerOptions = {}) {
|
|
112
|
+
this.defaultOptions = {
|
|
113
|
+
allowOverwrite: options.allowOverwrite ?? false,
|
|
114
|
+
enableLogging: options.enableLogging ?? true,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Register a service with the container
|
|
120
|
+
*/
|
|
121
|
+
register<T extends IService>(
|
|
122
|
+
name: string,
|
|
123
|
+
factory: ServiceFactory<T>,
|
|
124
|
+
options: ServiceContainerOptions = {}
|
|
125
|
+
): void {
|
|
126
|
+
if (this.isDisposing) {
|
|
127
|
+
throw new Error(`Cannot register service '${name}' during container disposal`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const mergedOptions = { ...this.defaultOptions, ...options };
|
|
131
|
+
|
|
132
|
+
if (this.services.has(name)) {
|
|
133
|
+
if (!mergedOptions.allowOverwrite) {
|
|
134
|
+
throw new Error(`Service '${name}' is already registered. Use registerAndReplace() to overwrite.`);
|
|
135
|
+
}
|
|
136
|
+
if (mergedOptions.enableLogging) {
|
|
137
|
+
logger.warn(`[ServiceContainer] Replacing service '${name}'`);
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
if (mergedOptions.enableLogging) {
|
|
141
|
+
logger.debug(`[ServiceContainer] Registering service '${name}'`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.services.set(name, {
|
|
146
|
+
factory,
|
|
147
|
+
instance: null,
|
|
148
|
+
initializationPromise: null,
|
|
149
|
+
options: mergedOptions,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Reset dependencies for this service
|
|
153
|
+
this.dependencies.set(name, new Set());
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Register a service, replacing any existing service
|
|
158
|
+
*/
|
|
159
|
+
async registerAndReplace<T extends IService>(
|
|
160
|
+
name: string,
|
|
161
|
+
factory: ServiceFactory<T>,
|
|
162
|
+
options: ServiceContainerOptions = {}
|
|
163
|
+
): Promise<void> {
|
|
164
|
+
const mergedOptions = { ...this.defaultOptions, ...options, allowOverwrite: true };
|
|
165
|
+
|
|
166
|
+
// Dispose existing service if present
|
|
167
|
+
if (this.services.has(name)) {
|
|
168
|
+
const registration = this.services.get(name)!;
|
|
169
|
+
if (registration.instance && registration.instance.dispose) {
|
|
170
|
+
try {
|
|
171
|
+
await registration.instance.dispose();
|
|
172
|
+
} catch (err: unknown) {
|
|
173
|
+
logger.warn(`[ServiceContainer] Error disposing replaced service '${name}':`, err);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
this.register(name, factory, mergedOptions);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Record a dependency between two services
|
|
183
|
+
*/
|
|
184
|
+
private addDependency(dependent: string, dependency: string): void {
|
|
185
|
+
let deps = this.dependencies.get(dependent);
|
|
186
|
+
if (!deps) {
|
|
187
|
+
deps = new Set();
|
|
188
|
+
this.dependencies.set(dependent, deps);
|
|
189
|
+
}
|
|
190
|
+
deps.add(dependency);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get a service instance, initializing it if necessary
|
|
195
|
+
*/
|
|
196
|
+
async get<T extends IService>(name: string, ctx?: any): Promise<T> {
|
|
197
|
+
if (this.isDisposing) {
|
|
198
|
+
throw new Error(`Cannot get service '${name}' during container disposal`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Track dependency if we are currently inside an initialization or use-case of another service
|
|
202
|
+
const caller = initializationContext.getStore();
|
|
203
|
+
if (caller && caller !== name) {
|
|
204
|
+
this.addDependency(caller, name);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const registration = this.services.get(name);
|
|
208
|
+
if (!registration) {
|
|
209
|
+
throw new Error(`Service '${name}' is not registered`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Return existing instance if already initialized
|
|
213
|
+
if (registration.instance) {
|
|
214
|
+
// Re-initialize if ctx is provided and service supports it
|
|
215
|
+
if (ctx && registration.instance.initialize) {
|
|
216
|
+
// Wrap in initializationContext so nested get() calls are tracked
|
|
217
|
+
return initializationContext.run(name, async () => {
|
|
218
|
+
await registration.instance!.initialize!(ctx);
|
|
219
|
+
return registration.instance as T;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return registration.instance as T;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Return existing initialization promise if in progress
|
|
226
|
+
if (registration.initializationPromise) {
|
|
227
|
+
return registration.initializationPromise as Promise<T>;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Initialize the service within the tracking context
|
|
231
|
+
registration.initializationPromise = initializationContext.run(name, () =>
|
|
232
|
+
this._initializeService(registration, ctx)
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const instance = await registration.initializationPromise;
|
|
237
|
+
return instance as T;
|
|
238
|
+
} catch (error) {
|
|
239
|
+
// Clear initialization promise on error so next call retries
|
|
240
|
+
registration.initializationPromise = null;
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get a service instance synchronously (returns null if not initialized)
|
|
247
|
+
*/
|
|
248
|
+
tryGet<T extends IService>(name: string): T | null {
|
|
249
|
+
if (this.isDisposing) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const registration = this.services.get(name);
|
|
254
|
+
if (!registration) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return registration.instance as T | null;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Check if a service is registered
|
|
263
|
+
*/
|
|
264
|
+
has(name: string): boolean {
|
|
265
|
+
return this.services.has(name);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Check if a service is initialized
|
|
270
|
+
*/
|
|
271
|
+
isInitialized(name: string): boolean {
|
|
272
|
+
const registration = this.services.get(name);
|
|
273
|
+
// Must check registration exists FIRST — undefined?.instance gives undefined,
|
|
274
|
+
// and undefined !== null is true, which would incorrectly report unregistered
|
|
275
|
+
// services as initialized.
|
|
276
|
+
if (!registration) return false;
|
|
277
|
+
return registration.instance !== null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Clear (reset) a service instance, forcing re-initialization on next access
|
|
282
|
+
*/
|
|
283
|
+
async clear(name: string): Promise<void> {
|
|
284
|
+
const registration = this.services.get(name);
|
|
285
|
+
if (!registration) {
|
|
286
|
+
throw new Error(`Service '${name}' is not registered`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (registration.instance && registration.instance.dispose) {
|
|
290
|
+
await registration.instance.dispose().catch((err: unknown) => {
|
|
291
|
+
logger.warn(`[ServiceContainer] Error disposing service '${name}':`, err);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
registration.instance = null;
|
|
296
|
+
registration.initializationPromise = null;
|
|
297
|
+
|
|
298
|
+
if (registration.options.enableLogging) {
|
|
299
|
+
logger.debug(`[ServiceContainer] Cleared service '${name}'`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Replace a service instance with a new one
|
|
305
|
+
*/
|
|
306
|
+
async replace<T extends IService>(name: string, newInstance: T): Promise<void> {
|
|
307
|
+
const registration = this.services.get(name);
|
|
308
|
+
if (!registration) {
|
|
309
|
+
throw new Error(`Service '${name}' is not registered`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Dispose old instance if present
|
|
313
|
+
if (registration.instance && registration.instance.dispose) {
|
|
314
|
+
await registration.instance.dispose().catch((err: unknown) => {
|
|
315
|
+
logger.warn(`[ServiceContainer] Error disposing old service '${name}':`, err);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
registration.instance = newInstance;
|
|
320
|
+
registration.initializationPromise = null;
|
|
321
|
+
|
|
322
|
+
if (registration.options.enableLogging) {
|
|
323
|
+
logger.debug(`[ServiceContainer] Replaced service '${name}'`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Dispose all services using a true Directed Acyclic Graph (DAG) teardown.
|
|
329
|
+
* This guarantees that services are only disposed after all their dependents
|
|
330
|
+
* have been successfully torn down.
|
|
331
|
+
*/
|
|
332
|
+
async disposeAll(): Promise<void> {
|
|
333
|
+
// Synchronous re-entrancy guard: isDisposing is set as _runDisposeAll's first
|
|
334
|
+
// statement (before any await), so a service whose dispose() re-enters disposeAll()
|
|
335
|
+
// gets a safe no-op instead of recursing. Also publish the in-flight run as
|
|
336
|
+
// _disposalPromise so reset() (a separate, non-re-entrant caller) can AWAIT the
|
|
337
|
+
// teardown instead of throwing on the race — the Windows CI teardown fix.
|
|
338
|
+
if (this.isDisposing) return;
|
|
339
|
+
this._disposalPromise = this._runDisposeAll().finally(() => { this._disposalPromise = null; });
|
|
340
|
+
return this._disposalPromise;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
private async _runDisposeAll(): Promise<void> {
|
|
344
|
+
this.isDisposing = true;
|
|
345
|
+
if (this.defaultOptions.enableLogging) {
|
|
346
|
+
logger.log('[ServiceContainer] Disposing all services (DAG-ordered teardown)...');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const activeServices = Array.from(this.services.keys()).filter(name =>
|
|
351
|
+
this.services.get(name)?.instance !== null
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const disposed = new Set<string>();
|
|
355
|
+
|
|
356
|
+
// Perform iterative disposal of services that have no active dependents
|
|
357
|
+
while (disposed.size < activeServices.length) {
|
|
358
|
+
const toDispose: string[] = [];
|
|
359
|
+
|
|
360
|
+
for (const name of activeServices) {
|
|
361
|
+
if (disposed.has(name)) continue;
|
|
362
|
+
|
|
363
|
+
// A service can be disposed if no OTHER active service depends on it
|
|
364
|
+
// that hasn't been disposed yet.
|
|
365
|
+
const hasUndisposedDependents = activeServices.some(other =>
|
|
366
|
+
!disposed.has(other) &&
|
|
367
|
+
other !== name &&
|
|
368
|
+
this.dependencies.get(other)?.has(name)
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
if (!hasUndisposedDependents) {
|
|
372
|
+
toDispose.push(name);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (toDispose.length === 0) {
|
|
377
|
+
// Circular dependency detected or logic error
|
|
378
|
+
const remaining = activeServices.filter(n => !disposed.has(n));
|
|
379
|
+
logger.warn(`[ServiceContainer] Circular dependency or stuck disposal detected for: ${remaining.join(', ')}. Falling back to reverse-registration order.`);
|
|
380
|
+
|
|
381
|
+
const reverseRegistrations = Array.from(this.services.entries()).reverse();
|
|
382
|
+
for (const [name, registration] of reverseRegistrations) {
|
|
383
|
+
if (!disposed.has(name) && registration.instance && registration.instance.dispose) {
|
|
384
|
+
try { await registration.instance.dispose(); } catch (err) { logger.warn(`[ServiceContainer] Error disposing '${name}':`, err); }
|
|
385
|
+
registration.instance = null;
|
|
386
|
+
registration.initializationPromise = null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// IMPORTANT: Sort toDispose in reverse-registration order.
|
|
393
|
+
// This ensures that if multiple services are independent, we still dispose
|
|
394
|
+
// the one registered latest first, maintaining strict LIFO behavior by default.
|
|
395
|
+
const serviceNamesInOrder = Array.from(this.services.keys());
|
|
396
|
+
toDispose.sort((a, b) => serviceNamesInOrder.indexOf(b) - serviceNamesInOrder.indexOf(a));
|
|
397
|
+
|
|
398
|
+
// Dispose independent services in parallel (but sequentially within this batch
|
|
399
|
+
// if we want to be 100% sure about order, though Promise.all is fine if they are truly independent)
|
|
400
|
+
// Actually, to satisfy the test expectations of strict order, we should do them sequentially.
|
|
401
|
+
for (const name of toDispose) {
|
|
402
|
+
const registration = this.services.get(name)!;
|
|
403
|
+
if (registration.instance && registration.instance.dispose) {
|
|
404
|
+
try {
|
|
405
|
+
await registration.instance.dispose();
|
|
406
|
+
} catch (err: unknown) {
|
|
407
|
+
logger.warn(`[ServiceContainer] Error disposing service '${name}':`, err);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
registration.instance = null;
|
|
411
|
+
registration.initializationPromise = null;
|
|
412
|
+
disposed.add(name);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
} finally {
|
|
416
|
+
this.isDisposing = false;
|
|
417
|
+
if (this.defaultOptions.enableLogging) {
|
|
418
|
+
logger.log('[ServiceContainer] All services disposed (registrations preserved)');
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Get the number of registered services
|
|
425
|
+
*/
|
|
426
|
+
get size(): number {
|
|
427
|
+
return this.services.size;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Get names of all registered services
|
|
432
|
+
*/
|
|
433
|
+
getServiceNames(): string[] {
|
|
434
|
+
return Array.from(this.services.keys());
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Get lifecycle state of a service
|
|
439
|
+
*/
|
|
440
|
+
getServiceLifecycle(name: string): ServiceLifecycle | null {
|
|
441
|
+
const registration = this.services.get(name);
|
|
442
|
+
if (!registration) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (registration.instance) {
|
|
447
|
+
return ServiceLifecycle.INITIALIZED;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (registration.initializationPromise) {
|
|
451
|
+
return ServiceLifecycle.INITIALIZING;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return ServiceLifecycle.UNINITIALIZED;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Reset the container, clearing all services
|
|
459
|
+
* This is primarily used for testing to ensure clean state between test runs
|
|
460
|
+
*/
|
|
461
|
+
async reset(): Promise<void> {
|
|
462
|
+
// If a disposal is already in flight (e.g. a test's un-awaited shutdown, or a
|
|
463
|
+
// concurrent teardown), WAIT for it rather than throwing — reset()'s contract is to
|
|
464
|
+
// end up disposed+cleared, which the in-flight disposal is already doing. This fixed
|
|
465
|
+
// a Windows-only CI teardown race that surfaced as unhandled
|
|
466
|
+
// "Cannot reset container while disposing" rejections from an afterEach.
|
|
467
|
+
if (this._disposalPromise) {
|
|
468
|
+
await this._disposalPromise.catch(() => { /* disposal errors are logged in _runDisposeAll */ });
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (this.defaultOptions.enableLogging) {
|
|
472
|
+
logger.debug('[ServiceContainer] Resetting container...');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Use the safe DAG disposal logic
|
|
476
|
+
await this.disposeAll();
|
|
477
|
+
|
|
478
|
+
// Drain any disposal still in flight before clearing registrations. disposeAll()
|
|
479
|
+
// returns immediately (isDisposing guard) when ANOTHER caller's disposal is already
|
|
480
|
+
// running, so our await above may not have waited for it — and a concurrent
|
|
481
|
+
// disposeAll iterating this.services while we clear() would make its services.get()
|
|
482
|
+
// throw. Loop until no disposal promise remains (each clears _disposalPromise in its
|
|
483
|
+
// finally). Non-reentrant and terminating: no new disposal can start once we hold the
|
|
484
|
+
// sync path here with isDisposing still true.
|
|
485
|
+
while (this._disposalPromise) {
|
|
486
|
+
await this._disposalPromise.catch(() => { /* logged in _runDisposeAll */ });
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Clear all registrations
|
|
490
|
+
this.services.clear();
|
|
491
|
+
this.dependencies.clear();
|
|
492
|
+
this.isDisposing = false;
|
|
493
|
+
this.isReady = false;
|
|
494
|
+
|
|
495
|
+
if (this.defaultOptions.enableLogging) {
|
|
496
|
+
logger.debug('[ServiceContainer] Container reset complete');
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Internal method to initialize a service
|
|
502
|
+
*/
|
|
503
|
+
private async _initializeService<T extends IService>(
|
|
504
|
+
registration: ServiceRegistration<T>,
|
|
505
|
+
ctx?: any
|
|
506
|
+
): Promise<T> {
|
|
507
|
+
let instance: T | null = null;
|
|
508
|
+
try {
|
|
509
|
+
instance = await registration.factory();
|
|
510
|
+
registration.instance = instance;
|
|
511
|
+
|
|
512
|
+
// Update lifecycle to initializing
|
|
513
|
+
instance.lifecycle = ServiceLifecycle.INITIALIZING;
|
|
514
|
+
|
|
515
|
+
// Call initialize hook if present
|
|
516
|
+
if (instance.initialize) {
|
|
517
|
+
await instance.initialize(ctx);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Update lifecycle to initialized — but PRESERVE a DISABLED verdict the service's own
|
|
521
|
+
// initialize() set for itself (e.g. the knowledge store when Knowledge Mode is 'none', or
|
|
522
|
+
// a missing native binding). Unconditionally forcing INITIALIZED here left the store
|
|
523
|
+
// "initialized" with null components: it mis-reported as "initializing" instead of
|
|
524
|
+
// "disabled", and — because the mode-reenable revival keys off lifecycle === DISABLED — it
|
|
525
|
+
// defeated live re-enabling via /research-config, so the store stayed dead until a restart.
|
|
526
|
+
// Cast defeats control-flow narrowing: TS still thinks lifecycle is INITIALIZING (set
|
|
527
|
+
// above), unaware that the awaited initialize() may have mutated it to DISABLED via `this`.
|
|
528
|
+
if ((instance.lifecycle as ServiceLifecycle) !== ServiceLifecycle.DISABLED) {
|
|
529
|
+
instance.lifecycle = ServiceLifecycle.INITIALIZED;
|
|
530
|
+
}
|
|
531
|
+
registration.initializationPromise = null;
|
|
532
|
+
|
|
533
|
+
if (registration.options.enableLogging) {
|
|
534
|
+
logger.debug(`[ServiceContainer] Service '${instance.name}' initialized`);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
return instance;
|
|
538
|
+
} catch (error) {
|
|
539
|
+
// Clean up instance on failure
|
|
540
|
+
if (instance) {
|
|
541
|
+
instance.lifecycle = ServiceLifecycle.UNINITIALIZED;
|
|
542
|
+
registration.instance = null;
|
|
543
|
+
}
|
|
544
|
+
registration.initializationPromise = null;
|
|
545
|
+
throw error;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// ============================================================================
|
|
551
|
+
// Global Service Container Instance
|
|
552
|
+
// ============================================================================
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Global service container instance
|
|
556
|
+
* This is the default container for the application.
|
|
557
|
+
* CLI mode typically uses this instance.
|
|
558
|
+
*/
|
|
559
|
+
let globalServiceContainer = new ServiceContainer({
|
|
560
|
+
allowOverwrite: false,
|
|
561
|
+
enableLogging: true,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Get the global service container instance
|
|
566
|
+
*/
|
|
567
|
+
export function getServiceContainer(): ServiceContainer {
|
|
568
|
+
return globalServiceContainer;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Create a new service container instance
|
|
573
|
+
*/
|
|
574
|
+
export function createServiceContainer(options: ServiceContainerOptions = {}): ServiceContainer {
|
|
575
|
+
return new ServiceContainer(options);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Convenience function to register a service
|
|
580
|
+
*/
|
|
581
|
+
export function registerService<T extends IService>(
|
|
582
|
+
name: string,
|
|
583
|
+
factory: ServiceFactory<T>,
|
|
584
|
+
options?: ServiceContainerOptions,
|
|
585
|
+
container: ServiceContainer = globalServiceContainer
|
|
586
|
+
): void {
|
|
587
|
+
container.register(name, factory, options);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Convenience function to replace a service
|
|
592
|
+
*/
|
|
593
|
+
export function replaceService<T extends IService>(
|
|
594
|
+
name: string,
|
|
595
|
+
factory: ServiceFactory<T>,
|
|
596
|
+
options?: ServiceContainerOptions,
|
|
597
|
+
container: ServiceContainer = globalServiceContainer
|
|
598
|
+
): Promise<void> {
|
|
599
|
+
return container.registerAndReplace(name, factory, options);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Convenience function to get a service
|
|
604
|
+
*/
|
|
605
|
+
export function getService<T extends IService>(name: string, ctx?: any, container: ServiceContainer = globalServiceContainer): Promise<T> {
|
|
606
|
+
return container.get<T>(name, ctx);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Convenience function to try getting a service synchronously
|
|
611
|
+
*/
|
|
612
|
+
export function tryGetService<T extends IService>(name: string, container: ServiceContainer = globalServiceContainer): T | null {
|
|
613
|
+
return container.tryGet<T>(name);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Convenience function to clear a service
|
|
618
|
+
*/
|
|
619
|
+
export function clearService(name: string, container: ServiceContainer = globalServiceContainer): Promise<void> {
|
|
620
|
+
return container.clear(name);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Convenience function to check if a service is registered
|
|
625
|
+
*/
|
|
626
|
+
export function hasService(name: string, container: ServiceContainer = globalServiceContainer): boolean {
|
|
627
|
+
return container.has(name);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Convenience function to check if a service is initialized
|
|
632
|
+
*/
|
|
633
|
+
export function isServiceInitialized(name: string, container: ServiceContainer = globalServiceContainer): boolean {
|
|
634
|
+
return container.isInitialized(name);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Helper to extract a service container from a context object, if present.
|
|
639
|
+
*/
|
|
640
|
+
export function tryGetServiceContainerFromCtx(ctx: any): ServiceContainer {
|
|
641
|
+
if (ctx?.container && typeof ctx.container.get === 'function' && typeof ctx.container.register === 'function') {
|
|
642
|
+
return ctx.container;
|
|
643
|
+
}
|
|
644
|
+
return globalServiceContainer;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Dispose all services
|
|
649
|
+
*/
|
|
650
|
+
export function disposeAllServices(container: ServiceContainer = globalServiceContainer): Promise<void> {
|
|
651
|
+
return container.disposeAll();
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Reset the global service container
|
|
656
|
+
*/
|
|
657
|
+
export function resetServiceContainer(container: ServiceContainer = globalServiceContainer): Promise<void> {
|
|
658
|
+
return container.reset();
|
|
659
|
+
}
|