@abapify/adt-rfc 0.4.1 → 0.4.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/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +11 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/transport/types.ts","../src/lib/client/rfc-client.ts","../src/lib/transport/soap-rfc.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/lib/transport/types.ts","../src/lib/client/rfc-client.ts","../src/lib/transport/soap-rfc.ts"],"mappings":";;;;;;;;KAQY;;UAGK;GACd,gBAAgB,YAAY,eAAe;;;KAIlC,WAAW;;KAGX,YAAY,eAAe,YAAY,eAAe;;KAGtD,cAAc,eAAe,YAAY,eAAe;;;;UAKnD;;EAEf;;EAEA,SAAS;;;;;cAME,qBAAqB;WAEd;WACA;WACA;EAFA,YAAA,mBACA,qBACA;;;;;;;cAYP,gCAAgC;WAEzB;WACA;WACA;EAFA,YAAA,gBACA,aACA;;;;;;;;;KC/BR,cACV,aACA;EACE;EACA,UAAU;EACV;MAEC;UAEY;;;;;;;EAOf,OAAO;;;;;EAMP;;UAGe;;;;;;;;EAQf,KACE,gBACA,SAAS,WACT,UAAU,iBACT,QAAQ;;;;;;;;;;;;;;;;;iBAwBG,gBAAgB,QAAQ,kBAAkB;;;;cC7D7C;;cAGA;;;;;;;;;iBAsDG,qBACd,gBACA,SAAQ;;;;;;;iBAmLM,qBAAqB,cAAc"}
|
package/dist/index.mjs
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* SOAP fault raised when the server returns a `soap:Fault`.
|
|
4
4
|
*/
|
|
5
5
|
var RfcSoapFault = class extends Error {
|
|
6
|
+
faultcode;
|
|
7
|
+
faultstring;
|
|
8
|
+
raw;
|
|
6
9
|
constructor(faultcode, faultstring, raw) {
|
|
7
10
|
super(`SOAP Fault: ${faultcode}: ${faultstring}`);
|
|
8
11
|
this.faultcode = faultcode;
|
|
@@ -17,6 +20,9 @@ var RfcSoapFault = class extends Error {
|
|
|
17
20
|
* against systems where SOAP-RFC is disabled.
|
|
18
21
|
*/
|
|
19
22
|
var RfcTransportUnavailable = class extends Error {
|
|
23
|
+
status;
|
|
24
|
+
url;
|
|
25
|
+
body;
|
|
20
26
|
constructor(status, url, body) {
|
|
21
27
|
super(`SOAP-RFC transport unavailable at ${url}: HTTP ${status}. The endpoint /sap/bc/soap/rfc may be disabled on this SAP system.`);
|
|
22
28
|
this.status = status;
|
|
@@ -212,7 +218,9 @@ function parseRfcSoapResponse(xml) {
|
|
|
212
218
|
const fault = body.node.children.find((c) => c.name.toLowerCase() === "fault");
|
|
213
219
|
if (fault) {
|
|
214
220
|
const faultObj = nodeToValue(fault.node);
|
|
215
|
-
|
|
221
|
+
const faultcode = String(faultObj.faultcode ?? "Server");
|
|
222
|
+
const faultstring = String(faultObj.faultstring ?? "Unknown SOAP fault");
|
|
223
|
+
throw new RfcSoapFault(faultcode, faultstring, xml);
|
|
216
224
|
}
|
|
217
225
|
const rfcRoot = body.node.children[0];
|
|
218
226
|
if (!rfcRoot) return {};
|
|
@@ -284,7 +292,8 @@ function createRfcClient(config) {
|
|
|
284
292
|
}
|
|
285
293
|
return String(value);
|
|
286
294
|
};
|
|
287
|
-
|
|
295
|
+
const xml = coerceToXml(response);
|
|
296
|
+
return parseRfcSoapResponse(xml);
|
|
288
297
|
} };
|
|
289
298
|
}
|
|
290
299
|
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/lib/transport/types.ts","../src/lib/transport/soap-rfc.ts","../src/lib/client/rfc-client.ts"],"sourcesContent":["/**\n * RFC transport primitives.\n *\n * Flat value types kept deliberately loose — SAP RFC function modules\n * accept scalars, structures (objects), and tables (arrays of objects).\n */\n\n/** A scalar RFC value — string, number, boolean, or bytes (base64) — or null. */\nexport type RfcScalar = string | number | boolean | null;\n\n/** A structure — flat field->scalar mapping. Nested structures allowed. */\nexport interface RfcStructure {\n [field: string]: RfcScalar | RfcStructure | RfcTable;\n}\n\n/** A table — array of structures. */\nexport type RfcTable = RfcStructure[];\n\n/** Top-level RFC parameter map. */\nexport type RfcParams = Record<string, RfcScalar | RfcStructure | RfcTable>;\n\n/** Top-level RFC response map (same shape). */\nexport type RfcResponse = Record<string, RfcScalar | RfcStructure | RfcTable>;\n\n/**\n * Options for one RFC call.\n */\nexport interface RfcCallOptions {\n /** Explicit sap-client override (otherwise uses the client's default). */\n client?: string;\n /** Abort signal. */\n signal?: AbortSignal;\n}\n\n/**\n * SOAP fault raised when the server returns a `soap:Fault`.\n */\nexport class RfcSoapFault extends Error {\n constructor(\n public readonly faultcode: string,\n public readonly faultstring: string,\n public readonly raw: string,\n ) {\n super(`SOAP Fault: ${faultcode}: ${faultstring}`);\n this.name = 'RfcSoapFault';\n }\n}\n\n/**\n * Raised when the HTTP endpoint /sap/bc/soap/rfc is unavailable\n * (401/403/404) — useful for gracefully skipping real-e2e tests\n * against systems where SOAP-RFC is disabled.\n */\nexport class RfcTransportUnavailable extends Error {\n constructor(\n public readonly status: number,\n public readonly url: string,\n public readonly body: string,\n ) {\n super(\n `SOAP-RFC transport unavailable at ${url}: HTTP ${status}. ` +\n `The endpoint /sap/bc/soap/rfc may be disabled on this SAP system.`,\n );\n this.name = 'RfcTransportUnavailable';\n }\n}\n","/**\n * SOAP-over-HTTP RFC transport for SAP.\n *\n * Wraps an RFC call in a SOAP 1.1 envelope targeted at\n * `/sap/bc/soap/rfc?sap-client=<n>` — the SAP NetWeaver built-in\n * wrapper around the NW RFC library. Avoids the native `libsapnwrfc`\n * dependency of node-rfc at the cost of a slightly restricted\n * parameter type space (scalars, structures, tables).\n *\n * No third-party XML parser is used — SAP's SOAP output for RFC is\n * well-formed, stable, and simple enough to handle with a minimal\n * hand-rolled parser (see {@link parseRfcSoapResponse}).\n *\n * Reference: https://wiki.scn.sap.com/wiki/display/Snippets/Calling+RFC+enabled+Function+Modules+via+SOAP\n */\n\nimport {\n RfcSoapFault,\n type RfcParams,\n type RfcResponse,\n type RfcScalar,\n type RfcStructure,\n type RfcTable,\n} from './types';\n\n/** SAP RFC SOAP namespace — fixed. */\nexport const RFC_SOAP_NS = 'urn:sap-com:document:sap:rfc:functions';\n\n/** SOAP 1.1 envelope namespace. */\nexport const SOAP_ENV_NS = 'http://schemas.xmlsoap.org/soap/envelope/';\n\n// ────────────────────────────────────────────────────────────────────────────\n// Envelope builder\n// ────────────────────────────────────────────────────────────────────────────\n\nfunction escapeXml(value: string): string {\n return value\n .replaceAll(/&/g, '&')\n .replaceAll(/</g, '<')\n .replaceAll(/>/g, '>');\n}\n\nfunction renderScalar(value: RfcScalar): string {\n if (value === null || value === undefined) return '';\n if (typeof value === 'boolean') return value ? 'X' : ' ';\n return escapeXml(String(value));\n}\n\nfunction renderStructure(obj: RfcStructure): string {\n let out = '';\n for (const [field, v] of Object.entries(obj)) {\n out += renderField(field, v);\n }\n return out;\n}\n\nfunction renderTable(name: string, rows: RfcTable): string {\n // SAP RFC SOAP convention: <TABLE_NAME><item>…</item><item>…</item></TABLE_NAME>\n const items = rows\n .map((row) => `<item>${renderStructure(row)}</item>`)\n .join('');\n return `<${name}>${items}</${name}>`;\n}\n\nfunction renderField(\n name: string,\n value: RfcScalar | RfcStructure | RfcTable,\n): string {\n if (Array.isArray(value)) return renderTable(name, value);\n if (value !== null && typeof value === 'object') {\n return `<${name}>${renderStructure(value)}</${name}>`;\n }\n return `<${name}>${renderScalar(value)}</${name}>`;\n}\n\n/**\n * Build the SOAP request envelope for an RFC function module call.\n *\n * @param fmName - The RFC function module name (will be upper-cased).\n * @param params - Flat map of importing / changing / tables parameters.\n * @returns XML string suitable for `POST /sap/bc/soap/rfc` with\n * `Content-Type: text/xml; charset=utf-8`.\n */\nexport function buildRfcSoapEnvelope(\n fmName: string,\n params: RfcParams = {},\n): string {\n const fm = fmName.toUpperCase();\n const body = renderStructure(params as RfcStructure);\n\n return (\n `<?xml version=\"1.0\" encoding=\"utf-8\"?>` +\n `<soap-env:Envelope xmlns:soap-env=\"${SOAP_ENV_NS}\">` +\n `<soap-env:Body>` +\n `<urn:${fm} xmlns:urn=\"${RFC_SOAP_NS}\">` +\n body +\n `</urn:${fm}>` +\n `</soap-env:Body>` +\n `</soap-env:Envelope>`\n );\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Envelope parser — minimal, regex + stack based\n// ────────────────────────────────────────────────────────────────────────────\n\ninterface Token {\n kind: 'open' | 'close' | 'self' | 'text';\n name?: string;\n text?: string;\n}\n\n// Linear tokenizer via split on '<' + indexOf('>'). Zero regex on the\n// hot path means CodeQL cannot flag it as polynomial-redos and the\n// runtime is guaranteed O(N).\nconst TAG_NAME_RE = /^\\/?([A-Za-z_][\\w.:-]*)/;\n\nfunction tagNameOf(inside: string): string | undefined {\n const m = TAG_NAME_RE.exec(inside);\n return m?.[1];\n}\n\nfunction tokenize(xml: string): Token[] {\n const tokens: Token[] = [];\n const parts = xml.split('<');\n // parts[0] is whatever text precedes the first '<'.\n if (parts[0] && parts[0].trim().length > 0) {\n tokens.push({ kind: 'text', text: parts[0] });\n }\n for (let i = 1; i < parts.length; i++) {\n const part = parts[i];\n const gt = part.indexOf('>');\n if (gt < 0) {\n // malformed (no '>'): treat everything as literal text\n if (part.trim().length > 0) {\n tokens.push({ kind: 'text', text: '<' + part });\n }\n continue;\n }\n const inside = part.slice(0, gt);\n const trailing = part.slice(gt + 1);\n // Skip XML prolog (<?...?>), DOCTYPE, comments (<!-- -->), and any\n // directive (<!...) — non-structural for RFC parsing.\n if (\n inside.startsWith('?') ||\n inside.startsWith('!--') ||\n inside.startsWith('!DOCTYPE') ||\n inside.startsWith('!')\n ) {\n if (trailing && trailing.trim().length > 0) {\n tokens.push({ kind: 'text', text: trailing });\n }\n continue;\n }\n const name = tagNameOf(inside);\n if (!name) {\n tokens.push({ kind: 'text', text: '<' + inside + '>' });\n } else if (inside.startsWith('/')) {\n tokens.push({ kind: 'close', name });\n } else if (inside.endsWith('/')) {\n tokens.push({ kind: 'self', name });\n } else {\n tokens.push({ kind: 'open', name });\n }\n if (trailing && trailing.trim().length > 0) {\n tokens.push({ kind: 'text', text: trailing });\n }\n }\n return tokens;\n}\n\nfunction localName(qname: string): string {\n const i = qname.indexOf(':');\n return i < 0 ? qname : qname.slice(i + 1);\n}\n\nfunction unescapeXml(s: string): string {\n return s\n .replaceAll(/</g, '<')\n .replaceAll(/>/g, '>')\n .replaceAll(/"/g, '\"')\n .replaceAll(/'/g, \"'\")\n .replaceAll(/&/g, '&');\n}\n\ninterface ParsedNode {\n text: string;\n children: Array<{ name: string; node: ParsedNode }>;\n}\n\nfunction newNode(): ParsedNode {\n return { text: '', children: [] };\n}\n\n/**\n * Parse an XML fragment (no `<?xml …?>` prolog required) into a tree of\n * `ParsedNode`s keyed by local element name. Repeated element names are\n * preserved as separate `children` entries (so `<item>` tables parse as\n * multiple children with the same name).\n */\nfunction parseXmlFragment(xml: string): ParsedNode {\n const tokens = tokenize(xml);\n const root = newNode();\n const stack: ParsedNode[] = [root];\n\n for (const tok of tokens) {\n const top = stack.at(-1)!;\n if (tok.kind === 'open') {\n const child = newNode();\n top.children.push({ name: localName(tok.name!), node: child });\n stack.push(child);\n } else if (tok.kind === 'close') {\n stack.pop();\n } else if (tok.kind === 'self') {\n const child = newNode();\n top.children.push({ name: localName(tok.name!), node: child });\n } else if (tok.kind === 'text') {\n top.text += tok.text ?? '';\n }\n }\n return root;\n}\n\n/**\n * Convert a `ParsedNode` into a JS value:\n * - leaf → string (unescaped)\n * - mixed children with all name=\"item\" → array of objects (table)\n * - object otherwise (structure)\n */\nfunction nodeToValue(node: ParsedNode): RfcScalar | RfcStructure | RfcTable {\n if (node.children.length === 0) {\n return unescapeXml(node.text.trim());\n }\n // table detection: all children named \"item\" (case-insensitive)\n const allItems =\n node.children.length > 0 &&\n node.children.every((c) => c.name.toLowerCase() === 'item');\n if (allItems) {\n return node.children.map((c) => nodeToValue(c.node) as RfcStructure);\n }\n // structure: collapse children into an object; duplicate names → arrays\n const obj: RfcStructure = {};\n for (const { name, node: child } of node.children) {\n const val = nodeToValue(child);\n if (name in obj) {\n const existing = obj[name];\n if (Array.isArray(existing)) {\n existing.push(val as RfcStructure);\n } else {\n obj[name] = [existing as unknown as RfcStructure, val as RfcStructure];\n }\n } else {\n obj[name] = val;\n }\n }\n return obj;\n}\n\n/**\n * Parse a SOAP-RFC response envelope and return the RFC response params.\n *\n * @param xml - Raw response body as returned by `/sap/bc/soap/rfc`.\n * @throws {@link RfcSoapFault} if the envelope contains a `soap:Fault`.\n */\nexport function parseRfcSoapResponse(xml: string): RfcResponse {\n const root = parseXmlFragment(xml);\n\n // Find soap:Envelope/soap:Body\n const envelope = root.children.find(\n (c) => c.name.toLowerCase() === 'envelope',\n );\n if (!envelope) {\n throw new Error(\n 'Invalid SOAP response: no <Envelope> element found. Got: ' +\n xml.slice(0, 200),\n );\n }\n const body = envelope.node.children.find(\n (c) => c.name.toLowerCase() === 'body',\n );\n if (!body) {\n throw new Error('Invalid SOAP response: no <Body> element inside envelope');\n }\n\n // Check for Fault first\n const fault = body.node.children.find(\n (c) => c.name.toLowerCase() === 'fault',\n );\n if (fault) {\n const faultObj = nodeToValue(fault.node) as RfcStructure;\n const faultcode = String(\n (faultObj.faultcode as string | number | undefined) ?? 'Server',\n );\n const faultstring = String(\n (faultObj.faultstring as string | number | undefined) ??\n 'Unknown SOAP fault',\n );\n throw new RfcSoapFault(faultcode, faultstring, xml);\n }\n\n // The RFC response is the single non-fault child of <Body>.\n // SAP names it either `<FMNAME.Response>` or `<FMNAMEResponse>` depending\n // on system version.\n const rfcRoot = body.node.children[0];\n if (!rfcRoot) return {};\n\n const result = nodeToValue(rfcRoot.node);\n if (result === null || typeof result !== 'object') {\n // degenerate scalar response (string, number, boolean, null)\n return {};\n }\n if (Array.isArray(result)) {\n // should not happen for an RFC response root\n return { items: result };\n }\n return result;\n}\n","/**\n * High-level RFC client — SOAP-over-HTTP transport by default.\n *\n * The client is deliberately decoupled from `@abapify/adt-client` at the\n * type level: callers pass a *fetcher* function matching the minimal\n * shape of `AdtClient.fetch()`. This keeps `adt-rfc` a leaf package\n * with no workspace dependencies (and avoids a circular graph once the\n * CLI wires the two together).\n */\n\nimport {\n buildRfcSoapEnvelope,\n parseRfcSoapResponse,\n} from '../transport/soap-rfc';\nimport {\n RfcTransportUnavailable,\n type RfcCallOptions,\n type RfcParams,\n type RfcResponse,\n} from '../transport/types';\n\n/**\n * Raw fetcher signature the RFC client depends on.\n * Matches `AdtClient.fetch()` — callers pass\n * `(url, options) => client.fetch(url, options)`.\n */\nexport type RawFetcher = (\n url: string,\n options: {\n method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n headers?: Record<string, string>;\n body?: string;\n },\n) => Promise<unknown>;\n\nexport interface RfcClientConfig {\n /**\n * A fetcher that performs authenticated HTTP requests against the SAP\n * system. Must already handle CSRF / session cookies.\n *\n * Signature matches `AdtClient.fetch()`.\n */\n fetch: RawFetcher;\n\n /**\n * Default `sap-client` (e.g. \"100\") appended as query parameter.\n * Optional — most SAP deployments can infer it from the cookie.\n */\n client?: string;\n}\n\nexport interface RfcClient {\n /**\n * Invoke an RFC function module synchronously.\n *\n * @param fmName - Function module name (case-insensitive).\n * @param params - Importing / changing / tables parameters (flat map).\n * @param options - Per-call overrides.\n */\n call(\n fmName: string,\n params?: RfcParams,\n options?: RfcCallOptions,\n ): Promise<RfcResponse>;\n}\n\nfunction buildSoapRfcUrl(client?: string): string {\n const base = '/sap/bc/soap/rfc';\n if (!client) return base;\n return `${base}?sap-client=${encodeURIComponent(client)}`;\n}\n\n/**\n * Create an RFC client backed by the SOAP-over-HTTP transport.\n *\n * @example\n * ```ts\n * import { createAdtClient } from '@abapify/adt-client';\n * import { createRfcClient } from '@abapify/adt-rfc';\n *\n * const adt = createAdtClient({ …auth });\n * const rfc = createRfcClient({ fetch: (url, opts) => adt.fetch(url, opts) });\n *\n * const resp = await rfc.call('STFC_CONNECTION', { REQUTEXT: 'hello' });\n * console.log(resp.ECHOTEXT); // \"hello\"\n * ```\n */\nexport function createRfcClient(config: RfcClientConfig): RfcClient {\n return {\n async call(\n fmName: string,\n params: RfcParams = {},\n options: RfcCallOptions = {},\n ): Promise<RfcResponse> {\n const envelope = buildRfcSoapEnvelope(fmName, params);\n const url = buildSoapRfcUrl(options.client ?? config.client);\n\n let response: unknown;\n try {\n response = await config.fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'text/xml; charset=utf-8',\n // SAP expects SOAPAction — empty is fine for RFC SOAP wrapper.\n SOAPAction: '\"\"',\n Accept: 'text/xml',\n },\n body: envelope,\n });\n } catch (err) {\n // Detect HTTP-level \"endpoint not available\" errors raised by the\n // underlying fetcher. `AdtClient.fetch()` throws errors whose\n // `message` usually contains the status code.\n const msg = err instanceof Error ? err.message : String(err);\n const statusMatch = /\\b(401|403|404|501)\\b/.exec(msg);\n if (statusMatch) {\n throw new RfcTransportUnavailable(Number(statusMatch[1]), url, msg);\n }\n throw err;\n }\n\n // Fallback coercion of the fetch result to XML text. JSON.stringify\n // can throw on circular refs or BigInt; guard against that so an\n // unrelated serialization error doesn't mask the real SOAP failure\n // downstream in parseRfcSoapResponse.\n const coerceToXml = (value: unknown): string => {\n if (typeof value === 'string') return value;\n if (value === null || value === undefined) return '';\n if (typeof value === 'object') {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n };\n const xml = coerceToXml(response);\n return parseRfcSoapResponse(xml);\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/lib/transport/types.ts","../src/lib/transport/soap-rfc.ts","../src/lib/client/rfc-client.ts"],"sourcesContent":["/**\n * RFC transport primitives.\n *\n * Flat value types kept deliberately loose — SAP RFC function modules\n * accept scalars, structures (objects), and tables (arrays of objects).\n */\n\n/** A scalar RFC value — string, number, boolean, or bytes (base64) — or null. */\nexport type RfcScalar = string | number | boolean | null;\n\n/** A structure — flat field->scalar mapping. Nested structures allowed. */\nexport interface RfcStructure {\n [field: string]: RfcScalar | RfcStructure | RfcTable;\n}\n\n/** A table — array of structures. */\nexport type RfcTable = RfcStructure[];\n\n/** Top-level RFC parameter map. */\nexport type RfcParams = Record<string, RfcScalar | RfcStructure | RfcTable>;\n\n/** Top-level RFC response map (same shape). */\nexport type RfcResponse = Record<string, RfcScalar | RfcStructure | RfcTable>;\n\n/**\n * Options for one RFC call.\n */\nexport interface RfcCallOptions {\n /** Explicit sap-client override (otherwise uses the client's default). */\n client?: string;\n /** Abort signal. */\n signal?: AbortSignal;\n}\n\n/**\n * SOAP fault raised when the server returns a `soap:Fault`.\n */\nexport class RfcSoapFault extends Error {\n constructor(\n public readonly faultcode: string,\n public readonly faultstring: string,\n public readonly raw: string,\n ) {\n super(`SOAP Fault: ${faultcode}: ${faultstring}`);\n this.name = 'RfcSoapFault';\n }\n}\n\n/**\n * Raised when the HTTP endpoint /sap/bc/soap/rfc is unavailable\n * (401/403/404) — useful for gracefully skipping real-e2e tests\n * against systems where SOAP-RFC is disabled.\n */\nexport class RfcTransportUnavailable extends Error {\n constructor(\n public readonly status: number,\n public readonly url: string,\n public readonly body: string,\n ) {\n super(\n `SOAP-RFC transport unavailable at ${url}: HTTP ${status}. ` +\n `The endpoint /sap/bc/soap/rfc may be disabled on this SAP system.`,\n );\n this.name = 'RfcTransportUnavailable';\n }\n}\n","/**\n * SOAP-over-HTTP RFC transport for SAP.\n *\n * Wraps an RFC call in a SOAP 1.1 envelope targeted at\n * `/sap/bc/soap/rfc?sap-client=<n>` — the SAP NetWeaver built-in\n * wrapper around the NW RFC library. Avoids the native `libsapnwrfc`\n * dependency of node-rfc at the cost of a slightly restricted\n * parameter type space (scalars, structures, tables).\n *\n * No third-party XML parser is used — SAP's SOAP output for RFC is\n * well-formed, stable, and simple enough to handle with a minimal\n * hand-rolled parser (see {@link parseRfcSoapResponse}).\n *\n * Reference: https://wiki.scn.sap.com/wiki/display/Snippets/Calling+RFC+enabled+Function+Modules+via+SOAP\n */\n\nimport {\n RfcSoapFault,\n type RfcParams,\n type RfcResponse,\n type RfcScalar,\n type RfcStructure,\n type RfcTable,\n} from './types';\n\n/** SAP RFC SOAP namespace — fixed. */\nexport const RFC_SOAP_NS = 'urn:sap-com:document:sap:rfc:functions';\n\n/** SOAP 1.1 envelope namespace. */\nexport const SOAP_ENV_NS = 'http://schemas.xmlsoap.org/soap/envelope/';\n\n// ────────────────────────────────────────────────────────────────────────────\n// Envelope builder\n// ────────────────────────────────────────────────────────────────────────────\n\nfunction escapeXml(value: string): string {\n return value\n .replaceAll(/&/g, '&')\n .replaceAll(/</g, '<')\n .replaceAll(/>/g, '>');\n}\n\nfunction renderScalar(value: RfcScalar): string {\n if (value === null || value === undefined) return '';\n if (typeof value === 'boolean') return value ? 'X' : ' ';\n return escapeXml(String(value));\n}\n\nfunction renderStructure(obj: RfcStructure): string {\n let out = '';\n for (const [field, v] of Object.entries(obj)) {\n out += renderField(field, v);\n }\n return out;\n}\n\nfunction renderTable(name: string, rows: RfcTable): string {\n // SAP RFC SOAP convention: <TABLE_NAME><item>…</item><item>…</item></TABLE_NAME>\n const items = rows\n .map((row) => `<item>${renderStructure(row)}</item>`)\n .join('');\n return `<${name}>${items}</${name}>`;\n}\n\nfunction renderField(\n name: string,\n value: RfcScalar | RfcStructure | RfcTable,\n): string {\n if (Array.isArray(value)) return renderTable(name, value);\n if (value !== null && typeof value === 'object') {\n return `<${name}>${renderStructure(value)}</${name}>`;\n }\n return `<${name}>${renderScalar(value)}</${name}>`;\n}\n\n/**\n * Build the SOAP request envelope for an RFC function module call.\n *\n * @param fmName - The RFC function module name (will be upper-cased).\n * @param params - Flat map of importing / changing / tables parameters.\n * @returns XML string suitable for `POST /sap/bc/soap/rfc` with\n * `Content-Type: text/xml; charset=utf-8`.\n */\nexport function buildRfcSoapEnvelope(\n fmName: string,\n params: RfcParams = {},\n): string {\n const fm = fmName.toUpperCase();\n const body = renderStructure(params as RfcStructure);\n\n return (\n `<?xml version=\"1.0\" encoding=\"utf-8\"?>` +\n `<soap-env:Envelope xmlns:soap-env=\"${SOAP_ENV_NS}\">` +\n `<soap-env:Body>` +\n `<urn:${fm} xmlns:urn=\"${RFC_SOAP_NS}\">` +\n body +\n `</urn:${fm}>` +\n `</soap-env:Body>` +\n `</soap-env:Envelope>`\n );\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Envelope parser — minimal, regex + stack based\n// ────────────────────────────────────────────────────────────────────────────\n\ninterface Token {\n kind: 'open' | 'close' | 'self' | 'text';\n name?: string;\n text?: string;\n}\n\n// Linear tokenizer via split on '<' + indexOf('>'). Zero regex on the\n// hot path means CodeQL cannot flag it as polynomial-redos and the\n// runtime is guaranteed O(N).\nconst TAG_NAME_RE = /^\\/?([A-Za-z_][\\w.:-]*)/;\n\nfunction tagNameOf(inside: string): string | undefined {\n const m = TAG_NAME_RE.exec(inside);\n return m?.[1];\n}\n\nfunction tokenize(xml: string): Token[] {\n const tokens: Token[] = [];\n const parts = xml.split('<');\n // parts[0] is whatever text precedes the first '<'.\n if (parts[0] && parts[0].trim().length > 0) {\n tokens.push({ kind: 'text', text: parts[0] });\n }\n for (let i = 1; i < parts.length; i++) {\n const part = parts[i];\n const gt = part.indexOf('>');\n if (gt < 0) {\n // malformed (no '>'): treat everything as literal text\n if (part.trim().length > 0) {\n tokens.push({ kind: 'text', text: '<' + part });\n }\n continue;\n }\n const inside = part.slice(0, gt);\n const trailing = part.slice(gt + 1);\n // Skip XML prolog (<?...?>), DOCTYPE, comments (<!-- -->), and any\n // directive (<!...) — non-structural for RFC parsing.\n if (\n inside.startsWith('?') ||\n inside.startsWith('!--') ||\n inside.startsWith('!DOCTYPE') ||\n inside.startsWith('!')\n ) {\n if (trailing && trailing.trim().length > 0) {\n tokens.push({ kind: 'text', text: trailing });\n }\n continue;\n }\n const name = tagNameOf(inside);\n if (!name) {\n tokens.push({ kind: 'text', text: '<' + inside + '>' });\n } else if (inside.startsWith('/')) {\n tokens.push({ kind: 'close', name });\n } else if (inside.endsWith('/')) {\n tokens.push({ kind: 'self', name });\n } else {\n tokens.push({ kind: 'open', name });\n }\n if (trailing && trailing.trim().length > 0) {\n tokens.push({ kind: 'text', text: trailing });\n }\n }\n return tokens;\n}\n\nfunction localName(qname: string): string {\n const i = qname.indexOf(':');\n return i < 0 ? qname : qname.slice(i + 1);\n}\n\nfunction unescapeXml(s: string): string {\n return s\n .replaceAll(/</g, '<')\n .replaceAll(/>/g, '>')\n .replaceAll(/"/g, '\"')\n .replaceAll(/'/g, \"'\")\n .replaceAll(/&/g, '&');\n}\n\ninterface ParsedNode {\n text: string;\n children: Array<{ name: string; node: ParsedNode }>;\n}\n\nfunction newNode(): ParsedNode {\n return { text: '', children: [] };\n}\n\n/**\n * Parse an XML fragment (no `<?xml …?>` prolog required) into a tree of\n * `ParsedNode`s keyed by local element name. Repeated element names are\n * preserved as separate `children` entries (so `<item>` tables parse as\n * multiple children with the same name).\n */\nfunction parseXmlFragment(xml: string): ParsedNode {\n const tokens = tokenize(xml);\n const root = newNode();\n const stack: ParsedNode[] = [root];\n\n for (const tok of tokens) {\n const top = stack.at(-1)!;\n if (tok.kind === 'open') {\n const child = newNode();\n top.children.push({ name: localName(tok.name!), node: child });\n stack.push(child);\n } else if (tok.kind === 'close') {\n stack.pop();\n } else if (tok.kind === 'self') {\n const child = newNode();\n top.children.push({ name: localName(tok.name!), node: child });\n } else if (tok.kind === 'text') {\n top.text += tok.text ?? '';\n }\n }\n return root;\n}\n\n/**\n * Convert a `ParsedNode` into a JS value:\n * - leaf → string (unescaped)\n * - mixed children with all name=\"item\" → array of objects (table)\n * - object otherwise (structure)\n */\nfunction nodeToValue(node: ParsedNode): RfcScalar | RfcStructure | RfcTable {\n if (node.children.length === 0) {\n return unescapeXml(node.text.trim());\n }\n // table detection: all children named \"item\" (case-insensitive)\n const allItems =\n node.children.length > 0 &&\n node.children.every((c) => c.name.toLowerCase() === 'item');\n if (allItems) {\n return node.children.map((c) => nodeToValue(c.node) as RfcStructure);\n }\n // structure: collapse children into an object; duplicate names → arrays\n const obj: RfcStructure = {};\n for (const { name, node: child } of node.children) {\n const val = nodeToValue(child);\n if (name in obj) {\n const existing = obj[name];\n if (Array.isArray(existing)) {\n existing.push(val as RfcStructure);\n } else {\n obj[name] = [existing as unknown as RfcStructure, val as RfcStructure];\n }\n } else {\n obj[name] = val;\n }\n }\n return obj;\n}\n\n/**\n * Parse a SOAP-RFC response envelope and return the RFC response params.\n *\n * @param xml - Raw response body as returned by `/sap/bc/soap/rfc`.\n * @throws {@link RfcSoapFault} if the envelope contains a `soap:Fault`.\n */\nexport function parseRfcSoapResponse(xml: string): RfcResponse {\n const root = parseXmlFragment(xml);\n\n // Find soap:Envelope/soap:Body\n const envelope = root.children.find(\n (c) => c.name.toLowerCase() === 'envelope',\n );\n if (!envelope) {\n throw new Error(\n 'Invalid SOAP response: no <Envelope> element found. Got: ' +\n xml.slice(0, 200),\n );\n }\n const body = envelope.node.children.find(\n (c) => c.name.toLowerCase() === 'body',\n );\n if (!body) {\n throw new Error('Invalid SOAP response: no <Body> element inside envelope');\n }\n\n // Check for Fault first\n const fault = body.node.children.find(\n (c) => c.name.toLowerCase() === 'fault',\n );\n if (fault) {\n const faultObj = nodeToValue(fault.node) as RfcStructure;\n const faultcode = String(\n (faultObj.faultcode as string | number | undefined) ?? 'Server',\n );\n const faultstring = String(\n (faultObj.faultstring as string | number | undefined) ??\n 'Unknown SOAP fault',\n );\n throw new RfcSoapFault(faultcode, faultstring, xml);\n }\n\n // The RFC response is the single non-fault child of <Body>.\n // SAP names it either `<FMNAME.Response>` or `<FMNAMEResponse>` depending\n // on system version.\n const rfcRoot = body.node.children[0];\n if (!rfcRoot) return {};\n\n const result = nodeToValue(rfcRoot.node);\n if (result === null || typeof result !== 'object') {\n // degenerate scalar response (string, number, boolean, null)\n return {};\n }\n if (Array.isArray(result)) {\n // should not happen for an RFC response root\n return { items: result };\n }\n return result;\n}\n","/**\n * High-level RFC client — SOAP-over-HTTP transport by default.\n *\n * The client is deliberately decoupled from `@abapify/adt-client` at the\n * type level: callers pass a *fetcher* function matching the minimal\n * shape of `AdtClient.fetch()`. This keeps `adt-rfc` a leaf package\n * with no workspace dependencies (and avoids a circular graph once the\n * CLI wires the two together).\n */\n\nimport {\n buildRfcSoapEnvelope,\n parseRfcSoapResponse,\n} from '../transport/soap-rfc';\nimport {\n RfcTransportUnavailable,\n type RfcCallOptions,\n type RfcParams,\n type RfcResponse,\n} from '../transport/types';\n\n/**\n * Raw fetcher signature the RFC client depends on.\n * Matches `AdtClient.fetch()` — callers pass\n * `(url, options) => client.fetch(url, options)`.\n */\nexport type RawFetcher = (\n url: string,\n options: {\n method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n headers?: Record<string, string>;\n body?: string;\n },\n) => Promise<unknown>;\n\nexport interface RfcClientConfig {\n /**\n * A fetcher that performs authenticated HTTP requests against the SAP\n * system. Must already handle CSRF / session cookies.\n *\n * Signature matches `AdtClient.fetch()`.\n */\n fetch: RawFetcher;\n\n /**\n * Default `sap-client` (e.g. \"100\") appended as query parameter.\n * Optional — most SAP deployments can infer it from the cookie.\n */\n client?: string;\n}\n\nexport interface RfcClient {\n /**\n * Invoke an RFC function module synchronously.\n *\n * @param fmName - Function module name (case-insensitive).\n * @param params - Importing / changing / tables parameters (flat map).\n * @param options - Per-call overrides.\n */\n call(\n fmName: string,\n params?: RfcParams,\n options?: RfcCallOptions,\n ): Promise<RfcResponse>;\n}\n\nfunction buildSoapRfcUrl(client?: string): string {\n const base = '/sap/bc/soap/rfc';\n if (!client) return base;\n return `${base}?sap-client=${encodeURIComponent(client)}`;\n}\n\n/**\n * Create an RFC client backed by the SOAP-over-HTTP transport.\n *\n * @example\n * ```ts\n * import { createAdtClient } from '@abapify/adt-client';\n * import { createRfcClient } from '@abapify/adt-rfc';\n *\n * const adt = createAdtClient({ …auth });\n * const rfc = createRfcClient({ fetch: (url, opts) => adt.fetch(url, opts) });\n *\n * const resp = await rfc.call('STFC_CONNECTION', { REQUTEXT: 'hello' });\n * console.log(resp.ECHOTEXT); // \"hello\"\n * ```\n */\nexport function createRfcClient(config: RfcClientConfig): RfcClient {\n return {\n async call(\n fmName: string,\n params: RfcParams = {},\n options: RfcCallOptions = {},\n ): Promise<RfcResponse> {\n const envelope = buildRfcSoapEnvelope(fmName, params);\n const url = buildSoapRfcUrl(options.client ?? config.client);\n\n let response: unknown;\n try {\n response = await config.fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'text/xml; charset=utf-8',\n // SAP expects SOAPAction — empty is fine for RFC SOAP wrapper.\n SOAPAction: '\"\"',\n Accept: 'text/xml',\n },\n body: envelope,\n });\n } catch (err) {\n // Detect HTTP-level \"endpoint not available\" errors raised by the\n // underlying fetcher. `AdtClient.fetch()` throws errors whose\n // `message` usually contains the status code.\n const msg = err instanceof Error ? err.message : String(err);\n const statusMatch = /\\b(401|403|404|501)\\b/.exec(msg);\n if (statusMatch) {\n throw new RfcTransportUnavailable(Number(statusMatch[1]), url, msg);\n }\n throw err;\n }\n\n // Fallback coercion of the fetch result to XML text. JSON.stringify\n // can throw on circular refs or BigInt; guard against that so an\n // unrelated serialization error doesn't mask the real SOAP failure\n // downstream in parseRfcSoapResponse.\n const coerceToXml = (value: unknown): string => {\n if (typeof value === 'string') return value;\n if (value === null || value === undefined) return '';\n if (typeof value === 'object') {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n };\n const xml = coerceToXml(response);\n return parseRfcSoapResponse(xml);\n },\n };\n}\n"],"mappings":";;;;AAqCA,IAAa,eAAb,cAAkC,MAAM;CAEpB;CACA;CACA;CAHlB,YACE,AAAgB,WAChB,AAAgB,aAChB,AAAgB,KAChB;EACA,MAAM,eAAe,UAAU,IAAI,aAAa;EAJhC;EACA;EACA;EAGhB,KAAK,OAAO;CACd;AACF;;;;;;AAOA,IAAa,0BAAb,cAA6C,MAAM;CAE/B;CACA;CACA;CAHlB,YACE,AAAgB,QAChB,AAAgB,KAChB,AAAgB,MAChB;EACA,MACE,qCAAqC,IAAI,SAAS,OAAO,oEAE3D;EAPgB;EACA;EACA;EAMhB,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;;;;;;;;;ACvCA,MAAa,cAAc;;AAG3B,MAAa,cAAc;AAM3B,SAAS,UAAU,OAAuB;CACxC,OAAO,MACJ,WAAW,MAAM,OAAO,CAAC,CACzB,WAAW,MAAM,MAAM,CAAC,CACxB,WAAW,MAAM,MAAM;AAC5B;AAEA,SAAS,aAAa,OAA0B;CAC9C,IAAI,UAAU,QAAQ,UAAU,QAAW,OAAO;CAClD,IAAI,OAAO,UAAU,WAAW,OAAO,QAAQ,MAAM;CACrD,OAAO,UAAU,OAAO,KAAK,CAAC;AAChC;AAEA,SAAS,gBAAgB,KAA2B;CAClD,IAAI,MAAM;CACV,KAAK,MAAM,CAAC,OAAO,MAAM,OAAO,QAAQ,GAAG,GACzC,OAAO,YAAY,OAAO,CAAC;CAE7B,OAAO;AACT;AAEA,SAAS,YAAY,MAAc,MAAwB;CAKzD,OAAO,IAAI,KAAK,GAHF,KACX,KAAK,QAAQ,SAAS,gBAAgB,GAAG,EAAE,QAAQ,CAAC,CACpD,KAAK,EACe,EAAE,IAAI,KAAK;AACpC;AAEA,SAAS,YACP,MACA,OACQ;CACR,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,YAAY,MAAM,KAAK;CACxD,IAAI,UAAU,QAAQ,OAAO,UAAU,UACrC,OAAO,IAAI,KAAK,GAAG,gBAAgB,KAAK,EAAE,IAAI,KAAK;CAErD,OAAO,IAAI,KAAK,GAAG,aAAa,KAAK,EAAE,IAAI,KAAK;AAClD;;;;;;;;;AAUA,SAAgB,qBACd,QACA,SAAoB,CAAC,GACb;CACR,MAAM,KAAK,OAAO,YAAY;CAC9B,MAAM,OAAO,gBAAgB,MAAsB;CAEnD,OACE,4EACsC,YAAY,wBAE1C,GAAG,cAAc,YAAY,MACrC,OACA,SAAS,GAAG;AAIhB;AAeA,MAAM,cAAc;AAEpB,SAAS,UAAU,QAAoC;CAErD,OADU,YAAY,KAAK,MACpB,CAAC,GAAG;AACb;AAEA,SAAS,SAAS,KAAsB;CACtC,MAAM,SAAkB,CAAC;CACzB,MAAM,QAAQ,IAAI,MAAM,GAAG;CAE3B,IAAI,MAAM,MAAM,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,SAAS,GACvC,OAAO,KAAK;EAAE,MAAM;EAAQ,MAAM,MAAM;CAAG,CAAC;CAE9C,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM;EACnB,MAAM,KAAK,KAAK,QAAQ,GAAG;EAC3B,IAAI,KAAK,GAAG;GAEV,IAAI,KAAK,KAAK,CAAC,CAAC,SAAS,GACvB,OAAO,KAAK;IAAE,MAAM;IAAQ,MAAM,MAAM;GAAK,CAAC;GAEhD;EACF;EACA,MAAM,SAAS,KAAK,MAAM,GAAG,EAAE;EAC/B,MAAM,WAAW,KAAK,MAAM,KAAK,CAAC;EAGlC,IACE,OAAO,WAAW,GAAG,KACrB,OAAO,WAAW,KAAK,KACvB,OAAO,WAAW,UAAU,KAC5B,OAAO,WAAW,GAAG,GACrB;GACA,IAAI,YAAY,SAAS,KAAK,CAAC,CAAC,SAAS,GACvC,OAAO,KAAK;IAAE,MAAM;IAAQ,MAAM;GAAS,CAAC;GAE9C;EACF;EACA,MAAM,OAAO,UAAU,MAAM;EAC7B,IAAI,CAAC,MACH,OAAO,KAAK;GAAE,MAAM;GAAQ,MAAM,MAAM,SAAS;EAAI,CAAC;OACjD,IAAI,OAAO,WAAW,GAAG,GAC9B,OAAO,KAAK;GAAE,MAAM;GAAS;EAAK,CAAC;OAC9B,IAAI,OAAO,SAAS,GAAG,GAC5B,OAAO,KAAK;GAAE,MAAM;GAAQ;EAAK,CAAC;OAElC,OAAO,KAAK;GAAE,MAAM;GAAQ;EAAK,CAAC;EAEpC,IAAI,YAAY,SAAS,KAAK,CAAC,CAAC,SAAS,GACvC,OAAO,KAAK;GAAE,MAAM;GAAQ,MAAM;EAAS,CAAC;CAEhD;CACA,OAAO;AACT;AAEA,SAAS,UAAU,OAAuB;CACxC,MAAM,IAAI,MAAM,QAAQ,GAAG;CAC3B,OAAO,IAAI,IAAI,QAAQ,MAAM,MAAM,IAAI,CAAC;AAC1C;AAEA,SAAS,YAAY,GAAmB;CACtC,OAAO,EACJ,WAAW,SAAS,GAAG,CAAC,CACxB,WAAW,SAAS,GAAG,CAAC,CACxB,WAAW,WAAW,IAAG,CAAC,CAC1B,WAAW,WAAW,GAAG,CAAC,CAC1B,WAAW,UAAU,GAAG;AAC7B;AAOA,SAAS,UAAsB;CAC7B,OAAO;EAAE,MAAM;EAAI,UAAU,CAAC;CAAE;AAClC;;;;;;;AAQA,SAAS,iBAAiB,KAAyB;CACjD,MAAM,SAAS,SAAS,GAAG;CAC3B,MAAM,OAAO,QAAQ;CACrB,MAAM,QAAsB,CAAC,IAAI;CAEjC,KAAK,MAAM,OAAO,QAAQ;EACxB,MAAM,MAAM,MAAM,GAAG,EAAE;EACvB,IAAI,IAAI,SAAS,QAAQ;GACvB,MAAM,QAAQ,QAAQ;GACtB,IAAI,SAAS,KAAK;IAAE,MAAM,UAAU,IAAI,IAAK;IAAG,MAAM;GAAM,CAAC;GAC7D,MAAM,KAAK,KAAK;EAClB,OAAO,IAAI,IAAI,SAAS,SACtB,MAAM,IAAI;OACL,IAAI,IAAI,SAAS,QAAQ;GAC9B,MAAM,QAAQ,QAAQ;GACtB,IAAI,SAAS,KAAK;IAAE,MAAM,UAAU,IAAI,IAAK;IAAG,MAAM;GAAM,CAAC;EAC/D,OAAO,IAAI,IAAI,SAAS,QACtB,IAAI,QAAQ,IAAI,QAAQ;CAE5B;CACA,OAAO;AACT;;;;;;;AAQA,SAAS,YAAY,MAAuD;CAC1E,IAAI,KAAK,SAAS,WAAW,GAC3B,OAAO,YAAY,KAAK,KAAK,KAAK,CAAC;CAMrC,IAFE,KAAK,SAAS,SAAS,KACvB,KAAK,SAAS,OAAO,MAAM,EAAE,KAAK,YAAY,MAAM,MAAM,GAE1D,OAAO,KAAK,SAAS,KAAK,MAAM,YAAY,EAAE,IAAI,CAAiB;CAGrE,MAAM,MAAoB,CAAC;CAC3B,KAAK,MAAM,EAAE,MAAM,MAAM,WAAW,KAAK,UAAU;EACjD,MAAM,MAAM,YAAY,KAAK;EAC7B,IAAI,QAAQ,KAAK;GACf,MAAM,WAAW,IAAI;GACrB,IAAI,MAAM,QAAQ,QAAQ,GACxB,SAAS,KAAK,GAAmB;QAEjC,IAAI,QAAQ,CAAC,UAAqC,GAAmB;EAEzE,OACE,IAAI,QAAQ;CAEhB;CACA,OAAO;AACT;;;;;;;AAQA,SAAgB,qBAAqB,KAA0B;CAI7D,MAAM,WAHO,iBAAiB,GAGV,CAAC,CAAC,SAAS,MAC5B,MAAM,EAAE,KAAK,YAAY,MAAM,UAClC;CACA,IAAI,CAAC,UACH,MAAM,IAAI,MACR,8DACE,IAAI,MAAM,GAAG,GAAG,CACpB;CAEF,MAAM,OAAO,SAAS,KAAK,SAAS,MACjC,MAAM,EAAE,KAAK,YAAY,MAAM,MAClC;CACA,IAAI,CAAC,MACH,MAAM,IAAI,MAAM,0DAA0D;CAI5E,MAAM,QAAQ,KAAK,KAAK,SAAS,MAC9B,MAAM,EAAE,KAAK,YAAY,MAAM,OAClC;CACA,IAAI,OAAO;EACT,MAAM,WAAW,YAAY,MAAM,IAAI;EACvC,MAAM,YAAY,OACf,SAAS,aAA6C,QACzD;EACA,MAAM,cAAc,OACjB,SAAS,eACR,oBACJ;EACA,MAAM,IAAI,aAAa,WAAW,aAAa,GAAG;CACpD;CAKA,MAAM,UAAU,KAAK,KAAK,SAAS;CACnC,IAAI,CAAC,SAAS,OAAO,CAAC;CAEtB,MAAM,SAAS,YAAY,QAAQ,IAAI;CACvC,IAAI,WAAW,QAAQ,OAAO,WAAW,UAEvC,OAAO,CAAC;CAEV,IAAI,MAAM,QAAQ,MAAM,GAEtB,OAAO,EAAE,OAAO,OAAO;CAEzB,OAAO;AACT;;;;;;;;;;;;;AC1PA,SAAS,gBAAgB,QAAyB;CAChD,MAAM,OAAO;CACb,IAAI,CAAC,QAAQ,OAAO;CACpB,OAAO,GAAG,KAAK,cAAc,mBAAmB,MAAM;AACxD;;;;;;;;;;;;;;;;AAiBA,SAAgB,gBAAgB,QAAoC;CAClE,OAAO,EACL,MAAM,KACJ,QACA,SAAoB,CAAC,GACrB,UAA0B,CAAC,GACL;EACtB,MAAM,WAAW,qBAAqB,QAAQ,MAAM;EACpD,MAAM,MAAM,gBAAgB,QAAQ,UAAU,OAAO,MAAM;EAE3D,IAAI;EACJ,IAAI;GACF,WAAW,MAAM,OAAO,MAAM,KAAK;IACjC,QAAQ;IACR,SAAS;KACP,gBAAgB;KAEhB,YAAY;KACZ,QAAQ;IACV;IACA,MAAM;GACR,CAAC;EACH,SAAS,KAAK;GAIZ,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;GAC3D,MAAM,cAAc,wBAAwB,KAAK,GAAG;GACpD,IAAI,aACF,MAAM,IAAI,wBAAwB,OAAO,YAAY,EAAE,GAAG,KAAK,GAAG;GAEpE,MAAM;EACR;EAMA,MAAM,eAAe,UAA2B;GAC9C,IAAI,OAAO,UAAU,UAAU,OAAO;GACtC,IAAI,UAAU,QAAQ,UAAU,QAAW,OAAO;GAClD,IAAI,OAAO,UAAU,UACnB,IAAI;IACF,OAAO,KAAK,UAAU,KAAK;GAC7B,QAAQ;IACN,OAAO,OAAO,KAAK;GACrB;GAEF,OAAO,OAAO,KAAK;EACrB;EACA,MAAM,MAAM,YAAY,QAAQ;EAChC,OAAO,qBAAqB,GAAG;CACjC,EACF;AACF"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.4.
|
|
6
|
+
"version": "0.4.3",
|
|
7
7
|
"description": "SAP RFC transport for @abapify — SOAP-over-HTTP RFC implementation (/sap/bc/soap/rfc)",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"types": "./dist/index.d.mts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"abap",
|
|
24
24
|
"startrfc"
|
|
25
25
|
],
|
|
26
|
-
"author": "
|
|
26
|
+
"author": "abapify",
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"repository": {
|
|
29
29
|
"type": "git",
|