diff --git a/.gitignore b/.gitignore index 76add87..a02761e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules -dist \ No newline at end of file +dist +docs \ No newline at end of file diff --git a/example/src/index.ts b/example/src/index.ts index 24d6b36..e073fd9 100644 --- a/example/src/index.ts +++ b/example/src/index.ts @@ -8,7 +8,7 @@ import MQTTConnector from "./modules/mqttConnector"; const aas = FileImporter.readAASByPath("../owntest.json"); // Then we create a new broker instance -const broker = new MultiMessageBroker(); +const broker = MultiMessageBroker.getInstance(); // We need to register the connector we want to use. // These will be chosen later if needed. broker.registerInterfaceConnection({ interfaceConnection: MQTTConnector, config: { reconnectPeriod: 1000 }}) diff --git a/lib/package-lock.json b/lib/package-lock.json index d38ec03..5616af2 100644 --- a/lib/package-lock.json +++ b/lib/package-lock.json @@ -15,6 +15,7 @@ "devDependencies": { "@types/node": "^20.8.2", "@types/uuid": "^9.0.6", + "typedoc": "^0.25.3", "typescript": "^5.2.2" } }, @@ -35,6 +36,99 @@ "integrity": "sha512-BT2Krtx4xaO6iwzwMFUYvWBWkV2pr37zD68Vmp1CDV196MzczBRxuEpD6Pr395HAgebC/co7hOphs53r8V7jew==", "dev": true }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shiki": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", + "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", + "dev": true, + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, + "node_modules/typedoc": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.3.tgz", + "integrity": "sha512-Ow8Bo7uY1Lwy7GTmphRIMEo6IOZ+yYUyrc8n5KXIZg1svpqhZSWgni2ZrDhe+wLosFS8yswowUzljTAV/3jmWw==", + "dev": true, + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.1" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x" + } + }, "node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -59,6 +153,18 @@ "bin": { "uuid": "dist/bin/uuid" } + }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true } }, "dependencies": { @@ -79,6 +185,78 @@ "integrity": "sha512-BT2Krtx4xaO6iwzwMFUYvWBWkV2pr37zD68Vmp1CDV196MzczBRxuEpD6Pr395HAgebC/co7hOphs53r8V7jew==", "dev": true }, + "ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, + "marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "shiki": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", + "integrity": "sha512-1gCAYOcmCFONmErGTrS1fjzJLA7MGZmKzrBNX7apqSwhyITJg2O102uFzXUeBxNnEkDA9vHIKLyeKq0V083vIw==", + "dev": true, + "requires": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, + "typedoc": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.3.tgz", + "integrity": "sha512-Ow8Bo7uY1Lwy7GTmphRIMEo6IOZ+yYUyrc8n5KXIZg1svpqhZSWgni2ZrDhe+wLosFS8yswowUzljTAV/3jmWw==", + "dev": true, + "requires": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.1" + } + }, "typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -89,6 +267,18 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + }, + "vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true } } } diff --git a/lib/package.json b/lib/package.json index cf30f78..32a9fbe 100644 --- a/lib/package.json +++ b/lib/package.json @@ -32,6 +32,7 @@ "devDependencies": { "@types/node": "^20.8.2", "@types/uuid": "^9.0.6", + "typedoc": "^0.25.3", "typescript": "^5.2.2" }, "dependencies": { diff --git a/lib/src/AIMCMapper.ts b/lib/src/AIMCMapper.ts index eaf54f6..62e572c 100644 --- a/lib/src/AIMCMapper.ts +++ b/lib/src/AIMCMapper.ts @@ -5,14 +5,29 @@ import Traverser from "./helper/traverser"; import AIDParser from "./parser/AIDParser"; import { SubmodelElementCollection } from "@aas-core-works/aas-core3.0-typescript/dist/types/types"; +/** + * The AIMC Mapper. + * @remarks + * This class is used to map properties t endpoints. + */ export default class AIMCMapper { + /** + * The map. + */ private map: AIMCMap = new Map(); + /** + * Creates the mapper. + * @param env The AAS Environment + */ public constructor(private readonly env: types.Environment) { this.generate(); } + /** + * Generates the map from the AAS Environment. + */ private generate(): void { const aimc = AIMCParser.parse(this.env); if (aimc === null) return; @@ -51,14 +66,29 @@ export default class AIMCMapper { } } + /** + * + * @returns The complete map + */ public getMap(): AIMCMap { return this.map; } + /** + * Get endpoint for a specific Element + * @param element Element + * @returns Endpoint description or undefined if not found + */ public get(element: types.Class): ConnectionConfiguration | undefined { return this.map.get(element); } + /** + * Returns all endpoints for a idShort + * Can return multiple as idShorts are not necessarily unique + * @param idShort idShort of the element + * @returns Endpoints descriptions of elements with that idShort + */ public getByIdShort(idShort: string): ConnectionConfiguration[] { const result = []; for (const [e, cc] of this.map.entries()) { @@ -67,6 +97,11 @@ export default class AIMCMapper { return result; } + /** + * Get an element by its endpoint + * @param path Absolute endpoint path + * @returns Elements that use that path + */ public reverseGet(path: string): types.Class[] { const result = []; diff --git a/lib/src/helper/fileImporter.ts b/lib/src/helper/fileImporter.ts index 0380f03..1727cfb 100644 --- a/lib/src/helper/fileImporter.ts +++ b/lib/src/helper/fileImporter.ts @@ -2,7 +2,18 @@ import { readdirSync, readFileSync } from "fs"; import { types, jsonization } from "@aas-core-works/aas-core3.0-typescript"; +/** + * Helper class to import AAS-Environments from files. + */ export default class FileImporter { + + /** + * Read an AAS-Environment from a file. + * @param path Filepath + * @returns Environment + * @throws Error if file is not a JSON file + * @throws Any IO error on file read operation + */ public static readAASByPath(path: string): types.Environment { if (!path.endsWith(".json")) throw new Error("File must be a JSON file"); @@ -15,6 +26,11 @@ export default class FileImporter { return aasJson.mustValue(); } + /** + * Import all AAS-Environments from a directory. + * @param path Directory path + * @returns Environments + */ public static readAllAASFromPath(path: string): types.Environment[] { return FileImporter.getAllAASFilePaths(path).map(file => { @@ -27,6 +43,11 @@ export default class FileImporter { }).filter(aas => aas !== null) as types.Environment[]; } + /** + * Get all JSON files from a directory. + * @param aasPath Directory path + * @returns Paths of the JSON files + */ private static getAllAASFilePaths(aasPath: string): string[] { const files = readdirSync(aasPath); return files.filter(file => file.endsWith(".json")).map(file => `${aasPath}/${file}`); diff --git a/lib/src/helper/traverser.ts b/lib/src/helper/traverser.ts index cde1c6e..0ffbf6f 100644 --- a/lib/src/helper/traverser.ts +++ b/lib/src/helper/traverser.ts @@ -3,28 +3,58 @@ import { types } from "@aas-core-works/aas-core3.0-typescript"; import { RelationshipElement } from "@aas-core-works/aas-core3.0-typescript/dist/types/types"; import { ResolvedRelationshipElement } from "../types/common"; +/** + * Keytypes which are globally identifiable. + */ const GLOBALLY_IDENTIFIABLES = [types.KeyTypes.GlobalReference, types.KeyTypes.AssetAdministrationShell, types.KeyTypes.ConceptDescription, types.KeyTypes.Identifiable, types.KeyTypes.Submodel] +/** + * Helper to traverse the AAS-Environment. + */ export default class Traverser { + /** + * Find an AAS by its ID. + * @param environment Environment to search in + * @param id ID + * @returns AAS or null if not found + */ public static findAASById(environment: types.Environment, id: string): types.AssetAdministrationShell | null { if (environment.assetAdministrationShells === null) return null; return (environment.assetAdministrationShells.find(aas => aas.id === id)) ?? null; } + /** + * Find a Submodel by its ID. + * @param environment Environment to search in + * @param id ID + * @returns Submodel or null if not found + */ public static findSMById(environment: types.Environment, id: string): types.Submodel | null { if (environment.submodels === null) return null; return (environment.submodels.find(sm => sm.id === id)) ?? null; } + /** + * Find Submodel by its idShort. + * @param environment Environment to search in + * @param id idShort + * @returns Submodel or null if not found + */ public static findSMByIdShort(environment: types.Environment, id: string): types.Submodel | null { if (environment.submodels === null) return null; return (environment.submodels.find(sm => sm.idShort === id)) ?? null; } + /** + * Resolve a reference to an element. + * @param env Environment to search in + * @param ref Reference + * @returns Element or null if not found + */ public static resolveReference(env: types.Environment, ref: types.Reference): types.Class | null { if (ref.type === types.ReferenceTypes.ExternalReference) return null; // Not implemented @@ -42,6 +72,12 @@ export default class Traverser { return current; } + /** + * Resolve both references of a relationship element + * @param env Environment to search in + * @param relationship Relationship element + * @returns Resolved relationship element or null if not found + */ public static resolveRelationship(env: types.Environment, relationship: RelationshipElement): ResolvedRelationshipElement | null { const first = Traverser.resolveReference(env, relationship.first); const second = Traverser.resolveReference(env, relationship.second); @@ -50,9 +86,15 @@ export default class Traverser { return { first, second }; } - public static traverseByShortIds(start: types.Class, shortIds: string[]): types.Class | null { + /** + * Traverse downwards from a start element by using idShorts. + * @param start Start element + * @param idShorts Array of idShorts to traverse by + * @returns Target element at the end of the path or null if not found + */ + public static traverseByShortIds(start: types.Class, idShorts: string[]): types.Class | null { let current: types.Class | null = start; - for (const shortId of shortIds) { + for (const shortId of idShorts) { if (current === null) break; current = Traverser.findChildByIdShort(current, shortId); @@ -61,12 +103,25 @@ export default class Traverser { return current; } + /** + * Get element by using idShorts from an submodel. + * @param env Environment to search in + * @param submodelOrIdShort Submodel element or Submodel idShort + * @param idShorts idShorts to traverse by + * @returns Target element at the end of the path or null if not found + */ public static getElementByIdPath(env: types.Environment, submodelOrIdShort: string | types.Submodel, idShorts: string[]): types.Class | null { const sm = typeof submodelOrIdShort === "string" ? Traverser.findSMByIdShort(env, submodelOrIdShort) : submodelOrIdShort; if (sm === null) return null; return Traverser.traverseByShortIds(sm, idShorts); } + /** + * Find an element using a function to check. + * @param start Start element + * @param checkFunction Function to find the element. First element which evaluates to true using this function will be returned. + * @returns Element or null if not found + */ public static findElement(start: types.Class, checkFunction: (element: types.Class) => boolean): types.Class | null { for (const element of start.descend()) { if (checkFunction(element)) return element; @@ -75,6 +130,12 @@ export default class Traverser { return null; } + /** + * Find a child element by its idShort. + * @param start Start element + * @param idShort idShort to search for + * @returns Child element or null if not found + */ public static findChildByIdShort(start: types.Class, idShort: string): types.Class | null { for (const child of start.descendOnce()) { if ((child as any).idShort === idShort) return child; diff --git a/lib/src/interfaceConnectionObject.ts b/lib/src/interfaceConnectionObject.ts index 68cbe7f..fb880cb 100644 --- a/lib/src/interfaceConnectionObject.ts +++ b/lib/src/interfaceConnectionObject.ts @@ -7,48 +7,146 @@ type ConnectionType = "ON_DEMAND" | "PERMANENT"; export type OnEventCallback = (response: any) => void +/** + * Abstract class for an connector to an asset. + * This should be used as base class for your own connectors. + */ export default abstract class InterfaceConnectionObject { + /** + * A name for your connector. + * @remarks + * Currently not used. + */ public static readonly connectorName: string; + /** + * Which protocols are supported by your connector. + * This is really important later so the AAS can decide which connector to use! + */ public static readonly uriProtocol: string[]|string; + /** + * The type of connection your connector uses. + * @remarks + * Currently not used. + */ public static readonly connectionType: ConnectionType; + /** + * Whether your connector supports subscriptions. + * @remarks + * Currently not used. + */ public static readonly supportsSubscriptions: boolean; + /** + * A store for all observers and their callbacks. + */ protected readonly observerStore: Record void)[]> = {}; + /** + * A store for all event subscriptions and their callbacks. + */ protected readonly eventSubStore: Record void)[]> = {}; + /** + * A store for all async action handles and their results. + */ protected readonly asyncActionStateStore: Record = {}; + /** + * Creates the connector. + * @param connectionParameter Connection parameter for your connector. + * @param endpointMetadata {@link EndpointMetadata} + * @param mapper {@link AIMCMapper} + * @param onConnectorEvent Callback when an event is received. + */ public constructor( protected readonly connectionParameter: ConfigInterface, public readonly endpointMetadata: EndpointMetadata, protected readonly mapper: AIMCMapper, protected readonly onConnectorEvent: OnEventCallback) {} + /** + * This should connect the Connector to the asset. + * @returns Whether the connection was successful. + */ public abstract connect(): boolean; + /** + * This should disconnect the Connector from the asset (if it even is connected). + */ public abstract disconnect(): void; + /** + * Read a property value from the asset. + * @param prop Property + * @returns Value of property casted to the type it says it should be. + */ public abstract readProperty(prop: types.Property): void; + /** + * Write a property value to the asset. + * @param prop Property + * @param value Value to write. + */ public abstract writeProperty(prop: types.Property, value: any): void; + /** + * Observe a property value from the asset. + * @param prop Property + * @param callback Callback to call when the property changes. + * @returns Whether the creation of an observer was successful. + */ public abstract observeProperty(prop: types.Property, callback: (value: any) => void): boolean; + /** + * Call an action on the asset synchronously. + * @param action Action + * @param args Arguments + * @returns Return value of the action. + */ public abstract callActionSync(action: types.Operation, args: Record): any; + /** + * Call an action on the asset asynchronously. + * @param action Action + * @param args Arguments + * @returns Handle for the async action. + */ public abstract callActionAsync(action: types.Operation, args: Record): string | null; - public readAsyncActionState(handle: string): any { + /** + * Read the state of an async action. + * @param handle Handle of the async action. + * @returns Whether the async action is finished. + */ + public readAsyncActionState(handle: string): boolean { return this.asyncActionStateStore[handle].finished; } + /** + * Read the result of an async action. + * @param handle Handle of the async action. + * @returns Result of the async action. + */ public readAsyncActionResponse(handle: string): any { return this.asyncActionStateStore[handle].result; }; + /** + * Subscribe to an event. + * @param event Event + * @param callback Callback to call when the event occurs. + * @returns Whether the subscription was successful. + */ public abstract subscribeEvent(event: types.BasicEventElement, callback: (event: types.BasicEventElement) => void): boolean; + /** + * Unsubscribe from an event. + * @param event Event + */ public abstract unsubscribeEvent(event: types.BasicEventElement): void; + /** + * Generate a handle for an async action. + * @returns Handle + */ protected generateAsyncHandle() { const handle = v4(); this.asyncActionStateStore[handle] = {finished: false, result: null}; diff --git a/lib/src/multimessageBroker.ts b/lib/src/multimessageBroker.ts index e05330b..98327e2 100644 --- a/lib/src/multimessageBroker.ts +++ b/lib/src/multimessageBroker.ts @@ -27,15 +27,46 @@ type InterfaceConnectionEntry = { config: ConfigInterface } +/** + * The core of the library. + * @remarks + * Here are all Interface Servers and Connectors are created and managed. + * Also every request is handled here. + */ export default class MultiMessageBroker { + private static instance: MultiMessageBroker|null = null; + + /** + * Whether the broker is prepared. + */ private prepared: boolean = false; + /** + * All registered AASs. + */ private aasRegistrations: AASRegistrationPrepared[] = []; + /** + * All registered Interface Connectors. + */ private interfaceConnections: InterfaceConnectionEntry[] = []; - public constructor() {} + private constructor() {} - public registerAAS(registration: AASRegistration): void { + /** + * Get the singleton instance of the broker. + */ + public static getInstance(): MultiMessageBroker { + if (this.instance === null) this.instance = new MultiMessageBroker(); + return this.instance; + } + + /** + * Register an AAS. + * @remarks + * This will also create the {@link AIMCMapper} for the AAS + * @param registration AAS registration + */ + public registerAAS(registration: AASRegistration): void { this.aasRegistrations.push({ ...registration, serverInstances: [], @@ -44,10 +75,20 @@ export default class MultiMessageBroker { }); } + /** + * Register an Interface Connector. + * @param interfaceConnectionEntry Interface connector registration + * @typeParam T - The config interface for your interface connector. + */ public registerInterfaceConnection(interfaceConnectionEntry: InterfaceConnectionEntry): void { this.interfaceConnections.push(interfaceConnectionEntry); } + /** + * Prepare the broker. + * @remarks + * This will create instances of the {@link AASInterfaceServer} and {@link InterfaceConnectionObject} classes. + */ public prepare(): void { for (const registration of this.aasRegistrations) { @@ -92,6 +133,9 @@ export default class MultiMessageBroker { this.prepared = true; } + /** + * Start the broker. + */ public start(): void { if (!this.prepared) this.prepare(); @@ -101,6 +145,12 @@ export default class MultiMessageBroker { } } + /** + * Callback on an server request + * @param request Request + * @param registration AAS Registration + * @returns Value or success state or nothing based on the request type + */ private onInterfaceRequest(request: Request, registration: AASRegistrationPrepared): any { const getConnector = (target: types.Class) => { @@ -162,6 +212,10 @@ export default class MultiMessageBroker { } } + /** + * Callback on a connector event. + * @param response + */ private onConnectorEvent(response: any) { for (const aas of this.aasRegistrations) { for (const server of aas.serverInstances ?? []) { diff --git a/lib/src/server.ts b/lib/src/server.ts index 12aa8f2..63466d6 100644 --- a/lib/src/server.ts +++ b/lib/src/server.ts @@ -1,18 +1,55 @@ import { types } from "@aas-core-works/aas-core3.0-typescript"; -import type { Request, OnRequestCallback } from "./types/requests"; +import type { OnRequestCallback } from "./types/requests"; +/** + * Abstract class for an interface server. + * This should be used as base class for your own interface servers! + * + * @typeParam ConfigInterface - The config interface for your interface server. + */ export default abstract class AASInterfaceServer { + /** + * A name for your interface server. + * @remarks + * Currently unused + */ public static readonly serverInterfaceName: string; + /** + * Whether your interface server supports subscriptions. + * @remarks + * Currently unused + */ public static readonly supportsSubscriptions: boolean; + /** + * Crate the interface server. + * @param config Interface server config + * @param aas AAS Environment for the server + * @param onRequestCallback Callback when a request is received and parsed + */ constructor( protected readonly config: ConfigInterface, protected readonly aas: types.Environment, protected readonly onRequestCallback: OnRequestCallback) {} + /** + * Prepare the interface server. + * @remarks + * Here you can create routes, listeners, callbacks,... + */ public abstract prepare(): void; + /** + * Run the interface server. + */ public abstract run(): void; + /** + * Stop the interface server. + */ public abstract stop(): void; + /** + * Notify observers/subscribers about an event. + * @param event Event to notify the interface server about + */ public abstract notify(event: any): void; } \ No newline at end of file