UNPKG

@contiva/sap-integration-suite-client

Version:
651 lines (650 loc) 30.5 kB
"use strict"; /** * SAP Integration Content Advanced Client * * Diese Datei enthält erweiterte Methoden für die Integration Content APIs von SAP Cloud Integration. * Diese Methoden sind Erweiterungen der grundlegenden API-Funktionen und bieten zusammengesetzte Operationen. * * @module sap-integration-suite-client/integration-content-advanced */ Object.defineProperty(exports, "__esModule", { value: true }); exports.IntegrationContentAdvancedClientFactory = exports.IntegrationContentAdvancedClient = void 0; const base_custom_client_1 = require("./base-custom-client"); /** * Erweiterter SAP Integration Content Client * * Diese Klasse stellt erweiterte, zusammengesetzte Operationen für die Interaktion * mit den SAP Integration Content APIs bereit. */ class IntegrationContentAdvancedClient extends base_custom_client_1.BaseCustomClient { /** * Erstellt einen neuen IntegrationContentAdvancedClient * * @param {IntegrationContentClient} client - Die zugrundeliegende Client-Instanz */ constructor(client) { super(client); /** Counter für 429 Rate Limit Fehler */ this.rateLimitErrors = 0; } /** * Gibt die Anzahl der 429 Rate Limit Fehler zurück * * @returns {number} Anzahl der Rate Limit Fehler */ getRateLimitErrorCount() { return this.rateLimitErrors; } /** * Setzt den Rate Limit Error Counter zurück */ resetRateLimitErrorCount() { this.rateLimitErrors = 0; } /** * Ruft alle Integrationspakete mit ihren zugehörigen Artefakten ab * * Diese Methode holt alle Integrationspakete und sammelt für jedes Paket: * - Integrationsflows * - Message Mappings * - Value Mappings * - Script Collections * * **Optimierungen:** * - MessageMappings & ValueMappings werden über globale Endpunkte abgerufen (nur 2 API-Calls) * - IntegrationFlows & ScriptCollections werden mit Concurrency-Limit abgerufen * - Bis zu 50% weniger API-Aufrufe im Vergleich zur nicht-optimierten Variante * * **Best Practices:** * - Für kleine bis mittlere Systeme (bis 100 Packages): `concurrency: 7-10` * - Für größere Systeme: Starte mit `concurrency: 5` und erhöhe schrittweise * - 429 Rate Limit Fehler können mit `getRateLimitErrorCount()` überwacht werden * * @param {Object} options Optionale Parameter für die Anfrage * @param {number} [options.top] Maximale Anzahl der zurückzugebenden Pakete * @param {number} [options.skip] Anzahl der zu überspringenden Pakete * @param {boolean} [options.includeEmpty=false] Ob leere Pakete ohne Artefakte inkludiert werden sollen * @param {boolean} [options.parallel=false] Ob die Artefakte parallel abgerufen werden sollen (schneller, aber möglicherweise API-Limits) * @param {number} [options.concurrency=2] Maximale Anzahl paralleler Requests (nur bei parallel=true). Default: 7 (optimal für die meisten Systeme) * @returns {Promise<PackageWithArtifacts[]>} Liste von Paketen mit ihren Artefakten * * @example * // Alle Pakete mit ihren Artefakten abrufen (sequentiell) * const packagesWithArtifacts = await client.getPackagesWithArtifacts(); * * // Nur die ersten 5 Pakete mit Artefakten abrufen * const packagesWithArtifacts = await client.getPackagesWithArtifacts({ top: 5 }); * * // Alle Pakete mit parallelen API-Aufrufen abrufen (mit optimalem Concurrency Limit) * const packagesWithArtifacts = await client.getPackagesWithArtifacts({ parallel: true, concurrency: 7 }); * * // 429 Rate Limit Fehler überwachen * const packagesWithArtifacts = await client.getPackagesWithArtifacts({ parallel: true }); * const rateLimitErrors = client.getRateLimitErrorCount(); * if (rateLimitErrors > 0) { * console.warn(`${rateLimitErrors} Rate Limit Fehler aufgetreten - reduziere Concurrency`); * } * * // Mit Progress-Callback für Echtzeit-Fortschrittsmeldungen * const packagesWithArtifacts = await client.getPackagesWithArtifacts({ * parallel: true, * onProgress: (event) => { * if (event.type === 'packages:loaded') { * console.log(`${event.totalPackages} Packages gefunden`); * } else if (event.type === 'step:start') { * console.log(`Lade ${event.step}...`); * } else if (event.type === 'package:processing') { * console.log(`Verarbeite ${event.currentPackage?.name} (${event.currentPackage?.index + 1}/${event.totalPackages})`); * } * } * }); */ async getPackagesWithArtifacts(options = {}) { const { onProgress } = options; const packages = await this.client.getIntegrationPackages({ top: options.top, skip: options.skip }); if (packages.length === 0) { return []; } if (onProgress) { onProgress({ type: 'packages:loaded', totalPackages: packages.length, packages: packages.map(pkg => ({ id: pkg.Id, name: (pkg.Name || pkg.Id) })) }); } // Map von PackageId -> Package erstellen für einfacheren Zugriff const packageMap = new Map(); for (const pkg of packages) { if (pkg.Id) { packageMap.set(pkg.Id, pkg); } } // Array für das Ergebnis mit erweiterter Struktur const result = packages.map(pkg => ({ package: { ...pkg, IntegrationDesigntimeArtifacts: [], MessageMappingDesigntimeArtifacts: [], ValueMappingDesigntimeArtifacts: [], ScriptCollectionDesigntimeArtifacts: [] } })); // Direktes Mapping von PackageId zu Result-Index const packageIndexMap = new Map(); packages.forEach((pkg, index) => { if (pkg.Id) { packageIndexMap.set(pkg.Id, index); } }); try { if (options.parallel) { const concurrency = options.concurrency || 7; if (onProgress) { onProgress({ type: 'step:start', step: 'messageMappings' }); } const [allMessageMappings, allValueMappings] = await Promise.all([ this.client._fetchAllMessageMappingsFromGlobalEndpoint(), this.client._fetchAllValueMappingsFromGlobalEndpoint() ]); if (onProgress) { onProgress({ type: 'step:complete', step: 'messageMappings' }); onProgress({ type: 'step:start', step: 'flows' }); } const allFlows = await this.getAllIntegrationFlowsWithConcurrency({ packages, concurrency, onProgress: onProgress ? (packageId, packageName, index) => { onProgress({ type: 'package:processing', step: 'flows', totalPackages: packages.length, currentPackage: { id: packageId, name: packageName, index }, completedPackages: index + 1, progress: (index + 1) / packages.length }); } : undefined }); if (onProgress) { onProgress({ type: 'step:complete', step: 'flows' }); onProgress({ type: 'step:start', step: 'scripts' }); } const allScriptCollections = await this.getAllScriptCollectionsWithConcurrency({ packages, concurrency, onProgress: onProgress ? (packageId, packageName, index) => { onProgress({ type: 'package:processing', step: 'scripts', totalPackages: packages.length, currentPackage: { id: packageId, name: packageName, index }, completedPackages: index + 1, progress: (index + 1) / packages.length }); } : undefined }); if (onProgress) { onProgress({ type: 'step:complete', step: 'scripts' }); } // Sortiere Flows nach PackageId for (const flow of allFlows) { if (flow.PackageId && packageIndexMap.has(flow.PackageId)) { const index = packageIndexMap.get(flow.PackageId); result[index].package.IntegrationDesigntimeArtifacts.push(flow); } } // Sortiere Message Mappings nach PackageId for (const mapping of allMessageMappings) { if (mapping.PackageId && packageIndexMap.has(mapping.PackageId)) { const index = packageIndexMap.get(mapping.PackageId); result[index].package.MessageMappingDesigntimeArtifacts.push(mapping); } } // Sortiere Value Mappings nach PackageId for (const mapping of allValueMappings) { if (mapping.PackageId && packageIndexMap.has(mapping.PackageId)) { const index = packageIndexMap.get(mapping.PackageId); result[index].package.ValueMappingDesigntimeArtifacts.push(mapping); } } // Sortiere Script Collections nach PackageId for (const script of allScriptCollections) { if (script.PackageId && packageIndexMap.has(script.PackageId)) { const index = packageIndexMap.get(script.PackageId); result[index].package.ScriptCollectionDesigntimeArtifacts.push(script); } } } else { if (onProgress) { onProgress({ type: 'step:start', step: 'messageMappings' }); } const [allMessageMappings, allValueMappings] = await Promise.allSettled([ this.client._fetchAllMessageMappingsFromGlobalEndpoint(), this.client._fetchAllValueMappingsFromGlobalEndpoint() ]); if (onProgress) { onProgress({ type: 'step:complete', step: 'messageMappings' }); } // Erstelle Maps für schnellere Zuordnung const messageMappingsByPackage = new Map(); const valueMappingsByPackage = new Map(); if (allMessageMappings.status === 'fulfilled') { for (const mapping of allMessageMappings.value) { if (mapping.PackageId) { const pkgId = mapping.PackageId; if (!messageMappingsByPackage.has(pkgId)) { messageMappingsByPackage.set(pkgId, []); } messageMappingsByPackage.get(pkgId).push(mapping); } } } if (allValueMappings.status === 'fulfilled') { for (const mapping of allValueMappings.value) { if (mapping.PackageId) { const pkgId = mapping.PackageId; if (!valueMappingsByPackage.has(pkgId)) { valueMappingsByPackage.set(pkgId, []); } valueMappingsByPackage.get(pkgId).push(mapping); } } } if (onProgress) { onProgress({ type: 'step:start', step: 'flows' }); } for (const [i, pkg] of packages.entries()) { const packageId = pkg.Id; const packageName = (pkg.Name || pkg.Id); if (onProgress) { onProgress({ type: 'package:processing', step: 'flows', totalPackages: packages.length, currentPackage: { id: packageId, name: packageName, index: i }, completedPackages: i, progress: i / packages.length }); } const [flows, scripts] = await Promise.allSettled([ this.client.getIntegrationFlows(packageId), this.client.getScriptCollections(packageId), ]); result[i].package.IntegrationDesigntimeArtifacts = flows.status === 'fulfilled' ? flows.value : []; result[i].package.ScriptCollectionDesigntimeArtifacts = scripts.status === 'fulfilled' ? scripts.value : []; result[i].package.MessageMappingDesigntimeArtifacts = messageMappingsByPackage.get(packageId) || []; result[i].package.ValueMappingDesigntimeArtifacts = valueMappingsByPackage.get(packageId) || []; if (process.env.DEBUG === 'true') { if (flows.status === 'rejected') { console.error(`Fehler beim Abrufen der Integrationsflows für Paket ${packageId}:`, flows.reason); } if (scripts.status === 'rejected') { console.error(`Fehler beim Abrufen der Script Collections für Paket ${packageId}:`, scripts.reason); } if (allMessageMappings.status === 'rejected') { console.error(`Fehler beim Abrufen aller Message Mappings:`, allMessageMappings.reason); } if (allValueMappings.status === 'rejected') { console.error(`Fehler beim Abrufen aller Value Mappings:`, allValueMappings.reason); } } } if (onProgress) { onProgress({ type: 'step:complete', step: 'flows' }); } } // Entferne leere Pakete, wenn includeEmpty=false if (!options.includeEmpty) { return result.filter(item => item.package.IntegrationDesigntimeArtifacts.length > 0 || item.package.MessageMappingDesigntimeArtifacts.length > 0 || item.package.ValueMappingDesigntimeArtifacts.length > 0 || item.package.ScriptCollectionDesigntimeArtifacts.length > 0); } return result; } catch (error) { console.error(`Fehler beim Abrufen der Artefakte:`, error); throw error; } } /** * Hilfsmethode: Führt Promises mit Concurrency-Limit aus * * @private * @param tasks Array von Promise-Factories * @param concurrency Maximale Anzahl paralleler Promises * @returns Promise mit allen Ergebnissen */ async executWithConcurrency(tasks, concurrency) { const results = []; const executing = new Set(); for (const task of tasks) { const promise = task().then(result => { results.push(result); executing.delete(promise); return result; }).catch(err => { executing.delete(promise); throw err; }); executing.add(promise); // Wenn wir das Concurrency-Limit erreicht haben, warte auf das erste fertige Promise if (executing.size >= concurrency) { await Promise.race(executing); } } // Warte auf alle verbleibenden Promises await Promise.all(Array.from(executing)); return results; } /** * Ruft alle Integrationsflows aller Pakete mit Concurrency-Limit ab * * @private * @param {Object} options Parameter für die Anfrage * @param {ComSapHciApiIntegrationPackage[]} options.packages Pakete * @param {number} options.concurrency Maximale Anzahl paralleler Requests * @returns {Promise<ComSapHciApiIntegrationDesigntimeArtifact[]>} Liste aller Integrationsflows */ async getAllIntegrationFlowsWithConcurrency(options) { const allFlows = []; let completedCount = 0; const tasks = options.packages.map((pkg, _index) => () => this.client.getIntegrationFlows(pkg.Id) .then(flows => { flows.forEach(flow => { if (!flow.PackageId && pkg.Id) { flow.PackageId = pkg.Id; } }); allFlows.push(...flows); if (options.onProgress) { options.onProgress(pkg.Id, (pkg.Name || pkg.Id), completedCount++); } return flows; }) .catch(error => { var _a; if ((error === null || error === void 0 ? void 0 : error.statusCode) === 429 || ((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) === 429) { this.rateLimitErrors++; } if (process.env.DEBUG === 'true') { console.error(`Fehler beim Abrufen der Flows für Paket ${pkg.Id}:`, error.message || error); } completedCount++; return []; })); await this.executWithConcurrency(tasks, options.concurrency); return allFlows; } /** * Ruft alle Script Collections aller Pakete mit Concurrency-Limit ab * * @private * @param {Object} options Parameter für die Anfrage * @param {ComSapHciApiIntegrationPackage[]} options.packages Pakete * @param {number} options.concurrency Maximale Anzahl paralleler Requests * @returns {Promise<ComSapHciApiScriptCollectionDesigntimeArtifact[]>} Liste aller Script Collections */ async getAllScriptCollectionsWithConcurrency(options) { const allScripts = []; let completedCount = 0; const tasks = options.packages.map((pkg, _index) => () => this.client.getScriptCollections(pkg.Id) .then(scripts => { scripts.forEach(script => { if (!script.PackageId && pkg.Id) { script.PackageId = pkg.Id; } }); allScripts.push(...scripts); if (options.onProgress) { options.onProgress(pkg.Id, (pkg.Name || pkg.Id), completedCount++); } return scripts; }) .catch(error => { var _a; if ((error === null || error === void 0 ? void 0 : error.statusCode) === 429 || ((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) === 429) { this.rateLimitErrors++; } if (process.env.DEBUG === 'true') { console.error(`Fehler beim Abrufen der Script Collections für Paket ${pkg.Id}:`, error.message || error); } completedCount++; return []; })); await this.executWithConcurrency(tasks, options.concurrency); return allScripts; } /** * Ruft alle Integrationsflows aller Pakete ab * * @param {Object} options Optionale Parameter für die Anfrage * @param {number} [options.top] Maximale Anzahl der zurückzugebenden Pakete für die Abfrage * @param {ComSapHciApiIntegrationPackage[]} [options.packages] Bereits abgerufene Pakete, um redundante API-Aufrufe zu vermeiden * @returns {Promise<ComSapHciApiIntegrationDesigntimeArtifact[]>} Liste aller Integrationsflows * * @example * const allFlows = await client.getAllIntegrationFlows(); * * // Mit bereits vorhandenen Paketen * const packages = await client.getIntegrationPackages(); * const allFlows = await client.getAllIntegrationFlows({ packages }); */ async getAllIntegrationFlows(options = {}) { // Verwende übergebene Pakete oder hole sie, wenn nicht vorhanden const packages = options.packages || await this.client.getIntegrationPackages({ top: options.top }); // Sammel alle Flows const allFlows = []; // Arrays von Promises für Promise.all - Retry-Logik ist bereits in den Basis-Methoden implementiert const promises = packages.map(pkg => this.client.getIntegrationFlows(pkg.Id) .then(flows => { // Stelle sicher, dass die PackageId in den Flows gesetzt ist flows.forEach(flow => { if (!flow.PackageId && pkg.Id) { flow.PackageId = pkg.Id; } }); allFlows.push(...flows); }) .catch(error => { console.error(`Fehler beim Abrufen der Flows für Paket ${pkg.Id}:`, error); return []; // Fehler ignorieren })); // Warte auf alle Promises await Promise.all(promises); return allFlows; } /** * Ruft alle Script Collections aller Pakete ab * * @param {Object} options Optionale Parameter für die Anfrage * @param {number} [options.top] Maximale Anzahl der zurückzugebenden Pakete für die Abfrage * @param {ComSapHciApiIntegrationPackage[]} [options.packages] Bereits abgerufene Pakete, um redundante API-Aufrufe zu vermeiden * @returns {Promise<ComSapHciApiScriptCollectionDesigntimeArtifact[]>} Liste aller Script Collections * * @example * const allScriptCollections = await client.getAllScriptCollections(); * * // Mit bereits vorhandenen Paketen * const packages = await client.getIntegrationPackages(); * const allScripts = await client.getAllScriptCollections({ packages }); */ async getAllScriptCollections(options = {}) { // Verwende übergebene Pakete oder hole sie, wenn nicht vorhanden const packages = options.packages || await this.client.getIntegrationPackages({ top: options.top }); // Sammel alle Script Collections const allScripts = []; // Arrays von Promises für Promise.all - Retry-Logik ist bereits in den Basis-Methoden implementiert const promises = packages.map(pkg => this.client.getScriptCollections(pkg.Id) .then(scripts => { // Stelle sicher, dass die PackageId in den Script Collections gesetzt ist scripts.forEach(script => { if (!script.PackageId && pkg.Id) { script.PackageId = pkg.Id; } }); allScripts.push(...scripts); }) .catch(error => { console.error(`Fehler beim Abrufen der Script Collections für Paket ${pkg.Id}:`, error); return []; // Fehler ignorieren })); // Warte auf alle Promises await Promise.all(promises); return allScripts; } /** * Gibt alle Message Mappings (tenant-weit) zurück. * * @param {Object} options Optionale Parameter für die Anfrage * @param {number} [options.top] Maximale Anzahl der zurückzugebenden Mappings * @param {number} [options.skip] Anzahl der zu überspringenden Mappings * @param {string} [options.select] Zu selektierende Properties * @param {string} [options.orderby] Sortierreihenfolge * @returns {Promise<ComSapHciApiMessageMappingDesigntimeArtifact[]>} Promise mit einer Liste von Message Mappings * * @example * const allMappings = await client.getAllMessageMappings({ top: 50 }); */ async getAllMessageMappings(options = {}) { // Wenn Pakete vorhanden sind, hole Mappings paketweise, ansonsten über die zentrale API if (options.packages && options.packages.length > 0) { const allMappings = []; // Arrays von Promises für Promise.all - Retry-Logik ist bereits in den Basis-Methoden implementiert const promises = options.packages.map(pkg => this.client.getMessageMappings(pkg.Id) .then(mappings => { // Stelle sicher, dass die PackageId in den Message Mappings gesetzt ist mappings.forEach(mapping => { if (!mapping.PackageId && pkg.Id) { mapping.PackageId = pkg.Id; } }); allMappings.push(...mappings); }) .catch(error => { console.error(`Fehler beim Abrufen der Message Mappings für Paket ${pkg.Id}:`, error); return []; // Fehler ignorieren })); // Warte auf alle Promises await Promise.all(promises); return allMappings; } else { // Über die zentrale API mit den ursprünglichen Parametern return this.client.getAllMessageMappings(options); } } /** * Gibt alle Value Mappings (tenant-weit) zurück. * * @param {Object} options Optionale Parameter für die Anfrage * @param {number} [options.top] Maximale Anzahl der zurückzugebenden Mappings * @param {number} [options.skip] Anzahl der zu überspringenden Mappings * @param {string} [options.select] Zu selektierende Properties * @param {string} [options.orderby] Sortierreihenfolge * @returns {Promise<ComSapHciApiValueMappingDesigntimeArtifact[]>} Promise mit einer Liste von Value Mappings * * @example * const allValueMappings = await client.getAllValueMappings({ top: 20 }); */ async getAllValueMappings(options = {}) { // Wenn Pakete vorhanden sind, hole Mappings paketweise, ansonsten über die zentrale API if (options.packages && options.packages.length > 0) { const allMappings = []; // Arrays von Promises für Promise.all - Retry-Logik ist bereits in den Basis-Methoden implementiert const promises = options.packages.map(pkg => this.client.getValueMappings(pkg.Id) .then(mappings => { // Stelle sicher, dass die PackageId in den Value Mappings gesetzt ist mappings.forEach(mapping => { if (!mapping.PackageId && pkg.Id) { mapping.PackageId = pkg.Id; } }); allMappings.push(...mappings); }) .catch(error => { console.error(`Fehler beim Abrufen der Value Mappings für Paket ${pkg.Id}:`, error); return []; // Fehler ignorieren })); // Warte auf alle Promises await Promise.all(promises); return allMappings; } else { // Über die zentrale API mit den ursprünglichen Parametern return this.client.getAllValueMappings(options); } } /** * Gibt detaillierte Fehlerinformationen für ein deployten Integrationsartefakt zurück * Diese Methode ruft den spezifischen $value-Endpunkt auf, der mehr Details enthält * * @param {string} artifactId ID des deployten Integrationsartefakts * @returns {Promise<DetailedErrorInformation | null>} Promise mit den detaillierten Fehlerinformationen oder null bei Fehlern * * @example * const detailedError = await client.getDetailedArtifactErrorInformation('MyFailedFlow'); * if (detailedError && detailedError.message) { * console.log(`Fehlertyp: ${detailedError.message.messageId}`); * if (detailedError.parameter && detailedError.parameter.length > 0) { * try { * const paramJson = JSON.parse(detailedError.parameter[0]); * console.log(`Fehlermeldung: ${paramJson.message}`); * } catch (e) { * console.log(`Parameter: ${detailedError.parameter[0]}`); * } * } * } */ async getDetailedArtifactErrorInformation(artifactId) { return this.client.getDetailedArtifactErrorInformation(artifactId); } /** * Parst die Fehlerdetails aus den Parametern einer DetailedErrorInformation * * @param {DetailedErrorInformation} errorInfo Die detaillierten Fehlerinformationen * @returns {ParsedErrorDetails | null} Die geparsten Fehlerdetails oder null, wenn keine Parameter vorhanden oder das Parsing fehlschlägt * * @example * const detailedError = await client.getDetailedArtifactErrorInformation('MyFailedFlow'); * if (detailedError) { * const errorDetails = client.parseErrorDetails(detailedError); * if (errorDetails && errorDetails.message) { * console.log(`Fehlermeldung: ${errorDetails.message}`); * * if (errorDetails.childMessageInstances && errorDetails.childMessageInstances.length > 0) { * console.log(`Hauptursache: ${errorDetails.childMessageInstances[0].message}`); * } * } * } */ parseErrorDetails(errorInfo) { return this.client.parseErrorDetails(errorInfo); } } exports.IntegrationContentAdvancedClient = IntegrationContentAdvancedClient; /** * Factory für die Erstellung von IntegrationContentAdvancedClient-Instanzen */ class IntegrationContentAdvancedClientFactory { /** * Erstellt eine neue IntegrationContentAdvancedClient-Instanz * * @param baseClient - Der zugrundeliegende Standard-Client * @returns Ein IntegrationContentAdvancedClient */ create(baseClient) { return new IntegrationContentAdvancedClient(baseClient); } } exports.IntegrationContentAdvancedClientFactory = IntegrationContentAdvancedClientFactory;