@mcp-abap-adt/adt-clients 10.0.2 → 10.1.0
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/README.md +17 -1
- package/dist/core/dataElement/update.d.ts.map +1 -1
- package/dist/core/dataElement/update.js +1 -1
- package/dist/core/domain/update.d.ts.map +1 -1
- package/dist/core/domain/update.js +1 -1
- package/dist/core/functionGroup/update.js +1 -3
- package/dist/core/package/update.d.ts.map +1 -1
- package/dist/core/package/update.js +1 -1
- package/dist/core/tabletype/update.d.ts.map +1 -1
- package/dist/core/tabletype/update.js +1 -1
- package/dist/utils/xmlPatch.d.ts +65 -6
- package/dist/utils/xmlPatch.d.ts.map +1 -1
- package/dist/utils/xmlPatch.js +117 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -364,7 +364,23 @@ implementations pass it internally on reads after create/update.
|
|
|
364
364
|
See the caveat above — on the system measured, the flag had no effect on object
|
|
365
365
|
reads at all.
|
|
366
366
|
|
|
367
|
-
**Note:**
|
|
367
|
+
**Note:** `create()` and `update()` request long polling on their follow-up reads
|
|
368
|
+
where the handler supports it. Read that as "asked for", not "ensured" — the
|
|
369
|
+
caveat above stands, and nothing in this library can make the system answer
|
|
370
|
+
sooner than it does.
|
|
371
|
+
|
|
372
|
+
What the library does guarantee is that a read which came back empty is not
|
|
373
|
+
written back. ADT answers a read of a not-yet-ready object with **HTTP 200 and
|
|
374
|
+
an empty body**, never a 404, so a read-modify-write update used to patch that
|
|
375
|
+
empty body — changing nothing, because there was nothing to change — and PUT the
|
|
376
|
+
result. Since 10.1.0 that read fails with `XmlPatchError`, naming the object:
|
|
377
|
+
|
|
378
|
+
```
|
|
379
|
+
Cannot update domain ZAC_DOM01: the read returned an empty body.
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
A slow system therefore surfaces as a read error, not as a write the server
|
|
383
|
+
rejects for a reason that points nowhere near the cause.
|
|
368
384
|
|
|
369
385
|
### Creating Behavior Implementation Classes
|
|
370
386
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/core/dataElement/update.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,OAAO,EACR,MAAM,0BAA0B,CAAC;AAgBlC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAIxD;;GAEG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,eAAe,EAC3B,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAmCjE;AAqID;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,wBAAwB,EAChC,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/core/dataElement/update.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,OAAO,EACR,MAAM,0BAA0B,CAAC;AAgBlC,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AAIxD;;GAEG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,eAAe,EAC3B,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAmCjE;AAqID;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,wBAAwB,EAChC,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,YAAY,CAAC,CAqEvB;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,eAAe,EAC3B,IAAI,EAAE,wBAAwB,EAC9B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EACnE,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,YAAY,CAAC,CAEvB"}
|
|
@@ -171,7 +171,7 @@ async function updateDataElement(connection, params, lockHandle, logger) {
|
|
|
171
171
|
timeout: (0, timeouts_1.getTimeout)('default'),
|
|
172
172
|
headers: { Accept: contentTypes_1.ACCEPT_DATA_ELEMENT },
|
|
173
173
|
});
|
|
174
|
-
const currentXml = (0, xmlPatch_1.extractXmlString)(currentResponse.data);
|
|
174
|
+
const currentXml = (0, xmlPatch_1.extractXmlString)(currentResponse.data, `data element ${params.data_element_name}`);
|
|
175
175
|
// 2. Patch only changed fields
|
|
176
176
|
const updatedXml = patchDataElementXml(currentXml, params);
|
|
177
177
|
// Debug: log XML when DEBUG_ADT_LIBS is enabled
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/core/domain/update.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAe9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAsEnD;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,eAAe,EAC3B,IAAI,EAAE,mBAAmB,EACzB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/core/domain/update.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAe9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAsEnD;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,UAAU,EAAE,eAAe,EAC3B,IAAI,EAAE,mBAAmB,EACzB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,CAAC,CAoCvB"}
|
|
@@ -69,7 +69,7 @@ async function updateDomain(connection, args, lockHandle) {
|
|
|
69
69
|
timeout: (0, timeouts_1.getTimeout)('default'),
|
|
70
70
|
headers: { Accept: contentTypes_1.ACCEPT_DOMAIN },
|
|
71
71
|
});
|
|
72
|
-
const currentXml = (0, xmlPatch_1.extractXmlString)(currentResponse.data);
|
|
72
|
+
const currentXml = (0, xmlPatch_1.extractXmlString)(currentResponse.data, `domain ${args.domain_name}`);
|
|
73
73
|
// 2. Patch only changed fields
|
|
74
74
|
const updatedXml = patchDomainXml(currentXml, args);
|
|
75
75
|
// 3. PUT
|
|
@@ -60,9 +60,7 @@ async function updateFunctionGroup(connection, params, contentTypes) {
|
|
|
60
60
|
}
|
|
61
61
|
// Get current XML
|
|
62
62
|
const currentResponse = await (0, read_1.getFunctionGroup)(connection, params.function_group_name, undefined);
|
|
63
|
-
const currentXml =
|
|
64
|
-
? currentResponse.data
|
|
65
|
-
: JSON.stringify(currentResponse.data);
|
|
63
|
+
const currentXml = (0, xmlPatch_1.extractXmlString)(currentResponse.data, `function group ${params.function_group_name}`);
|
|
66
64
|
// Update metadata
|
|
67
65
|
const updateResponse = await updateFunctionGroupMetadata(connection, params.function_group_name, currentXml, params.description, lockHandle, params.transport_request, contentTypes);
|
|
68
66
|
// Unlock if we locked it
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/core/package/update.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AA4ElC;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,oBAAoB,EAC5B,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/core/package/update.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EACZ,oBAAoB,EACrB,MAAM,0BAA0B,CAAC;AA4ElC;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,oBAAoB,EAC5B,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,CAAC,CA0CvB;AAED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,YAAY,CAAC,CAkBvB"}
|
|
@@ -61,7 +61,7 @@ async function updatePackage(connection, params, lockHandle) {
|
|
|
61
61
|
timeout: (0, timeouts_1.getTimeout)('default'),
|
|
62
62
|
headers: { Accept: contentTypes_1.ACCEPT_PACKAGE },
|
|
63
63
|
});
|
|
64
|
-
const currentXml = (0, xmlPatch_1.extractXmlString)(currentResponse.data);
|
|
64
|
+
const currentXml = (0, xmlPatch_1.extractXmlString)(currentResponse.data, `package ${params.package_name}`);
|
|
65
65
|
// 2. Patch only changed fields
|
|
66
66
|
const updatedXml = patchPackageXml(currentXml, params);
|
|
67
67
|
// 3. PUT
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/core/tabletype/update.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAEV,eAAe,EACf,YAAY,EACZ,OAAO,EACR,MAAM,0BAA0B,CAAC;AAalC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AA0CtD;;GAEG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,sBAAsB,EAC9B,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../src/core/tabletype/update.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAEV,eAAe,EACf,YAAY,EACZ,OAAO,EACR,MAAM,0BAA0B,CAAC;AAalC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AA0CtD;;GAEG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,sBAAsB,EAC9B,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,YAAY,CAAC,CAyDvB"}
|
|
@@ -83,7 +83,7 @@ async function updateTableType(connection, params, lockHandle, logger) {
|
|
|
83
83
|
// 1. GET current XML
|
|
84
84
|
const { getTableTypeMetadata } = await Promise.resolve().then(() => __importStar(require('./read')));
|
|
85
85
|
const currentResponse = await getTableTypeMetadata(connection, tableTypeName, undefined, logger);
|
|
86
|
-
const currentXml = (0, xmlPatch_1.extractXmlString)(currentResponse.data);
|
|
86
|
+
const currentXml = (0, xmlPatch_1.extractXmlString)(currentResponse.data, `table type ${params.tabletype_name}`);
|
|
87
87
|
// 2. Patch only changed fields
|
|
88
88
|
const updatedXml = patchTableTypeXml(currentXml, params);
|
|
89
89
|
// 3. PUT
|
package/dist/utils/xmlPatch.d.ts
CHANGED
|
@@ -1,27 +1,77 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* XML patch utilities for read-modify-write update pattern.
|
|
2
|
+
* XML patch utilities for the read-modify-write update pattern.
|
|
3
3
|
*
|
|
4
|
-
* Instead of building XML from scratch (which loses unknown fields like
|
|
5
|
-
* these helpers modify specific values in the original
|
|
4
|
+
* Instead of building XML from scratch (which loses unknown fields like
|
|
5
|
+
* abapLanguageVersion), these helpers modify specific values in the original
|
|
6
|
+
* XML string obtained from GET.
|
|
7
|
+
*
|
|
8
|
+
* **Every patch here fails loudly when it cannot find its target.** That is the
|
|
9
|
+
* point of this module's error handling, and it was learned from a live
|
|
10
|
+
* failure: a full test run reported
|
|
11
|
+
*
|
|
12
|
+
* ```
|
|
13
|
+
* update FAILED (HTTP 400): The description is missing for ZAC_DOM01
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* on a domain whose description was present in the configuration all along. The
|
|
17
|
+
* chain was:
|
|
18
|
+
*
|
|
19
|
+
* 1. the GET came back slow, or the object was not yet ready — ADT answers such
|
|
20
|
+
* reads with **200 and an empty body**, never a 404, so nothing upstream had
|
|
21
|
+
* a status to react to;
|
|
22
|
+
* 2. `patchXmlAttribute` was asked to set `adtcore:description` and found no
|
|
23
|
+
* attribute to replace. Being `String.replace`, it returned the input
|
|
24
|
+
* unchanged — silently;
|
|
25
|
+
* 3. the PUT went out without a description, and the server rejected it.
|
|
26
|
+
*
|
|
27
|
+
* The ABAP system's varying response time is the trigger, and nothing here can
|
|
28
|
+
* change that. What these helpers control is what a slow read turns into: a
|
|
29
|
+
* read error naming the object, or a malformed write blamed on the server. It
|
|
30
|
+
* used to be the second.
|
|
6
31
|
*/
|
|
32
|
+
/** Raised when a patch cannot find what it was asked to change. */
|
|
33
|
+
export declare class XmlPatchError extends Error {
|
|
34
|
+
constructor(message: string);
|
|
35
|
+
}
|
|
7
36
|
/**
|
|
8
37
|
* Replace an XML attribute value.
|
|
9
38
|
* Example: `adtcore:description="old"` → `adtcore:description="new"`
|
|
39
|
+
*
|
|
40
|
+
* @throws {XmlPatchError} when the attribute is absent
|
|
10
41
|
*/
|
|
11
42
|
export declare function patchXmlAttribute(xml: string, attrName: string, newValue: string): string;
|
|
12
43
|
/**
|
|
13
44
|
* Replace XML element text content.
|
|
14
45
|
* Handles both `<tag>content</tag>` and self-closing `<tag/>`.
|
|
46
|
+
*
|
|
47
|
+
* @throws {XmlPatchError} when the element is absent
|
|
15
48
|
*/
|
|
16
49
|
export declare function patchXmlElement(xml: string, tagName: string, newValue: string): string;
|
|
17
50
|
/**
|
|
18
|
-
*
|
|
51
|
+
* Set an XML attribute on a specific element.
|
|
19
52
|
* Example: `<pak:superPackage adtcore:name="OLD"/>` → `<pak:superPackage adtcore:name="NEW"/>`
|
|
53
|
+
*
|
|
54
|
+
* Unlike the other patches this one **adds** the attribute when the element is
|
|
55
|
+
* there without it, because that is a state ADT genuinely produces for an unset
|
|
56
|
+
* reference — probed on a live system:
|
|
57
|
+
*
|
|
58
|
+
* ```
|
|
59
|
+
* <doma:valueTableRef/> a domain with no value table
|
|
60
|
+
* <pak:superPackage/> a package with no parent
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* Refusing those would forbid ever setting a value table or a super package for
|
|
64
|
+
* the first time. Only a missing *element* means the XML is not what the caller
|
|
65
|
+
* thinks it is, and only that throws.
|
|
66
|
+
*
|
|
67
|
+
* @throws {XmlPatchError} when the element itself is absent
|
|
20
68
|
*/
|
|
21
69
|
export declare function patchXmlElementAttribute(xml: string, elementTag: string, attrName: string, newValue: string): string;
|
|
22
70
|
/**
|
|
23
71
|
* Replace an entire XML block (from opening to closing tag) with new content.
|
|
24
72
|
* Handles both `<tag>...</tag>` (including nested content) and self-closing `<tag/>`.
|
|
73
|
+
*
|
|
74
|
+
* @throws {XmlPatchError} when the block is absent
|
|
25
75
|
*/
|
|
26
76
|
export declare function patchXmlBlock(xml: string, tagName: string, newContent: string): string;
|
|
27
77
|
/**
|
|
@@ -29,7 +79,16 @@ export declare function patchXmlBlock(xml: string, tagName: string, newContent:
|
|
|
29
79
|
*/
|
|
30
80
|
export declare function patchIf<T>(xml: string, value: T | undefined | null, patchFn: (xml: string, val: T) => string): string;
|
|
31
81
|
/**
|
|
32
|
-
*
|
|
82
|
+
* Take the XML out of a response, refusing anything that cannot be patched.
|
|
83
|
+
*
|
|
84
|
+
* This used to `JSON.stringify` a non-string body and hand back the result,
|
|
85
|
+
* which is not XML and matches no patch — turning a bad read into a bad write
|
|
86
|
+
* one step earlier than the patches did. An empty body passed through just as
|
|
87
|
+
* quietly, and an empty body is exactly what ADT returns for an object that is
|
|
88
|
+
* not ready yet.
|
|
89
|
+
*
|
|
90
|
+
* @param what object being read, named in the error (e.g. `'domain ZAC_DOM01'`)
|
|
91
|
+
* @throws {XmlPatchError} when the body is not usable XML
|
|
33
92
|
*/
|
|
34
|
-
export declare function extractXmlString(data: unknown): string;
|
|
93
|
+
export declare function extractXmlString(data: unknown, what?: string): string;
|
|
35
94
|
//# sourceMappingURL=xmlPatch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xmlPatch.d.ts","sourceRoot":"","sources":["../../src/utils/xmlPatch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"xmlPatch.d.ts","sourceRoot":"","sources":["../../src/utils/xmlPatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,mEAAmE;AACnE,qBAAa,aAAc,SAAQ,KAAK;gBAC1B,OAAO,EAAE,MAAM;CAI5B;AA2BD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,MAAM,CAOR;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,MAAM,CASR;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,MAAM,CAuBR;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,MAAM,CAOR;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,CAAC,EACvB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAC3B,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,MAAM,GACvC,MAAM,CAGR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,SAAW,GAAG,MAAM,CAiBvE"}
|
package/dist/utils/xmlPatch.js
CHANGED
|
@@ -1,52 +1,132 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* XML patch utilities for read-modify-write update pattern.
|
|
3
|
+
* XML patch utilities for the read-modify-write update pattern.
|
|
4
4
|
*
|
|
5
|
-
* Instead of building XML from scratch (which loses unknown fields like
|
|
6
|
-
* these helpers modify specific values in the original
|
|
5
|
+
* Instead of building XML from scratch (which loses unknown fields like
|
|
6
|
+
* abapLanguageVersion), these helpers modify specific values in the original
|
|
7
|
+
* XML string obtained from GET.
|
|
8
|
+
*
|
|
9
|
+
* **Every patch here fails loudly when it cannot find its target.** That is the
|
|
10
|
+
* point of this module's error handling, and it was learned from a live
|
|
11
|
+
* failure: a full test run reported
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* update FAILED (HTTP 400): The description is missing for ZAC_DOM01
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* on a domain whose description was present in the configuration all along. The
|
|
18
|
+
* chain was:
|
|
19
|
+
*
|
|
20
|
+
* 1. the GET came back slow, or the object was not yet ready — ADT answers such
|
|
21
|
+
* reads with **200 and an empty body**, never a 404, so nothing upstream had
|
|
22
|
+
* a status to react to;
|
|
23
|
+
* 2. `patchXmlAttribute` was asked to set `adtcore:description` and found no
|
|
24
|
+
* attribute to replace. Being `String.replace`, it returned the input
|
|
25
|
+
* unchanged — silently;
|
|
26
|
+
* 3. the PUT went out without a description, and the server rejected it.
|
|
27
|
+
*
|
|
28
|
+
* The ABAP system's varying response time is the trigger, and nothing here can
|
|
29
|
+
* change that. What these helpers control is what a slow read turns into: a
|
|
30
|
+
* read error naming the object, or a malformed write blamed on the server. It
|
|
31
|
+
* used to be the second.
|
|
7
32
|
*/
|
|
8
33
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.XmlPatchError = void 0;
|
|
9
35
|
exports.patchXmlAttribute = patchXmlAttribute;
|
|
10
36
|
exports.patchXmlElement = patchXmlElement;
|
|
11
37
|
exports.patchXmlElementAttribute = patchXmlElementAttribute;
|
|
12
38
|
exports.patchXmlBlock = patchXmlBlock;
|
|
13
39
|
exports.patchIf = patchIf;
|
|
14
40
|
exports.extractXmlString = extractXmlString;
|
|
41
|
+
/** Raised when a patch cannot find what it was asked to change. */
|
|
42
|
+
class XmlPatchError extends Error {
|
|
43
|
+
constructor(message) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = 'XmlPatchError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.XmlPatchError = XmlPatchError;
|
|
49
|
+
/**
|
|
50
|
+
* Apply a replacement, refusing to pass the XML through untouched.
|
|
51
|
+
*
|
|
52
|
+
* A caller reaches a patch only when it intends the change — `patchIf` skips
|
|
53
|
+
* the call entirely for an absent value — so "no match" always means the PUT
|
|
54
|
+
* would not carry what the caller asked for. There is no case where quietly
|
|
55
|
+
* returning the input is the right answer.
|
|
56
|
+
*/
|
|
57
|
+
function replaceOrThrow(xml, regex, replacement, target) {
|
|
58
|
+
if (!regex.test(xml)) {
|
|
59
|
+
throw new XmlPatchError(`Cannot patch ${target}: it is not present in the XML being updated. ` +
|
|
60
|
+
'This usually means the preceding read returned an empty or partial ' +
|
|
61
|
+
'body — ADT answers a read of a not-yet-ready object with HTTP 200 and ' +
|
|
62
|
+
'no content rather than an error.');
|
|
63
|
+
}
|
|
64
|
+
return xml.replace(regex, replacement);
|
|
65
|
+
}
|
|
15
66
|
/**
|
|
16
67
|
* Replace an XML attribute value.
|
|
17
68
|
* Example: `adtcore:description="old"` → `adtcore:description="new"`
|
|
69
|
+
*
|
|
70
|
+
* @throws {XmlPatchError} when the attribute is absent
|
|
18
71
|
*/
|
|
19
72
|
function patchXmlAttribute(xml, attrName, newValue) {
|
|
20
|
-
|
|
21
|
-
return xml.replace(regex, `${attrName}="${escapeXmlAttr(newValue)}"`);
|
|
73
|
+
return replaceOrThrow(xml, new RegExp(`${attrName}="[^"]*"`), `${attrName}="${escapeXmlAttr(newValue)}"`, `attribute ${attrName}`);
|
|
22
74
|
}
|
|
23
75
|
/**
|
|
24
76
|
* Replace XML element text content.
|
|
25
77
|
* Handles both `<tag>content</tag>` and self-closing `<tag/>`.
|
|
78
|
+
*
|
|
79
|
+
* @throws {XmlPatchError} when the element is absent
|
|
26
80
|
*/
|
|
27
81
|
function patchXmlElement(xml, tagName, newValue) {
|
|
28
|
-
// Match <tag>...</tag> or <tag/> (self-closing)
|
|
29
82
|
const regex = new RegExp(`<${tagName}>([^<]*)</${tagName}>|<${tagName}\\s*/>`);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return xml
|
|
83
|
+
const replacement = newValue === '' || newValue === undefined
|
|
84
|
+
? `<${tagName}/>`
|
|
85
|
+
: `<${tagName}>${escapeXmlText(newValue)}</${tagName}>`;
|
|
86
|
+
return replaceOrThrow(xml, regex, replacement, `element <${tagName}>`);
|
|
34
87
|
}
|
|
35
88
|
/**
|
|
36
|
-
*
|
|
89
|
+
* Set an XML attribute on a specific element.
|
|
37
90
|
* Example: `<pak:superPackage adtcore:name="OLD"/>` → `<pak:superPackage adtcore:name="NEW"/>`
|
|
91
|
+
*
|
|
92
|
+
* Unlike the other patches this one **adds** the attribute when the element is
|
|
93
|
+
* there without it, because that is a state ADT genuinely produces for an unset
|
|
94
|
+
* reference — probed on a live system:
|
|
95
|
+
*
|
|
96
|
+
* ```
|
|
97
|
+
* <doma:valueTableRef/> a domain with no value table
|
|
98
|
+
* <pak:superPackage/> a package with no parent
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* Refusing those would forbid ever setting a value table or a super package for
|
|
102
|
+
* the first time. Only a missing *element* means the XML is not what the caller
|
|
103
|
+
* thinks it is, and only that throws.
|
|
104
|
+
*
|
|
105
|
+
* @throws {XmlPatchError} when the element itself is absent
|
|
38
106
|
*/
|
|
39
107
|
function patchXmlElementAttribute(xml, elementTag, attrName, newValue) {
|
|
40
|
-
const
|
|
41
|
-
|
|
108
|
+
const value = escapeXmlAttr(newValue);
|
|
109
|
+
const withAttribute = new RegExp(`(<${elementTag}[^>]*?)${attrName}="[^"]*"`);
|
|
110
|
+
if (withAttribute.test(xml)) {
|
|
111
|
+
return xml.replace(withAttribute, `$1${attrName}="${value}"`);
|
|
112
|
+
}
|
|
113
|
+
const element = new RegExp(`<${elementTag}(\\s[^>]*?)?(/?)>`);
|
|
114
|
+
if (element.test(xml)) {
|
|
115
|
+
return xml.replace(element, (_match, attrs, selfClosing) => `<${elementTag}${attrs ?? ''} ${attrName}="${value}"${selfClosing}>`);
|
|
116
|
+
}
|
|
117
|
+
throw new XmlPatchError(`Cannot patch attribute ${attrName}: element <${elementTag}> is not present ` +
|
|
118
|
+
'in the XML being updated. This usually means the preceding read returned ' +
|
|
119
|
+
'an empty or partial body — ADT answers a read of a not-yet-ready object ' +
|
|
120
|
+
'with HTTP 200 and no content rather than an error.');
|
|
42
121
|
}
|
|
43
122
|
/**
|
|
44
123
|
* Replace an entire XML block (from opening to closing tag) with new content.
|
|
45
124
|
* Handles both `<tag>...</tag>` (including nested content) and self-closing `<tag/>`.
|
|
125
|
+
*
|
|
126
|
+
* @throws {XmlPatchError} when the block is absent
|
|
46
127
|
*/
|
|
47
128
|
function patchXmlBlock(xml, tagName, newContent) {
|
|
48
|
-
|
|
49
|
-
return xml.replace(regex, newContent);
|
|
129
|
+
return replaceOrThrow(xml, new RegExp(`<${tagName}[\\s\\S]*?</${tagName}>|<${tagName}\\s*/>`), newContent, `block <${tagName}>`);
|
|
50
130
|
}
|
|
51
131
|
/**
|
|
52
132
|
* Conditionally apply a patch only if value is defined.
|
|
@@ -57,12 +137,29 @@ function patchIf(xml, value, patchFn) {
|
|
|
57
137
|
return patchFn(xml, value);
|
|
58
138
|
}
|
|
59
139
|
/**
|
|
60
|
-
*
|
|
140
|
+
* Take the XML out of a response, refusing anything that cannot be patched.
|
|
141
|
+
*
|
|
142
|
+
* This used to `JSON.stringify` a non-string body and hand back the result,
|
|
143
|
+
* which is not XML and matches no patch — turning a bad read into a bad write
|
|
144
|
+
* one step earlier than the patches did. An empty body passed through just as
|
|
145
|
+
* quietly, and an empty body is exactly what ADT returns for an object that is
|
|
146
|
+
* not ready yet.
|
|
147
|
+
*
|
|
148
|
+
* @param what object being read, named in the error (e.g. `'domain ZAC_DOM01'`)
|
|
149
|
+
* @throws {XmlPatchError} when the body is not usable XML
|
|
61
150
|
*/
|
|
62
|
-
function extractXmlString(data) {
|
|
63
|
-
if (typeof data
|
|
64
|
-
|
|
65
|
-
|
|
151
|
+
function extractXmlString(data, what = 'object') {
|
|
152
|
+
if (typeof data !== 'string') {
|
|
153
|
+
const kind = data === null || data === undefined ? String(data) : typeof data;
|
|
154
|
+
throw new XmlPatchError(`Cannot update ${what}: the read returned ${kind}, not XML.`);
|
|
155
|
+
}
|
|
156
|
+
const xml = data.trim();
|
|
157
|
+
if (xml === '' || !xml.startsWith('<')) {
|
|
158
|
+
throw new XmlPatchError(`Cannot update ${what}: the read returned ${xml === '' ? 'an empty body' : 'a body that is not XML'}. ` +
|
|
159
|
+
'ADT answers a read of a not-yet-ready object with HTTP 200 and no ' +
|
|
160
|
+
'content, so the status alone does not show this.');
|
|
161
|
+
}
|
|
162
|
+
return data;
|
|
66
163
|
}
|
|
67
164
|
/** Escape special characters for XML attribute values */
|
|
68
165
|
function escapeXmlAttr(value) {
|