@onerjs/shared-ui-components 8.50.1 → 8.50.3
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/modularTool/extensibility/extensionManager.js +1 -1
- package/modularTool/extensibility/extensionManager.js.map +1 -1
- package/modularTool/modularBridge.d.ts +53 -0
- package/modularTool/modularBridge.js +42 -0
- package/modularTool/modularBridge.js.map +1 -0
- package/modularTool/modularTool.d.ts +3 -2
- package/modularTool/modularTool.js +17 -10
- package/modularTool/modularTool.js.map +1 -1
- package/modularTool/modularity/serviceContainer.d.ts +6 -7
- package/modularTool/modularity/serviceContainer.js +13 -23
- package/modularTool/modularity/serviceContainer.js.map +1 -1
- package/modularTool/modularity/serviceDefinition.d.ts +3 -4
- package/modularTool/modularity/serviceDefinition.js.map +1 -1
- package/modularTool/services/cli/bridgeCommandRegistry.d.ts +78 -0
- package/modularTool/services/cli/bridgeCommandRegistry.js +7 -0
- package/modularTool/services/cli/bridgeCommandRegistry.js.map +1 -0
- package/modularTool/services/cli/bridgeConnectionStatus.d.ts +29 -0
- package/modularTool/services/cli/bridgeConnectionStatus.js +7 -0
- package/modularTool/services/cli/bridgeConnectionStatus.js.map +1 -0
- package/modularTool/services/cli/bridgeService.d.ts +32 -0
- package/modularTool/services/cli/bridgeService.js +204 -0
- package/modularTool/services/cli/bridgeService.js.map +1 -0
- package/modularTool/services/cli/protocol.d.ts +202 -0
- package/modularTool/services/cli/protocol.js +3 -0
- package/modularTool/services/cli/protocol.js.map +1 -0
- package/nodeGraphSystem/graphNode.js +1 -1
- package/nodeGraphSystem/graphNode.js.map +1 -1
- package/package.json +1 -1
|
@@ -33,17 +33,15 @@ export class ServiceContainer {
|
|
|
33
33
|
_parent?._children.add(this);
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
|
-
* Adds a set of service definitions
|
|
36
|
+
* Adds a set of service definitions to the service container.
|
|
37
37
|
* The services are sorted based on their dependencies.
|
|
38
|
-
* @param
|
|
39
|
-
* @returns A disposable that will remove the service
|
|
38
|
+
* @param serviceDefinitions The service definitions to register.
|
|
39
|
+
* @returns A disposable that will remove the service definitions from the service container.
|
|
40
40
|
*/
|
|
41
|
-
|
|
41
|
+
addServices(...serviceDefinitions) {
|
|
42
42
|
if (this._isDisposed) {
|
|
43
43
|
throw new Error("ServiceContainer is disposed.");
|
|
44
44
|
}
|
|
45
|
-
const abortSignal = args[args.length - 1] instanceof AbortSignal ? args.pop() : undefined;
|
|
46
|
-
const serviceDefinitions = args;
|
|
47
45
|
const sortedServiceDefinitions = SortServiceDefinitions(serviceDefinitions);
|
|
48
46
|
const dispose = () => {
|
|
49
47
|
for (const serviceDefinition of sortedServiceDefinitions.reverse()) {
|
|
@@ -52,9 +50,7 @@ export class ServiceContainer {
|
|
|
52
50
|
};
|
|
53
51
|
try {
|
|
54
52
|
for (const serviceDefinition of sortedServiceDefinitions) {
|
|
55
|
-
|
|
56
|
-
// eslint-disable-next-line no-await-in-loop
|
|
57
|
-
await this._addServiceAsync(serviceDefinition, abortSignal);
|
|
53
|
+
this._addService(serviceDefinition);
|
|
58
54
|
}
|
|
59
55
|
}
|
|
60
56
|
catch (error) {
|
|
@@ -68,18 +64,12 @@ export class ServiceContainer {
|
|
|
68
64
|
/**
|
|
69
65
|
* Registers a service definition in the service container.
|
|
70
66
|
* @param serviceDefinition The service definition to register.
|
|
71
|
-
* @param abortSignal An optional abort signal.
|
|
72
67
|
* @returns A disposable that will remove the service definition from the service container.
|
|
73
68
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return await this.addServicesAsync(serviceDefinition, abortSignal);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
return await this.addServicesAsync(serviceDefinition);
|
|
80
|
-
}
|
|
69
|
+
addService(serviceDefinition) {
|
|
70
|
+
return this.addServices(serviceDefinition);
|
|
81
71
|
}
|
|
82
|
-
|
|
72
|
+
_addService(service) {
|
|
83
73
|
if (this._isDisposed) {
|
|
84
74
|
throw new Error(`'${this._friendlyName}' container is disposed.`);
|
|
85
75
|
}
|
|
@@ -92,7 +82,7 @@ export class ServiceContainer {
|
|
|
92
82
|
this._serviceDefinitions.set(contract, service);
|
|
93
83
|
});
|
|
94
84
|
const dependencies = service.consumes?.map((contract) => this._resolveDependency(contract, service)) ?? [];
|
|
95
|
-
this._serviceInstances.set(service,
|
|
85
|
+
this._serviceInstances.set(service, service.factory(...dependencies));
|
|
96
86
|
}
|
|
97
87
|
/**
|
|
98
88
|
* Resolves a dependency by contract identity for a consuming service.
|
|
@@ -105,15 +95,15 @@ export class ServiceContainer {
|
|
|
105
95
|
_resolveDependency(contract, consumer) {
|
|
106
96
|
const definition = this._serviceDefinitions.get(contract);
|
|
107
97
|
if (definition) {
|
|
98
|
+
const instance = this._serviceInstances.get(definition);
|
|
99
|
+
if (!instance) {
|
|
100
|
+
throw new Error(`Service '${contract.toString()}' has not been instantiated in the '${this._friendlyName}' container.`);
|
|
101
|
+
}
|
|
108
102
|
let dependentDefinitions = this._serviceDependents.get(definition);
|
|
109
103
|
if (!dependentDefinitions) {
|
|
110
104
|
this._serviceDependents.set(definition, (dependentDefinitions = new Set()));
|
|
111
105
|
}
|
|
112
106
|
dependentDefinitions.add(consumer);
|
|
113
|
-
const instance = this._serviceInstances.get(definition);
|
|
114
|
-
if (!instance) {
|
|
115
|
-
throw new Error(`Service '${contract.toString()}' has not been instantiated in the '${this._friendlyName}' container.`);
|
|
116
|
-
}
|
|
117
107
|
return instance;
|
|
118
108
|
}
|
|
119
109
|
if (this._parent) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serviceContainer.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/modularTool/modularity/serviceContainer.ts"],"names":[],"mappings":"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","sourcesContent":["import { type IDisposable } from \"core/index\";\r\n\r\nimport { type IService, type ServiceDefinition } from \"./serviceDefinition\";\r\n\r\nimport { SortGraph } from \"../misc/graphUtils\";\r\n\r\n// Service definitions should strongly typed when they are defined to avoid programming errors. However, they are tracked internally\r\n// in a weakly typed manner so they can be handled generically.\r\nexport type WeaklyTypedServiceDefinition = Omit<ServiceDefinition<IService<symbol>[] | [], IService<symbol>[] | []>, \"factory\"> & {\r\n /**\r\n * A factory function responsible for creating a service instance.\r\n */\r\n factory: (...args: any) => ReturnType<ServiceDefinition<IService<symbol>[] | [], IService<symbol>[] | []>[\"factory\"]>;\r\n};\r\n\r\n// This sorts a set of service definitions based on their dependencies (e.g. a topological sort).\r\nfunction SortServiceDefinitions(serviceDefinitions: WeaklyTypedServiceDefinition[]) {\r\n const sortedServiceDefinitions: typeof serviceDefinitions = [];\r\n SortGraph(\r\n serviceDefinitions,\r\n function* (serviceDefinition) {\r\n // Check each dependency.\r\n for (const contractIdentity of serviceDefinition.consumes ?? []) {\r\n // If another service definition produces the dependency contract, return it as an adjacent node for the purpose of sorting.\r\n yield* serviceDefinitions.filter((otherServiceDefinition) => (otherServiceDefinition.produces ?? []).includes(contractIdentity));\r\n }\r\n },\r\n sortedServiceDefinitions.push.bind(sortedServiceDefinitions)\r\n );\r\n return sortedServiceDefinitions;\r\n}\r\n\r\n/**\r\n * A service container manages the lifetimes of a set of services.\r\n * It takes care of instantiating the services in the correct order based on their dependencies,\r\n * passing dependencies through to services, and disposing of services when the container is disposed.\r\n */\r\nexport class ServiceContainer implements IDisposable {\r\n private _isDisposed = false;\r\n private readonly _serviceDefinitions = new Map<symbol, WeaklyTypedServiceDefinition>();\r\n private readonly _serviceDependents = new Map<WeaklyTypedServiceDefinition, Set<WeaklyTypedServiceDefinition>>();\r\n private readonly _serviceInstances = new Map<WeaklyTypedServiceDefinition, (IService<symbol> & Partial<IDisposable>) | void>();\r\n private readonly _children = new Set<ServiceContainer>();\r\n\r\n /**\r\n * Creates a new ServiceContainer.\r\n * @param _friendlyName A human-readable name for debugging.\r\n * @param _parent An optional parent container. Dependencies not found locally will be resolved from the parent.\r\n */\r\n public constructor(\r\n private readonly _friendlyName: string,\r\n private readonly _parent?: ServiceContainer\r\n ) {\r\n _parent?._children.add(this);\r\n }\r\n\r\n /**\r\n * Adds a set of service definitions in the service container.\r\n * The services are sorted based on their dependencies.\r\n * @param args The service definitions to register, and optionally an abort signal.\r\n * @returns A disposable that will remove the service definition from the service container.\r\n */\r\n public async addServicesAsync(\r\n ...args: WeaklyTypedServiceDefinition[] | [...serviceDefinitions: WeaklyTypedServiceDefinition[], abortSignal: AbortSignal]\r\n ): Promise<IDisposable> {\r\n if (this._isDisposed) {\r\n throw new Error(\"ServiceContainer is disposed.\");\r\n }\r\n\r\n const abortSignal = args[args.length - 1] instanceof AbortSignal ? (args.pop() as AbortSignal) : undefined;\r\n const serviceDefinitions = args as WeaklyTypedServiceDefinition[];\r\n\r\n const sortedServiceDefinitions = SortServiceDefinitions(serviceDefinitions);\r\n\r\n const dispose = () => {\r\n for (const serviceDefinition of sortedServiceDefinitions.reverse()) {\r\n this._removeService(serviceDefinition);\r\n }\r\n };\r\n\r\n try {\r\n for (const serviceDefinition of sortedServiceDefinitions) {\r\n // We could possibly optimize this by allowing some parallel initialization of services, but this would be way more complex, so let's wait and see if it's needed.\r\n // eslint-disable-next-line no-await-in-loop\r\n await this._addServiceAsync(serviceDefinition, abortSignal);\r\n }\r\n } catch (error: unknown) {\r\n dispose();\r\n throw error;\r\n }\r\n\r\n return {\r\n dispose,\r\n };\r\n }\r\n\r\n /**\r\n * Registers a service definition in the service container.\r\n * @param serviceDefinition The service definition to register.\r\n * @param abortSignal An optional abort signal.\r\n * @returns A disposable that will remove the service definition from the service container.\r\n */\r\n public async addServiceAsync<Produces extends IService<symbol>[] = [], Consumes extends IService<symbol>[] = []>(\r\n serviceDefinition: ServiceDefinition<Produces, Consumes>,\r\n abortSignal?: AbortSignal\r\n ): Promise<IDisposable> {\r\n if (abortSignal) {\r\n return await this.addServicesAsync(serviceDefinition, abortSignal);\r\n } else {\r\n return await this.addServicesAsync(serviceDefinition);\r\n }\r\n }\r\n\r\n private async _addServiceAsync(service: WeaklyTypedServiceDefinition, abortSignal?: AbortSignal) {\r\n if (this._isDisposed) {\r\n throw new Error(`'${this._friendlyName}' container is disposed.`);\r\n }\r\n\r\n service.produces?.forEach((contract) => {\r\n if (this._serviceDefinitions.has(contract)) {\r\n throw new Error(`A service producing the contract '${contract.toString()}' has already been added to this '${this._friendlyName}' container.`);\r\n }\r\n });\r\n\r\n service.produces?.forEach((contract) => {\r\n this._serviceDefinitions.set(contract, service);\r\n });\r\n\r\n const dependencies = service.consumes?.map((contract) => this._resolveDependency(contract, service)) ?? [];\r\n\r\n this._serviceInstances.set(service, await service.factory(...dependencies, abortSignal));\r\n }\r\n\r\n /**\r\n * Resolves a dependency by contract identity for a consuming service.\r\n * Checks local services first, then walks up the parent chain.\r\n * Registers the consumer as a dependent in whichever container owns the dependency.\r\n * @param contract The contract identity to resolve.\r\n * @param consumer The service definition that consumes this dependency.\r\n * @returns The resolved service instance.\r\n */\r\n private _resolveDependency(contract: symbol, consumer: WeaklyTypedServiceDefinition): IService<symbol> & Partial<IDisposable> {\r\n const definition = this._serviceDefinitions.get(contract);\r\n if (definition) {\r\n let dependentDefinitions = this._serviceDependents.get(definition);\r\n if (!dependentDefinitions) {\r\n this._serviceDependents.set(definition, (dependentDefinitions = new Set()));\r\n }\r\n dependentDefinitions.add(consumer);\r\n\r\n const instance = this._serviceInstances.get(definition);\r\n if (!instance) {\r\n throw new Error(`Service '${contract.toString()}' has not been instantiated in the '${this._friendlyName}' container.`);\r\n }\r\n return instance;\r\n }\r\n\r\n if (this._parent) {\r\n return this._parent._resolveDependency(contract, consumer);\r\n }\r\n\r\n throw new Error(`Service '${contract.toString()}' has not been registered in the '${this._friendlyName}' container.`);\r\n }\r\n\r\n /**\r\n * Removes a consumer from the dependent set for a given contract, checking locally first then the parent chain.\r\n * @param contract The contract identity.\r\n * @param consumer The service definition to remove as a dependent.\r\n */\r\n private _removeDependentFromChain(contract: symbol, consumer: WeaklyTypedServiceDefinition): void {\r\n const definition = this._serviceDefinitions.get(contract);\r\n if (definition) {\r\n const dependentDefinitions = this._serviceDependents.get(definition);\r\n if (dependentDefinitions) {\r\n dependentDefinitions.delete(consumer);\r\n if (dependentDefinitions.size === 0) {\r\n this._serviceDependents.delete(definition);\r\n }\r\n }\r\n return;\r\n }\r\n\r\n this._parent?._removeDependentFromChain(contract, consumer);\r\n }\r\n\r\n private _removeService(service: WeaklyTypedServiceDefinition) {\r\n if (this._isDisposed) {\r\n throw new Error(`'${this._friendlyName}' container is disposed.`);\r\n }\r\n\r\n const serviceDependents = this._serviceDependents.get(service);\r\n if (serviceDependents && serviceDependents.size > 0) {\r\n throw new Error(\r\n `Service '${service.friendlyName}' has dependents: ${Array.from(serviceDependents)\r\n .map((dependent) => dependent.friendlyName)\r\n .join(\", \")}`\r\n );\r\n }\r\n\r\n // NOTE: The service instance could be undefined, as the service factory for a service that does not produce any contracts is not required to return an actual service instance.\r\n const serviceInstance = this._serviceInstances.get(service);\r\n this._serviceInstances.delete(service);\r\n serviceInstance?.dispose?.();\r\n\r\n service.produces?.forEach((contract) => {\r\n this._serviceDefinitions.delete(contract);\r\n });\r\n\r\n // Remove this service as a dependent from each of its consumed dependencies (local or in parent chain).\r\n service.consumes?.forEach((contract) => {\r\n this._removeDependentFromChain(contract, service);\r\n });\r\n }\r\n\r\n /**\r\n * Disposes the service container and all contained services.\r\n * Throws if this container is still a parent of any live child containers.\r\n */\r\n public dispose() {\r\n if (this._children.size > 0) {\r\n throw new Error(`'${this._friendlyName}' container cannot be disposed because it has ${this._children.size} active child container(s).`);\r\n }\r\n\r\n Array.from(this._serviceInstances.keys()).reverse().forEach(this._removeService.bind(this));\r\n this._serviceInstances.clear();\r\n this._serviceDependents.clear();\r\n this._serviceDefinitions.clear();\r\n this._parent?._children.delete(this);\r\n this._isDisposed = true;\r\n }\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"serviceContainer.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/modularTool/modularity/serviceContainer.ts"],"names":[],"mappings":"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","sourcesContent":["import { type IDisposable } from \"core/index\";\r\n\r\nimport { type IService, type ServiceDefinition } from \"./serviceDefinition\";\r\n\r\nimport { SortGraph } from \"../misc/graphUtils\";\r\n\r\n// Service definitions should strongly typed when they are defined to avoid programming errors. However, they are tracked internally\r\n// in a weakly typed manner so they can be handled generically.\r\nexport type WeaklyTypedServiceDefinition = Omit<ServiceDefinition<IService<symbol>[] | [], IService<symbol>[] | []>, \"factory\"> & {\r\n /**\r\n * A factory function responsible for creating a service instance.\r\n */\r\n factory: (...args: any) => ReturnType<ServiceDefinition<IService<symbol>[] | [], IService<symbol>[] | []>[\"factory\"]>;\r\n};\r\n\r\n// This sorts a set of service definitions based on their dependencies (e.g. a topological sort).\r\nfunction SortServiceDefinitions(serviceDefinitions: WeaklyTypedServiceDefinition[]) {\r\n const sortedServiceDefinitions: typeof serviceDefinitions = [];\r\n SortGraph(\r\n serviceDefinitions,\r\n function* (serviceDefinition) {\r\n // Check each dependency.\r\n for (const contractIdentity of serviceDefinition.consumes ?? []) {\r\n // If another service definition produces the dependency contract, return it as an adjacent node for the purpose of sorting.\r\n yield* serviceDefinitions.filter((otherServiceDefinition) => (otherServiceDefinition.produces ?? []).includes(contractIdentity));\r\n }\r\n },\r\n sortedServiceDefinitions.push.bind(sortedServiceDefinitions)\r\n );\r\n return sortedServiceDefinitions;\r\n}\r\n\r\n/**\r\n * A service container manages the lifetimes of a set of services.\r\n * It takes care of instantiating the services in the correct order based on their dependencies,\r\n * passing dependencies through to services, and disposing of services when the container is disposed.\r\n */\r\nexport class ServiceContainer implements IDisposable {\r\n private _isDisposed = false;\r\n private readonly _serviceDefinitions = new Map<symbol, WeaklyTypedServiceDefinition>();\r\n private readonly _serviceDependents = new Map<WeaklyTypedServiceDefinition, Set<WeaklyTypedServiceDefinition>>();\r\n private readonly _serviceInstances = new Map<WeaklyTypedServiceDefinition, (IService<symbol> & Partial<IDisposable>) | void>();\r\n private readonly _children = new Set<ServiceContainer>();\r\n\r\n /**\r\n * Creates a new ServiceContainer.\r\n * @param _friendlyName A human-readable name for debugging.\r\n * @param _parent An optional parent container. Dependencies not found locally will be resolved from the parent.\r\n */\r\n public constructor(\r\n private readonly _friendlyName: string,\r\n private readonly _parent?: ServiceContainer\r\n ) {\r\n _parent?._children.add(this);\r\n }\r\n\r\n /**\r\n * Adds a set of service definitions to the service container.\r\n * The services are sorted based on their dependencies.\r\n * @param serviceDefinitions The service definitions to register.\r\n * @returns A disposable that will remove the service definitions from the service container.\r\n */\r\n public addServices(...serviceDefinitions: WeaklyTypedServiceDefinition[]): IDisposable {\r\n if (this._isDisposed) {\r\n throw new Error(\"ServiceContainer is disposed.\");\r\n }\r\n\r\n const sortedServiceDefinitions = SortServiceDefinitions(serviceDefinitions);\r\n\r\n const dispose = () => {\r\n for (const serviceDefinition of sortedServiceDefinitions.reverse()) {\r\n this._removeService(serviceDefinition);\r\n }\r\n };\r\n\r\n try {\r\n for (const serviceDefinition of sortedServiceDefinitions) {\r\n this._addService(serviceDefinition);\r\n }\r\n } catch (error: unknown) {\r\n dispose();\r\n throw error;\r\n }\r\n\r\n return {\r\n dispose,\r\n };\r\n }\r\n\r\n /**\r\n * Registers a service definition in the service container.\r\n * @param serviceDefinition The service definition to register.\r\n * @returns A disposable that will remove the service definition from the service container.\r\n */\r\n public addService<Produces extends IService<symbol>[] = [], Consumes extends IService<symbol>[] = []>(serviceDefinition: ServiceDefinition<Produces, Consumes>): IDisposable {\r\n return this.addServices(serviceDefinition);\r\n }\r\n\r\n private _addService(service: WeaklyTypedServiceDefinition) {\r\n if (this._isDisposed) {\r\n throw new Error(`'${this._friendlyName}' container is disposed.`);\r\n }\r\n\r\n service.produces?.forEach((contract) => {\r\n if (this._serviceDefinitions.has(contract)) {\r\n throw new Error(`A service producing the contract '${contract.toString()}' has already been added to this '${this._friendlyName}' container.`);\r\n }\r\n });\r\n\r\n service.produces?.forEach((contract) => {\r\n this._serviceDefinitions.set(contract, service);\r\n });\r\n\r\n const dependencies = service.consumes?.map((contract) => this._resolveDependency(contract, service)) ?? [];\r\n\r\n this._serviceInstances.set(service, service.factory(...dependencies));\r\n }\r\n\r\n /**\r\n * Resolves a dependency by contract identity for a consuming service.\r\n * Checks local services first, then walks up the parent chain.\r\n * Registers the consumer as a dependent in whichever container owns the dependency.\r\n * @param contract The contract identity to resolve.\r\n * @param consumer The service definition that consumes this dependency.\r\n * @returns The resolved service instance.\r\n */\r\n private _resolveDependency(contract: symbol, consumer: WeaklyTypedServiceDefinition): IService<symbol> & Partial<IDisposable> {\r\n const definition = this._serviceDefinitions.get(contract);\r\n if (definition) {\r\n const instance = this._serviceInstances.get(definition);\r\n if (!instance) {\r\n throw new Error(`Service '${contract.toString()}' has not been instantiated in the '${this._friendlyName}' container.`);\r\n }\r\n\r\n let dependentDefinitions = this._serviceDependents.get(definition);\r\n if (!dependentDefinitions) {\r\n this._serviceDependents.set(definition, (dependentDefinitions = new Set()));\r\n }\r\n dependentDefinitions.add(consumer);\r\n\r\n return instance;\r\n }\r\n\r\n if (this._parent) {\r\n return this._parent._resolveDependency(contract, consumer);\r\n }\r\n\r\n throw new Error(`Service '${contract.toString()}' has not been registered in the '${this._friendlyName}' container.`);\r\n }\r\n\r\n /**\r\n * Removes a consumer from the dependent set for a given contract, checking locally first then the parent chain.\r\n * @param contract The contract identity.\r\n * @param consumer The service definition to remove as a dependent.\r\n */\r\n private _removeDependentFromChain(contract: symbol, consumer: WeaklyTypedServiceDefinition): void {\r\n const definition = this._serviceDefinitions.get(contract);\r\n if (definition) {\r\n const dependentDefinitions = this._serviceDependents.get(definition);\r\n if (dependentDefinitions) {\r\n dependentDefinitions.delete(consumer);\r\n if (dependentDefinitions.size === 0) {\r\n this._serviceDependents.delete(definition);\r\n }\r\n }\r\n return;\r\n }\r\n\r\n this._parent?._removeDependentFromChain(contract, consumer);\r\n }\r\n\r\n private _removeService(service: WeaklyTypedServiceDefinition) {\r\n if (this._isDisposed) {\r\n throw new Error(`'${this._friendlyName}' container is disposed.`);\r\n }\r\n\r\n const serviceDependents = this._serviceDependents.get(service);\r\n if (serviceDependents && serviceDependents.size > 0) {\r\n throw new Error(\r\n `Service '${service.friendlyName}' has dependents: ${Array.from(serviceDependents)\r\n .map((dependent) => dependent.friendlyName)\r\n .join(\", \")}`\r\n );\r\n }\r\n\r\n // NOTE: The service instance could be undefined, as the service factory for a service that does not produce any contracts is not required to return an actual service instance.\r\n const serviceInstance = this._serviceInstances.get(service);\r\n this._serviceInstances.delete(service);\r\n serviceInstance?.dispose?.();\r\n\r\n service.produces?.forEach((contract) => {\r\n this._serviceDefinitions.delete(contract);\r\n });\r\n\r\n // Remove this service as a dependent from each of its consumed dependencies (local or in parent chain).\r\n service.consumes?.forEach((contract) => {\r\n this._removeDependentFromChain(contract, service);\r\n });\r\n }\r\n\r\n /**\r\n * Disposes the service container and all contained services.\r\n * Throws if this container is still a parent of any live child containers.\r\n */\r\n public dispose() {\r\n if (this._children.size > 0) {\r\n throw new Error(`'${this._friendlyName}' container cannot be disposed because it has ${this._children.size} active child container(s).`);\r\n }\r\n\r\n Array.from(this._serviceInstances.keys()).reverse().forEach(this._removeService.bind(this));\r\n this._serviceInstances.clear();\r\n this._serviceDependents.clear();\r\n this._serviceDefinitions.clear();\r\n this._parent?._children.delete(this);\r\n this._isDisposed = true;\r\n }\r\n}\r\n"]}
|
|
@@ -20,14 +20,13 @@ type ExtractContractIdentities<ServiceContracts extends IService<symbol>[]> = {
|
|
|
20
20
|
[Index in keyof ServiceContracts]: ExtractContractIdentity<ServiceContracts[Index]>;
|
|
21
21
|
};
|
|
22
22
|
type UnionToIntersection<Union> = (Union extends any ? (k: Union) => void : never) extends (k: infer Intersection) => void ? Intersection : never;
|
|
23
|
-
type MaybePromise<T> = T | Promise<T>;
|
|
24
23
|
/**
|
|
25
24
|
* A factory function responsible for creating a service instance.
|
|
26
25
|
* Consumed services are passed as arguments to the factory function.
|
|
27
|
-
* The returned value must implement all produced services, and may IDisposable.
|
|
28
|
-
* If
|
|
26
|
+
* The returned value must implement all produced services, and may implement IDisposable.
|
|
27
|
+
* If no services are produced, the returned value may implement IDisposable, otherwise it may return void.
|
|
29
28
|
*/
|
|
30
|
-
export type ServiceFactory<Produces extends IService<symbol>[], Consumes extends IService<symbol>[]> = (...dependencies: [...Consumes
|
|
29
|
+
export type ServiceFactory<Produces extends IService<symbol>[], Consumes extends IService<symbol>[]> = (...dependencies: [...Consumes]) => Produces extends [] ? Partial<IDisposable> | void : Partial<IDisposable> & UnionToIntersection<Produces[number]>;
|
|
31
30
|
/**
|
|
32
31
|
* Defines a service, which is a logical unit that consumes other services (dependencies), and optionally produces services that can be consumed by other services (dependents).
|
|
33
32
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serviceDefinition.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/modularTool/modularity/serviceDefinition.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAA0C,WAAkB;IAC1F,OAAO,CAAC,GAAG,IAAkC,EAAE,EAAE,CAAC,IAAI,WAAW,CAAC,GAAG,IAAI,CAAC,CAAC;AAC/E,CAAC;AAED,6EAA6E;AAC7E,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC","sourcesContent":["import { type IDisposable } from \"core/index\";\r\n\r\n/**\r\n * A helper to create a service factory function from a class constructor.\r\n * @param constructor The class to create a factory function for.\r\n * @returns A factory function that creates an instance of the class.\r\n */\r\nexport function ConstructorFactory<Class extends new (...args: any) => any>(constructor: Class): (...args: ConstructorParameters<Class>) => InstanceType<Class> {\r\n return (...args: ConstructorParameters<Class>) => new constructor(...args);\r\n}\r\n\r\n// This allows us to map from a service contract back to a contract identity.\r\nconst Contract = Symbol();\r\n/**\r\n * This interface must be implemented by all service contracts.\r\n */\r\nexport interface IService<ContractIdentity extends symbol> {\r\n /**\r\n * @internal\r\n */\r\n readonly [Contract]?: ContractIdentity;\r\n}\r\n\r\n// This utility type extracts the service identity (unique symbol type) from a service contract. That is, it extracts T from IService<T>.\r\ntype ExtractContractIdentity<ServiceContract extends IService<symbol>> = ServiceContract extends IService<infer ContractIdentity> ? ContractIdentity : never;\r\n\r\n// This utility type extracts the contract identities from an array of service contracts. That is, it extracts [T1, T2, ...] from [IService<T1>, IService<T2>, ...].\r\ntype ExtractContractIdentities<ServiceContracts extends IService<symbol>[]> = {\r\n [Index in keyof ServiceContracts]: ExtractContractIdentity<ServiceContracts[Index]>;\r\n};\r\n\r\n// This utility type converts a union of types into an intersection of types. That is, it converts T1 | T2 | ... into T1 & T2 & ...\r\n// This is specifically used to determine the type that a service factory function returns when it produces multiple services.\r\ntype UnionToIntersection<Union> = (Union extends any ? (k: Union) => void : never) extends (k: infer Intersection) => void ? Intersection : never;\r\n\r\
|
|
1
|
+
{"version":3,"file":"serviceDefinition.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/modularTool/modularity/serviceDefinition.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAA0C,WAAkB;IAC1F,OAAO,CAAC,GAAG,IAAkC,EAAE,EAAE,CAAC,IAAI,WAAW,CAAC,GAAG,IAAI,CAAC,CAAC;AAC/E,CAAC;AAED,6EAA6E;AAC7E,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC","sourcesContent":["import { type IDisposable } from \"core/index\";\r\n\r\n/**\r\n * A helper to create a service factory function from a class constructor.\r\n * @param constructor The class to create a factory function for.\r\n * @returns A factory function that creates an instance of the class.\r\n */\r\nexport function ConstructorFactory<Class extends new (...args: any) => any>(constructor: Class): (...args: ConstructorParameters<Class>) => InstanceType<Class> {\r\n return (...args: ConstructorParameters<Class>) => new constructor(...args);\r\n}\r\n\r\n// This allows us to map from a service contract back to a contract identity.\r\nconst Contract = Symbol();\r\n/**\r\n * This interface must be implemented by all service contracts.\r\n */\r\nexport interface IService<ContractIdentity extends symbol> {\r\n /**\r\n * @internal\r\n */\r\n readonly [Contract]?: ContractIdentity;\r\n}\r\n\r\n// This utility type extracts the service identity (unique symbol type) from a service contract. That is, it extracts T from IService<T>.\r\ntype ExtractContractIdentity<ServiceContract extends IService<symbol>> = ServiceContract extends IService<infer ContractIdentity> ? ContractIdentity : never;\r\n\r\n// This utility type extracts the contract identities from an array of service contracts. That is, it extracts [T1, T2, ...] from [IService<T1>, IService<T2>, ...].\r\ntype ExtractContractIdentities<ServiceContracts extends IService<symbol>[]> = {\r\n [Index in keyof ServiceContracts]: ExtractContractIdentity<ServiceContracts[Index]>;\r\n};\r\n\r\n// This utility type converts a union of types into an intersection of types. That is, it converts T1 | T2 | ... into T1 & T2 & ...\r\n// This is specifically used to determine the type that a service factory function returns when it produces multiple services.\r\ntype UnionToIntersection<Union> = (Union extends any ? (k: Union) => void : never) extends (k: infer Intersection) => void ? Intersection : never;\r\n\r\n/**\r\n * A factory function responsible for creating a service instance.\r\n * Consumed services are passed as arguments to the factory function.\r\n * The returned value must implement all produced services, and may implement IDisposable.\r\n * If no services are produced, the returned value may implement IDisposable, otherwise it may return void.\r\n */\r\nexport type ServiceFactory<Produces extends IService<symbol>[], Consumes extends IService<symbol>[]> = (\r\n ...dependencies: [...Consumes]\r\n) => Produces extends [] ? Partial<IDisposable> | void : Partial<IDisposable> & UnionToIntersection<Produces[number]>;\r\n\r\n/**\r\n * Defines a service, which is a logical unit that consumes other services (dependencies), and optionally produces services that can be consumed by other services (dependents).\r\n */\r\nexport type ServiceDefinition<Produces extends IService<symbol>[] = [], Consumes extends IService<symbol>[] = []> = {\r\n /**\r\n * A human readable name for the service to help with debugging.\r\n */\r\n friendlyName: string;\r\n\r\n /**\r\n * A function that instantiates the service.\r\n */\r\n factory: ServiceFactory<Produces, Consumes>;\r\n} & (Produces extends []\r\n ? {\r\n /**\r\n * An empty list or undefined, since the type specification has indicated no contracts are produced.\r\n */\r\n produces?: [];\r\n }\r\n : {\r\n /**\r\n * The list of contract identities that this service produces for consumption by other services.\r\n */\r\n produces: ExtractContractIdentities<Produces>;\r\n }) &\r\n (Consumes extends []\r\n ? {\r\n /**\r\n * An empty list or undefined, since the type specification has indicated that no other services are consumed.\r\n */\r\n consumes?: [];\r\n }\r\n : {\r\n /**\r\n * The list of contract identities of other services that this service consumes.\r\n */\r\n consumes: ExtractContractIdentities<Consumes>;\r\n });\r\n"]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type IDisposable } from "@onerjs/core/index.js";
|
|
2
|
+
import { type IService } from "../../modularity/serviceDefinition.js";
|
|
3
|
+
/**
|
|
4
|
+
* The type of a bridge command argument, which determines how
|
|
5
|
+
* the CLI processes the value before sending it to the browser.
|
|
6
|
+
* @experimental
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export type BridgeCommandArgType = "string" | "file";
|
|
10
|
+
/**
|
|
11
|
+
* Describes an argument for a bridge command.
|
|
12
|
+
* @experimental
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export type BridgeCommandArg = {
|
|
16
|
+
/**
|
|
17
|
+
* The name of the argument.
|
|
18
|
+
*/
|
|
19
|
+
name: string;
|
|
20
|
+
/**
|
|
21
|
+
* A description of the argument.
|
|
22
|
+
*/
|
|
23
|
+
description: string;
|
|
24
|
+
/**
|
|
25
|
+
* Whether the argument is required.
|
|
26
|
+
*/
|
|
27
|
+
required?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* The type of the argument. Defaults to "string".
|
|
30
|
+
* When set to "file", the CLI reads the file at the given path
|
|
31
|
+
* and passes its contents as the argument value.
|
|
32
|
+
*/
|
|
33
|
+
type?: BridgeCommandArgType;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Describes a command that can be invoked from the bridge.
|
|
37
|
+
* @experimental
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
export type BridgeCommandDescriptor = {
|
|
41
|
+
/**
|
|
42
|
+
* A unique identifier for the command.
|
|
43
|
+
*/
|
|
44
|
+
id: string;
|
|
45
|
+
/**
|
|
46
|
+
* A human-readable description of what the command does.
|
|
47
|
+
*/
|
|
48
|
+
description: string;
|
|
49
|
+
/**
|
|
50
|
+
* The arguments that this command accepts.
|
|
51
|
+
*/
|
|
52
|
+
args?: BridgeCommandArg[];
|
|
53
|
+
/**
|
|
54
|
+
* Executes the command with the given arguments and returns a result string.
|
|
55
|
+
* @param args A map of argument names to their values.
|
|
56
|
+
* @returns A promise that resolves to the result string.
|
|
57
|
+
*/
|
|
58
|
+
executeAsync: (args: Record<string, string>) => Promise<string>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* The service identity for the bridge command registry.
|
|
62
|
+
* @experimental
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
export declare const BridgeCommandRegistryIdentity: unique symbol;
|
|
66
|
+
/**
|
|
67
|
+
* A registry for commands that can be invoked from the bridge.
|
|
68
|
+
* @experimental
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
export interface IBridgeCommandRegistry extends IService<typeof BridgeCommandRegistryIdentity> {
|
|
72
|
+
/**
|
|
73
|
+
* Registers a command that can be invoked from the bridge.
|
|
74
|
+
* @param descriptor The command descriptor.
|
|
75
|
+
* @returns A disposable token that unregisters the command when disposed.
|
|
76
|
+
*/
|
|
77
|
+
addCommand(descriptor: BridgeCommandDescriptor): IDisposable;
|
|
78
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridgeCommandRegistry.js","sourceRoot":"","sources":["../../../../../../dev/sharedUiComponents/src/modularTool/services/cli/bridgeCommandRegistry.ts"],"names":[],"mappings":"AAqEA;;;;GAIG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,MAAM,CAAC,uBAAuB,CAAC,CAAC","sourcesContent":["import { type IDisposable } from \"core/index\";\r\nimport { type IService } from \"../../modularity/serviceDefinition\";\r\n\r\n/**\r\n * The type of a bridge command argument, which determines how\r\n * the CLI processes the value before sending it to the browser.\r\n * @experimental\r\n * @internal\r\n */\r\nexport type BridgeCommandArgType = \"string\" | \"file\";\r\n\r\n/**\r\n * Describes an argument for a bridge command.\r\n * @experimental\r\n * @internal\r\n */\r\nexport type BridgeCommandArg = {\r\n /**\r\n * The name of the argument.\r\n */\r\n name: string;\r\n\r\n /**\r\n * A description of the argument.\r\n */\r\n description: string;\r\n\r\n /**\r\n * Whether the argument is required.\r\n */\r\n required?: boolean;\r\n\r\n /**\r\n * The type of the argument. Defaults to \"string\".\r\n * When set to \"file\", the CLI reads the file at the given path\r\n * and passes its contents as the argument value.\r\n */\r\n type?: BridgeCommandArgType;\r\n};\r\n\r\n/**\r\n * Describes a command that can be invoked from the bridge.\r\n * @experimental\r\n * @internal\r\n */\r\nexport type BridgeCommandDescriptor = {\r\n /**\r\n * A unique identifier for the command.\r\n */\r\n id: string;\r\n\r\n /**\r\n * A human-readable description of what the command does.\r\n */\r\n description: string;\r\n\r\n /**\r\n * The arguments that this command accepts.\r\n */\r\n args?: BridgeCommandArg[];\r\n\r\n /**\r\n * Executes the command with the given arguments and returns a result string.\r\n * @param args A map of argument names to their values.\r\n * @returns A promise that resolves to the result string.\r\n */\r\n executeAsync: (args: Record<string, string>) => Promise<string>;\r\n};\r\n\r\n/**\r\n * The service identity for the bridge command registry.\r\n * @experimental\r\n * @internal\r\n */\r\nexport const BridgeCommandRegistryIdentity = Symbol(\"BridgeCommandRegistry\");\r\n\r\n/**\r\n * A registry for commands that can be invoked from the bridge.\r\n * @experimental\r\n * @internal\r\n */\r\nexport interface IBridgeCommandRegistry extends IService<typeof BridgeCommandRegistryIdentity> {\r\n /**\r\n * Registers a command that can be invoked from the bridge.\r\n * @param descriptor The command descriptor.\r\n * @returns A disposable token that unregisters the command when disposed.\r\n */\r\n addCommand(descriptor: BridgeCommandDescriptor): IDisposable;\r\n}\r\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type IReadonlyObservable } from "@onerjs/core/index.js";
|
|
2
|
+
import { type IService } from "../../modularity/serviceDefinition.js";
|
|
3
|
+
/**
|
|
4
|
+
* The service identity for the CLI connection status.
|
|
5
|
+
* @experimental
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export declare const CliConnectionStatusIdentity: unique symbol;
|
|
9
|
+
/**
|
|
10
|
+
* Provides the connection status and enable/disable control for the CLI bridge.
|
|
11
|
+
* @experimental
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export interface ICliConnectionStatus extends IService<typeof CliConnectionStatusIdentity> {
|
|
15
|
+
/**
|
|
16
|
+
* Whether the bridge is enabled. When true, the bridge actively tries to
|
|
17
|
+
* maintain a WebSocket connection. When false, the bridge is disconnected
|
|
18
|
+
* and idle.
|
|
19
|
+
*/
|
|
20
|
+
isEnabled: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Whether the bridge WebSocket is currently connected.
|
|
23
|
+
*/
|
|
24
|
+
readonly isConnected: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Observable that fires when either {@link isEnabled} or {@link isConnected} changes.
|
|
27
|
+
*/
|
|
28
|
+
readonly onConnectionStatusChanged: IReadonlyObservable<void>;
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridgeConnectionStatus.js","sourceRoot":"","sources":["../../../../../../dev/sharedUiComponents/src/modularTool/services/cli/bridgeConnectionStatus.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG,MAAM,CAAC,qBAAqB,CAAC,CAAC","sourcesContent":["import { type IReadonlyObservable } from \"core/index\";\r\nimport { type IService } from \"../../modularity/serviceDefinition\";\r\n\r\n/**\r\n * The service identity for the CLI connection status.\r\n * @experimental\r\n * @internal\r\n */\r\nexport const CliConnectionStatusIdentity = Symbol(\"CliConnectionStatus\");\r\n\r\n/**\r\n * Provides the connection status and enable/disable control for the CLI bridge.\r\n * @experimental\r\n * @internal\r\n */\r\nexport interface ICliConnectionStatus extends IService<typeof CliConnectionStatusIdentity> {\r\n /**\r\n * Whether the bridge is enabled. When true, the bridge actively tries to\r\n * maintain a WebSocket connection. When false, the bridge is disconnected\r\n * and idle.\r\n */\r\n isEnabled: boolean;\r\n\r\n /**\r\n * Whether the bridge WebSocket is currently connected.\r\n */\r\n readonly isConnected: boolean;\r\n\r\n /**\r\n * Observable that fires when either {@link isEnabled} or {@link isConnected} changes.\r\n */\r\n readonly onConnectionStatusChanged: IReadonlyObservable<void>;\r\n}\r\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ServiceDefinition } from "../../modularity/serviceDefinition.js";
|
|
2
|
+
import { type ICliConnectionStatus } from "./bridgeConnectionStatus.js";
|
|
3
|
+
import { type IBridgeCommandRegistry } from "./bridgeCommandRegistry.js";
|
|
4
|
+
/**
|
|
5
|
+
* Options for the CLI bridge service.
|
|
6
|
+
* @experimental
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export type BridgeServiceOptions = {
|
|
10
|
+
/**
|
|
11
|
+
* The WebSocket port for the bridge's browser port.
|
|
12
|
+
*/
|
|
13
|
+
port: number;
|
|
14
|
+
/**
|
|
15
|
+
* The session display name sent to the bridge.
|
|
16
|
+
* Can be a getter to provide a dynamic value that is re-read
|
|
17
|
+
* each time the bridge queries session information.
|
|
18
|
+
*/
|
|
19
|
+
name: string;
|
|
20
|
+
/**
|
|
21
|
+
* Whether to automatically start connecting when the service is created.
|
|
22
|
+
*/
|
|
23
|
+
autoStart: boolean;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Creates the service definition for the CLI Bridge Service.
|
|
27
|
+
* @param options The options for connecting to the bridge.
|
|
28
|
+
* @returns A service definition that produces an IBridgeCommandRegistry and ICliConnectionStatus.
|
|
29
|
+
* @experimental
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export declare function MakeBridgeServiceDefinition(options: BridgeServiceOptions): ServiceDefinition<[IBridgeCommandRegistry, ICliConnectionStatus], []>;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { Observable } from "@onerjs/core/Misc/observable.js";
|
|
2
|
+
import { CliConnectionStatusIdentity } from "./bridgeConnectionStatus.js";
|
|
3
|
+
import { BridgeCommandRegistryIdentity } from "./bridgeCommandRegistry.js";
|
|
4
|
+
import { Logger } from "@onerjs/core/Misc/logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* Creates the service definition for the CLI Bridge Service.
|
|
7
|
+
* @param options The options for connecting to the bridge.
|
|
8
|
+
* @returns A service definition that produces an IBridgeCommandRegistry and ICliConnectionStatus.
|
|
9
|
+
* @experimental
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export function MakeBridgeServiceDefinition(options) {
|
|
13
|
+
return {
|
|
14
|
+
friendlyName: "CLI Bridge Service",
|
|
15
|
+
produces: [BridgeCommandRegistryIdentity, CliConnectionStatusIdentity],
|
|
16
|
+
factory: () => {
|
|
17
|
+
const commands = new Map();
|
|
18
|
+
let ws = null;
|
|
19
|
+
let reconnectTimer = null;
|
|
20
|
+
let disposed = false;
|
|
21
|
+
let enabled = options.autoStart;
|
|
22
|
+
let connected = false;
|
|
23
|
+
const onConnectionStatusChanged = new Observable();
|
|
24
|
+
function notifyStatusChanged() {
|
|
25
|
+
onConnectionStatusChanged.notifyObservers();
|
|
26
|
+
}
|
|
27
|
+
function setConnected(value) {
|
|
28
|
+
if (connected !== value) {
|
|
29
|
+
connected = value;
|
|
30
|
+
notifyStatusChanged();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function sendToBridge(message) {
|
|
34
|
+
ws?.send(JSON.stringify(message));
|
|
35
|
+
}
|
|
36
|
+
function connect() {
|
|
37
|
+
if (disposed || !enabled) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// Close any existing WebSocket to avoid orphaned connections that
|
|
41
|
+
// keep a stale session alive on the bridge.
|
|
42
|
+
if (ws) {
|
|
43
|
+
const oldWs = ws;
|
|
44
|
+
oldWs.onopen = null;
|
|
45
|
+
oldWs.onclose = null;
|
|
46
|
+
oldWs.onmessage = null;
|
|
47
|
+
oldWs.onerror = null;
|
|
48
|
+
oldWs.close();
|
|
49
|
+
ws = null;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
// NOTE: The browser unconditionally logs a console error for failed WebSocket
|
|
53
|
+
// connections at the network level. This cannot be suppressed from JavaScript.
|
|
54
|
+
ws = new WebSocket(`ws://127.0.0.1:${options.port}`);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
ws = null;
|
|
58
|
+
setConnected(false);
|
|
59
|
+
Logger.Warn(`CLIBridgeService: Failed to create WebSocket connection on port ${options.port}.`);
|
|
60
|
+
scheduleReconnect();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
ws.onopen = () => {
|
|
64
|
+
setConnected(true);
|
|
65
|
+
sendToBridge({ type: "register", name: options.name });
|
|
66
|
+
};
|
|
67
|
+
ws.onmessage = (event) => {
|
|
68
|
+
try {
|
|
69
|
+
const message = JSON.parse(event.data);
|
|
70
|
+
void handleMessage(message);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
Logger.Warn("CLIBridgeService: Failed to parse message from bridge.");
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
ws.onclose = () => {
|
|
77
|
+
ws = null;
|
|
78
|
+
setConnected(false);
|
|
79
|
+
scheduleReconnect();
|
|
80
|
+
};
|
|
81
|
+
ws.onerror = () => {
|
|
82
|
+
// onclose will fire after onerror, which handles reconnection.
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function disconnect() {
|
|
86
|
+
if (reconnectTimer !== null) {
|
|
87
|
+
clearTimeout(reconnectTimer);
|
|
88
|
+
reconnectTimer = null;
|
|
89
|
+
}
|
|
90
|
+
if (ws) {
|
|
91
|
+
ws.onclose = null;
|
|
92
|
+
ws.close();
|
|
93
|
+
ws = null;
|
|
94
|
+
}
|
|
95
|
+
setConnected(false);
|
|
96
|
+
}
|
|
97
|
+
function scheduleReconnect() {
|
|
98
|
+
if (disposed || !enabled || reconnectTimer !== null) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
reconnectTimer = setTimeout(() => {
|
|
102
|
+
reconnectTimer = null;
|
|
103
|
+
connect();
|
|
104
|
+
}, 3000);
|
|
105
|
+
}
|
|
106
|
+
async function handleMessage(message) {
|
|
107
|
+
switch (message.type) {
|
|
108
|
+
case "listCommands": {
|
|
109
|
+
const commandList = Array.from(commands.values()).map((cmd) => ({
|
|
110
|
+
id: cmd.id,
|
|
111
|
+
description: cmd.description,
|
|
112
|
+
args: cmd.args,
|
|
113
|
+
}));
|
|
114
|
+
sendToBridge({
|
|
115
|
+
type: "commandListResponse",
|
|
116
|
+
requestId: message.requestId,
|
|
117
|
+
commands: commandList,
|
|
118
|
+
});
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case "getInfo": {
|
|
122
|
+
sendToBridge({
|
|
123
|
+
type: "infoResponse",
|
|
124
|
+
requestId: message.requestId,
|
|
125
|
+
name: options.name,
|
|
126
|
+
});
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case "execCommand": {
|
|
130
|
+
const command = commands.get(message.commandId);
|
|
131
|
+
if (!command) {
|
|
132
|
+
sendToBridge({
|
|
133
|
+
type: "commandResponse",
|
|
134
|
+
requestId: message.requestId,
|
|
135
|
+
error: `Unknown command: ${message.commandId}`,
|
|
136
|
+
});
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const result = await command.executeAsync(message.args);
|
|
141
|
+
sendToBridge({
|
|
142
|
+
type: "commandResponse",
|
|
143
|
+
requestId: message.requestId,
|
|
144
|
+
result,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
sendToBridge({
|
|
149
|
+
type: "commandResponse",
|
|
150
|
+
requestId: message.requestId,
|
|
151
|
+
error: String(error),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (enabled) {
|
|
159
|
+
connect();
|
|
160
|
+
}
|
|
161
|
+
const registry = {
|
|
162
|
+
addCommand(descriptor) {
|
|
163
|
+
if (commands.has(descriptor.id)) {
|
|
164
|
+
throw new Error(`Command '${descriptor.id}' is already registered.`);
|
|
165
|
+
}
|
|
166
|
+
commands.set(descriptor.id, descriptor);
|
|
167
|
+
return {
|
|
168
|
+
dispose: () => {
|
|
169
|
+
commands.delete(descriptor.id);
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
get isEnabled() {
|
|
174
|
+
return enabled;
|
|
175
|
+
},
|
|
176
|
+
set isEnabled(value) {
|
|
177
|
+
if (enabled !== value) {
|
|
178
|
+
enabled = value;
|
|
179
|
+
if (enabled) {
|
|
180
|
+
connect();
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
disconnect();
|
|
184
|
+
}
|
|
185
|
+
notifyStatusChanged();
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
get isConnected() {
|
|
189
|
+
return connected;
|
|
190
|
+
},
|
|
191
|
+
onConnectionStatusChanged,
|
|
192
|
+
dispose: () => {
|
|
193
|
+
disposed = true;
|
|
194
|
+
enabled = false;
|
|
195
|
+
disconnect();
|
|
196
|
+
commands.clear();
|
|
197
|
+
onConnectionStatusChanged.clear();
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
return registry;
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=bridgeService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridgeService.js","sourceRoot":"","sources":["../../../../../../dev/sharedUiComponents/src/modularTool/services/cli/bridgeService.ts"],"names":[],"mappings":"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","sourcesContent":["import { type IDisposable } from \"core/index\";\r\nimport { Observable } from \"core/Misc/observable\";\r\nimport { type BrowserRequest, type BrowserResponse, type CommandInfo } from \"./protocol\";\r\nimport { type ServiceDefinition } from \"../../modularity/serviceDefinition\";\r\nimport { type ICliConnectionStatus, CliConnectionStatusIdentity } from \"./bridgeConnectionStatus\";\r\nimport { type IBridgeCommandRegistry, type BridgeCommandDescriptor, BridgeCommandRegistryIdentity } from \"./bridgeCommandRegistry\";\r\nimport { Logger } from \"core/Misc/logger\";\r\n\r\n/**\r\n * Options for the CLI bridge service.\r\n * @experimental\r\n * @internal\r\n */\r\nexport type BridgeServiceOptions = {\r\n /**\r\n * The WebSocket port for the bridge's browser port.\r\n */\r\n port: number;\r\n\r\n /**\r\n * The session display name sent to the bridge.\r\n * Can be a getter to provide a dynamic value that is re-read\r\n * each time the bridge queries session information.\r\n */\r\n name: string;\r\n\r\n /**\r\n * Whether to automatically start connecting when the service is created.\r\n */\r\n autoStart: boolean;\r\n};\r\n\r\n/**\r\n * Creates the service definition for the CLI Bridge Service.\r\n * @param options The options for connecting to the bridge.\r\n * @returns A service definition that produces an IBridgeCommandRegistry and ICliConnectionStatus.\r\n * @experimental\r\n * @internal\r\n */\r\nexport function MakeBridgeServiceDefinition(options: BridgeServiceOptions): ServiceDefinition<[IBridgeCommandRegistry, ICliConnectionStatus], []> {\r\n return {\r\n friendlyName: \"CLI Bridge Service\",\r\n produces: [BridgeCommandRegistryIdentity, CliConnectionStatusIdentity],\r\n factory: () => {\r\n const commands = new Map<string, BridgeCommandDescriptor>();\r\n let ws: WebSocket | null = null;\r\n let reconnectTimer: ReturnType<typeof setTimeout> | null = null;\r\n let disposed = false;\r\n let enabled = options.autoStart;\r\n let connected = false;\r\n const onConnectionStatusChanged = new Observable<void>();\r\n\r\n function notifyStatusChanged() {\r\n onConnectionStatusChanged.notifyObservers();\r\n }\r\n\r\n function setConnected(value: boolean) {\r\n if (connected !== value) {\r\n connected = value;\r\n notifyStatusChanged();\r\n }\r\n }\r\n\r\n function sendToBridge(message: BrowserRequest) {\r\n ws?.send(JSON.stringify(message));\r\n }\r\n\r\n function connect() {\r\n if (disposed || !enabled) {\r\n return;\r\n }\r\n\r\n // Close any existing WebSocket to avoid orphaned connections that\r\n // keep a stale session alive on the bridge.\r\n if (ws) {\r\n const oldWs = ws;\r\n oldWs.onopen = null;\r\n oldWs.onclose = null;\r\n oldWs.onmessage = null;\r\n oldWs.onerror = null;\r\n oldWs.close();\r\n ws = null;\r\n }\r\n\r\n try {\r\n // NOTE: The browser unconditionally logs a console error for failed WebSocket\r\n // connections at the network level. This cannot be suppressed from JavaScript.\r\n ws = new WebSocket(`ws://127.0.0.1:${options.port}`);\r\n } catch {\r\n ws = null;\r\n setConnected(false);\r\n Logger.Warn(`CLIBridgeService: Failed to create WebSocket connection on port ${options.port}.`);\r\n scheduleReconnect();\r\n return;\r\n }\r\n\r\n ws.onopen = () => {\r\n setConnected(true);\r\n sendToBridge({ type: \"register\", name: options.name });\r\n };\r\n\r\n ws.onmessage = (event) => {\r\n try {\r\n const message = JSON.parse(event.data as string);\r\n void handleMessage(message);\r\n } catch {\r\n Logger.Warn(\"CLIBridgeService: Failed to parse message from bridge.\");\r\n }\r\n };\r\n\r\n ws.onclose = () => {\r\n ws = null;\r\n setConnected(false);\r\n scheduleReconnect();\r\n };\r\n\r\n ws.onerror = () => {\r\n // onclose will fire after onerror, which handles reconnection.\r\n };\r\n }\r\n\r\n function disconnect() {\r\n if (reconnectTimer !== null) {\r\n clearTimeout(reconnectTimer);\r\n reconnectTimer = null;\r\n }\r\n if (ws) {\r\n ws.onclose = null;\r\n ws.close();\r\n ws = null;\r\n }\r\n setConnected(false);\r\n }\r\n\r\n function scheduleReconnect() {\r\n if (disposed || !enabled || reconnectTimer !== null) {\r\n return;\r\n }\r\n reconnectTimer = setTimeout(() => {\r\n reconnectTimer = null;\r\n connect();\r\n }, 3000);\r\n }\r\n\r\n async function handleMessage(message: BrowserResponse) {\r\n switch (message.type) {\r\n case \"listCommands\": {\r\n const commandList: CommandInfo[] = Array.from(commands.values()).map((cmd) => ({\r\n id: cmd.id,\r\n description: cmd.description,\r\n args: cmd.args,\r\n }));\r\n sendToBridge({\r\n type: \"commandListResponse\",\r\n requestId: message.requestId,\r\n commands: commandList,\r\n });\r\n break;\r\n }\r\n case \"getInfo\": {\r\n sendToBridge({\r\n type: \"infoResponse\",\r\n requestId: message.requestId,\r\n name: options.name,\r\n });\r\n break;\r\n }\r\n case \"execCommand\": {\r\n const command = commands.get(message.commandId);\r\n if (!command) {\r\n sendToBridge({\r\n type: \"commandResponse\",\r\n requestId: message.requestId,\r\n error: `Unknown command: ${message.commandId}`,\r\n });\r\n break;\r\n }\r\n try {\r\n const result = await command.executeAsync(message.args);\r\n sendToBridge({\r\n type: \"commandResponse\",\r\n requestId: message.requestId,\r\n result,\r\n });\r\n } catch (error: unknown) {\r\n sendToBridge({\r\n type: \"commandResponse\",\r\n requestId: message.requestId,\r\n error: String(error),\r\n });\r\n }\r\n break;\r\n }\r\n }\r\n }\r\n\r\n if (enabled) {\r\n connect();\r\n }\r\n\r\n const registry: IBridgeCommandRegistry & ICliConnectionStatus & IDisposable = {\r\n addCommand(descriptor: BridgeCommandDescriptor): IDisposable {\r\n if (commands.has(descriptor.id)) {\r\n throw new Error(`Command '${descriptor.id}' is already registered.`);\r\n }\r\n commands.set(descriptor.id, descriptor);\r\n return {\r\n dispose: () => {\r\n commands.delete(descriptor.id);\r\n },\r\n };\r\n },\r\n get isEnabled() {\r\n return enabled;\r\n },\r\n set isEnabled(value: boolean) {\r\n if (enabled !== value) {\r\n enabled = value;\r\n if (enabled) {\r\n connect();\r\n } else {\r\n disconnect();\r\n }\r\n notifyStatusChanged();\r\n }\r\n },\r\n get isConnected() {\r\n return connected;\r\n },\r\n onConnectionStatusChanged,\r\n dispose: () => {\r\n disposed = true;\r\n enabled = false;\r\n disconnect();\r\n commands.clear();\r\n onConnectionStatusChanged.clear();\r\n },\r\n };\r\n\r\n return registry;\r\n },\r\n };\r\n}\r\n"]}
|