Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-Claude) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
7.0 KiB
SOAP Spike Results — Sage X3 WSDL Validation
Date: 2026-03-10
Method
- Created a mock WSDL (
mock-x3.wsdl) replicating X3'sCAdxWebServiceXmlCCstructure - Mock uses RPC/encoded binding style with SOAP 1.1,
soapenc:Arraytypes, Adonix/WSS namespace - Ran a local mock SOAP server + client using
soap@1.8.0to validate all 5 questions - All 6 tests passed (5 questions + 1 bonus envelope inspection)
Validation Questions
Q1: Can soap parse X3's WSDL? YES
The soap library successfully parses a WSDL with:
style="rpc"anduse="encoded"bindingsoapenc:Arraycomplex types (ArrayOfCAdxMessage, ArrayOfCAdxParamKeyValue, etc.)- Complex nested types (CAdxCallContext, CAdxResultXml)
- Multiple operations with different message signatures
All 5 operations discovered via client.describe():
getDescription,read,query,save,run
Each operation's input/output types were correctly introspected including the CAdxCallContext parameter structure.
Q2: Can it construct getDescription with CAdxCallContext? YES
The generated SOAP envelope correctly contains:
<tns:getDescription>
<callContext>
<codeLang>ENG</codeLang>
<codeUser></codeUser>
<password></password>
<poolAlias>SEED</poolAlias>
<poolId></poolId>
<requestConfig>adxwss.optreturn=JSON&adxwss.beautify=true</requestConfig>
</callContext>
<publicName>SIH</publicName>
</tns:getDescription>
Key observations:
- CAdxCallContext fields are correctly nested inside
<callContext> requestConfigvalue is properly XML-escaped (&)- RPC namespace
http://www.adonix.com/WSSis present astns - Uses
tns:getDescriptionwrapper element (RPC style, not document/literal)
Q3: Does adxwss.optreturn=JSON work? PARTIALLY TESTABLE
What we confirmed:
resultXmlis always a string field in the SOAP response- The
soaplibrary passes the string value through without parsing it - When the server puts JSON inside
resultXml,JSON.parse(resultXml)works - When the server puts XML inside
resultXml, we needfast-xml-parser
What requires a real X3 server to fully validate:
- Whether X3 actually returns valid JSON when
adxwss.optreturn=JSONis set - The exact JSON structure X3 produces (field names, nesting)
Implementation approach: Always try JSON.parse(resultXml) first, fall back to fast-xml-parser.
Q4: What format does resultXml come in? STRING
Key findings from the spike:
| Field | Type returned by soap lib | Notes |
|---|---|---|
resultXml |
string |
Raw string, NOT parsed. We parse it ourselves. |
status |
string |
Returns "1" not 1. Must parseInt(). |
messages |
undefined |
Empty arrays come back as undefined. Must default to []. |
technicalInfos |
undefined |
Same as messages — default to []. |
The raw SOAP response correctly HTML-encodes XML inside <resultXml>:
<resultXml><SINVOICE><FLD NAME="NUM">INV001</FLD></SINVOICE></resultXml>
The soap library correctly decodes this back to the original XML string.
Q5: Does Basic Auth work at HTTP level? YES
client.setSecurity(new soap.BasicAuthSecurity(user, password))works- Auth header is added at HTTP transport level (not in SOAP body)
- CAdxCallContext
codeUserandpasswordshould remain empty strings for V12 - The SOAP envelope correctly shows empty
<codeUser></codeUser>and<password></password>
Recommendation: Use soap library
The soap@1.8.0 library is the right choice for Sage X3 SOAP integration.
Why soap library wins:
- Correctly parses RPC/encoded WSDL with
soapenc:Arraytypes - Generates proper SOAP 1.1 envelopes with CAdxCallContext
- Built-in
BasicAuthSecuritymatches X3 V12 auth pattern client.describe()enables runtime operation discovery- Handles XML entity encoding/decoding in resultXml automatically
- Promise-based API via
*Asyncmethods (getDescriptionAsync, readAsync, etc.)
Still needed: fast-xml-parser as companion
- Parse
resultXmlcontent when X3 returns XML (no JSON flag, or flag unsupported) - Fallback for any edge cases where JSON parsing fails
Caveats discovered:
statusreturns as string "1", not number — alwaysparseInt(status, 10)- Empty arrays (messages, technicalInfos) return as
undefined— always default to[] - The soap lib does NOT add
xsi:typeorencodingStyleattributes to individual elements — X3 may or may not require these (test with real server) - No
soapenc:arrayTypeattribute on array elements in the request — monitor for compatibility
Implementation Notes
SOAP client creation pattern:
const client = await soap.createClientAsync(wsdlUrl, {});
client.setSecurity(new soap.BasicAuthSecurity(user, password));
client.setEndpoint(soapEndpoint);
Calling X3 operations:
const [response] = await client.readAsync({
callContext: {
codeLang: "ENG",
codeUser: "",
password: "",
poolAlias: "SEED",
poolId: "",
requestConfig: "adxwss.optreturn=JSON",
},
publicName: "SIH",
objectKeys: [{ key: "NUM", value: "INV001" }],
});
const status = parseInt(response.readReturn.status, 10);
const resultXml = response.readReturn.resultXml;
const data = tryParseJson(resultXml) ?? parseXml(resultXml);
const messages = response.readReturn.messages ?? [];
Response normalization:
function normalizeResult(raw: any): SoapResult {
return {
status: parseInt(raw.status, 10),
data: tryParseJson(raw.resultXml) ?? parseXml(raw.resultXml),
messages: (raw.messages ?? []).map(normalizeMessage),
technicalInfos: (raw.technicalInfos ?? []).map(normalizeTechInfo),
};
}
Fallback Plan: Raw HTTP POST
If the soap library fails with a real X3 server (e.g., missing xsi:type attributes cause rejection), the fallback is:
const envelope = `<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:wss="http://www.adonix.com/WSS"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<soap:Body>
<wss:read soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<callContext xsi:type="wss:CAdxCallContext">
<codeLang xsi:type="xsd:string">ENG</codeLang>
<poolAlias xsi:type="xsd:string">SEED</poolAlias>
...
</callContext>
...
</wss:read>
</soap:Body>
</soap:Envelope>`;
const response = await fetch(soapEndpoint, {
method: "POST",
headers: {
"Content-Type": "text/xml; charset=utf-8",
"SOAPAction": '""',
"Authorization": `Basic ${btoa(`${user}:${password}`)}`,
},
body: envelope,
});
This approach gives full control over xsi:type annotations and encodingStyle attributes, but requires manual XML construction and parsing. Use only if the soap library proves incompatible with the real X3 server.