@onlineapps/service-wrapper 3.0.9 → 3.0.10
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/jest.config.js +1 -0
- package/package.json +1 -1
- package/src/ServiceWrapper.js +104 -4
package/jest.config.js
CHANGED
|
@@ -26,6 +26,7 @@ module.exports = {
|
|
|
26
26
|
'@onlineapps/conn-orch-registry': '<rootDir>/tests/mocks/connectors.js',
|
|
27
27
|
'@onlineapps/conn-base-logger': '<rootDir>/tests/mocks/connectors.js',
|
|
28
28
|
'@onlineapps/conn-orch-orchestrator': '<rootDir>/tests/mocks/connectors.js',
|
|
29
|
+
'@onlineapps/conn-orch-orchestrator/package.json': '<rootDir>/tests/mocks/orchestrator-package-v2.js',
|
|
29
30
|
'@onlineapps/conn-orch-api-mapper': '<rootDir>/tests/mocks/connectors.js',
|
|
30
31
|
'@onlineapps/conn-orch-cookbook': '<rootDir>/tests/mocks/connectors.js',
|
|
31
32
|
'@onlineapps/conn-base-state': '<rootDir>/tests/mocks/connectors.js'
|
package/package.json
CHANGED
package/src/ServiceWrapper.js
CHANGED
|
@@ -34,6 +34,40 @@ const { ErrorMapper, UnknownOperationError, ValidationError } = require('./Error
|
|
|
34
34
|
const RUNTIME_DIR = 'conn-runtime';
|
|
35
35
|
const PROOF_RELATIVE_PATH = `${RUNTIME_DIR}/validation-proof.json`;
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* service-wrapper v3 wires OrchestratorConnector with invoker RPC (conn-orch-orchestrator ≥2).
|
|
39
|
+
* If npm resolves orchestrator 1.x (stale tree / lockfile), WorkflowOrchestrator throws
|
|
40
|
+
* "apiMapper is required" — fail here with an explicit contract error instead.
|
|
41
|
+
* @returns {{ version: string, major: number }}
|
|
42
|
+
*/
|
|
43
|
+
function readConnOrchOrchestratorContractVersion() {
|
|
44
|
+
let pkg;
|
|
45
|
+
try {
|
|
46
|
+
pkg = require('@onlineapps/conn-orch-orchestrator/package.json');
|
|
47
|
+
} catch (e) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`[ServiceWrapper] Missing dependency - cannot resolve @onlineapps/conn-orch-orchestrator/package.json — ${e.message}. Fix: npm install from service root so @onlineapps/conn-orch-orchestrator is installed.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
const version = pkg.version;
|
|
53
|
+
const major = Number(String(version).split('.')[0]);
|
|
54
|
+
if (!Number.isFinite(major)) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`[ServiceWrapper] Invalid dependency metadata - @onlineapps/conn-orch-orchestrator version "${version}" is not semver-like. Fix: reinstall node_modules from a valid registry lockfile.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return { version, major };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function assertConnOrchOrchestratorV2Contract() {
|
|
63
|
+
const { version, major } = readConnOrchOrchestratorContractVersion();
|
|
64
|
+
if (major < 2) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`[ServiceWrapper] Dependency contract violation - resolved @onlineapps/conn-orch-orchestrator@${version} is incompatible with service-wrapper v3 (orchestrator invoker model). Expected major version >= 2. Fix: pin "@onlineapps/conn-orch-orchestrator":"2.0.2" (or newer 2.x) in the service package.json, delete node_modules and .oa_drive_deps_hash, then run npm ci.`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
37
71
|
const REVALIDATION_FAST_BACKOFF_MS = [30000, 60000, 120000, 300000, 600000, 1800000];
|
|
38
72
|
const REVALIDATION_HOURLY_MS = 3600000;
|
|
39
73
|
const REVALIDATION_HOURLY_MAX = 24;
|
|
@@ -1468,6 +1502,8 @@ class ServiceWrapper {
|
|
|
1468
1502
|
* @private
|
|
1469
1503
|
*/
|
|
1470
1504
|
async _initializeOrchestrator() {
|
|
1505
|
+
assertConnOrchOrchestratorV2Contract();
|
|
1506
|
+
|
|
1471
1507
|
const serviceName = this.config.service?.name;
|
|
1472
1508
|
const serviceVersion = this.config.service?.version;
|
|
1473
1509
|
const environment = this.config.service?.env;
|
|
@@ -1628,23 +1664,71 @@ class ServiceWrapper {
|
|
|
1628
1664
|
}
|
|
1629
1665
|
}
|
|
1630
1666
|
|
|
1667
|
+
/**
|
|
1668
|
+
* If the inbound message used RPC (replyTo + correlationId), publish the JSON response
|
|
1669
|
+
* to the caller's reply queue with the same AMQP correlationId.
|
|
1670
|
+
*
|
|
1671
|
+
* @private
|
|
1672
|
+
* @param {object} rawMessage - amqplib consume message
|
|
1673
|
+
* @param {object|null} parsedMessage - parsed JSON body, or null when JSON.parse failed
|
|
1674
|
+
* @param {{ status: string, output?: object, error?: object, correlation_id?: string, workflow_id?: string }} body
|
|
1675
|
+
*/
|
|
1676
|
+
async _publishWorkflowRpcReply(rawMessage, parsedMessage, body) {
|
|
1677
|
+
const props = rawMessage && rawMessage.properties;
|
|
1678
|
+
if (!this.mqClient || !props || !props.replyTo || props.correlationId == null || String(props.correlationId) === '') {
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
const amqpCid = String(props.correlationId);
|
|
1682
|
+
const correlation_id =
|
|
1683
|
+
body.correlation_id != null
|
|
1684
|
+
? String(body.correlation_id)
|
|
1685
|
+
: parsedMessage && parsedMessage.correlation_id != null
|
|
1686
|
+
? String(parsedMessage.correlation_id)
|
|
1687
|
+
: amqpCid;
|
|
1688
|
+
const workflow_id =
|
|
1689
|
+
body.workflow_id != null
|
|
1690
|
+
? body.workflow_id
|
|
1691
|
+
: parsedMessage &&
|
|
1692
|
+
(parsedMessage.workflow_id != null || parsedMessage.workflowId != null)
|
|
1693
|
+
? parsedMessage.workflow_id || parsedMessage.workflowId
|
|
1694
|
+
: undefined;
|
|
1695
|
+
const payload = { ...body, correlation_id };
|
|
1696
|
+
if (workflow_id !== undefined && payload.workflow_id === undefined) {
|
|
1697
|
+
payload.workflow_id = workflow_id;
|
|
1698
|
+
}
|
|
1699
|
+
await this.mqClient.publish(props.replyTo, payload, {
|
|
1700
|
+
correlationId: amqpCid,
|
|
1701
|
+
persistent: true,
|
|
1702
|
+
contentType: 'application/json',
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1631
1706
|
/**
|
|
1632
1707
|
* Process workflow message
|
|
1633
1708
|
* @private
|
|
1634
1709
|
*/
|
|
1635
1710
|
async _processWorkflowMessage(rawMessage, queueName) {
|
|
1711
|
+
let message;
|
|
1636
1712
|
try {
|
|
1637
|
-
// Parse message
|
|
1638
|
-
let message;
|
|
1639
1713
|
if (rawMessage && rawMessage.content) {
|
|
1640
|
-
// AMQP message with content buffer
|
|
1641
1714
|
const messageContent = rawMessage.content.toString();
|
|
1642
1715
|
message = JSON.parse(messageContent);
|
|
1643
1716
|
} else {
|
|
1644
|
-
// Already parsed message
|
|
1645
1717
|
message = rawMessage;
|
|
1646
1718
|
}
|
|
1719
|
+
} catch (parseErr) {
|
|
1720
|
+
try {
|
|
1721
|
+
await this._publishWorkflowRpcReply(rawMessage, null, {
|
|
1722
|
+
status: 'error',
|
|
1723
|
+
error: { message: parseErr.message, code: 'INVALID_JSON' },
|
|
1724
|
+
});
|
|
1725
|
+
} catch (replyErr) {
|
|
1726
|
+
this.logger?.error('[ServiceWrapper] RPC reply after JSON parse error failed', replyErr);
|
|
1727
|
+
}
|
|
1728
|
+
throw parseErr;
|
|
1729
|
+
}
|
|
1647
1730
|
|
|
1731
|
+
try {
|
|
1648
1732
|
// Extract and normalize flags
|
|
1649
1733
|
const flags = Array.isArray(message.flags) ? message.flags : [];
|
|
1650
1734
|
const isTest = flags.includes('test');
|
|
@@ -1741,6 +1825,11 @@ class ServiceWrapper {
|
|
|
1741
1825
|
// ServiceWrapper should NOT publish here to avoid duplicate messages
|
|
1742
1826
|
// If orchestrator is not used, ServiceWrapper would publish, but that's legacy path
|
|
1743
1827
|
|
|
1828
|
+
await this._publishWorkflowRpcReply(rawMessage, message, {
|
|
1829
|
+
status: 'ok',
|
|
1830
|
+
output: result,
|
|
1831
|
+
});
|
|
1832
|
+
|
|
1744
1833
|
return result;
|
|
1745
1834
|
|
|
1746
1835
|
} catch (error) {
|
|
@@ -1755,6 +1844,17 @@ class ServiceWrapper {
|
|
|
1755
1844
|
} else {
|
|
1756
1845
|
this.logger?.error(`Error processing message from ${queueName}:`, error);
|
|
1757
1846
|
}
|
|
1847
|
+
try {
|
|
1848
|
+
await this._publishWorkflowRpcReply(rawMessage, message, {
|
|
1849
|
+
status: 'error',
|
|
1850
|
+
error: {
|
|
1851
|
+
message: error.message,
|
|
1852
|
+
code: error.code || 'PROCESSING_ERROR',
|
|
1853
|
+
},
|
|
1854
|
+
});
|
|
1855
|
+
} catch (replyErr) {
|
|
1856
|
+
this.logger?.error('[ServiceWrapper] RPC error reply publish failed', replyErr);
|
|
1857
|
+
}
|
|
1758
1858
|
throw error;
|
|
1759
1859
|
}
|
|
1760
1860
|
}
|