@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 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:** Long polling is automatically used in `create()` and `update()` methods of all `AdtObject` implementations to ensure objects are ready before proceeding with subsequent operations.
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,CAkEvB;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"}
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,CAiCvB"}
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 = typeof currentResponse.data === 'string'
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,CAuCvB;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"}
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,CAsDvB"}
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
@@ -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 abapLanguageVersion),
5
- * these helpers modify specific values in the original XML string obtained from GET.
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
- * Replace an XML attribute on a specific element.
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
- * Extract raw XML string from response data.
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;AAEH;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,MAAM,CAGR;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,MAAM,CAYR;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,MAAM,CAGR;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,MAAM,CAKR;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;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAGtD"}
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"}
@@ -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 abapLanguageVersion),
6
- * these helpers modify specific values in the original XML string obtained from GET.
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
- const regex = new RegExp(`${attrName}="[^"]*"`);
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
- if (newValue === '' || newValue === undefined) {
31
- return xml.replace(regex, `<${tagName}/>`);
32
- }
33
- return xml.replace(regex, `<${tagName}>${escapeXmlText(newValue)}</${tagName}>`);
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
- * Replace an XML attribute on a specific element.
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 regex = new RegExp(`(<${elementTag}[^>]*?)${attrName}="[^"]*"`);
41
- return xml.replace(regex, `$1${attrName}="${escapeXmlAttr(newValue)}"`);
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
- const regex = new RegExp(`<${tagName}[\\s\\S]*?</${tagName}>|<${tagName}\\s*/>`);
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
- * Extract raw XML string from response data.
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 === 'string')
64
- return data;
65
- return JSON.stringify(data);
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/adt-clients",
3
- "version": "10.0.2",
3
+ "version": "10.1.0",
4
4
  "description": "ADT clients for SAP ABAP systems - AdtClient and AdtRuntimeClient",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",