@mcp-abap-adt/adt-clients 32.0.0 → 32.2.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.
Files changed (63) hide show
  1. package/README.md +29 -1
  2. package/dist/batch/AdtBatchConnection.d.ts +29 -0
  3. package/dist/batch/AdtBatchConnection.d.ts.map +1 -0
  4. package/dist/batch/AdtBatchConnection.js +155 -0
  5. package/dist/batch/index.d.ts +2 -0
  6. package/dist/batch/index.d.ts.map +1 -0
  7. package/dist/batch/index.js +5 -0
  8. package/dist/batch/wire.d.ts +10 -0
  9. package/dist/batch/wire.d.ts.map +1 -0
  10. package/dist/batch/wire.js +88 -0
  11. package/dist/core/accessControl/versions.d.ts +1 -1
  12. package/dist/core/accessControl/versions.js +1 -1
  13. package/dist/core/appendStructure/versions.d.ts +1 -1
  14. package/dist/core/appendStructure/versions.js +1 -1
  15. package/dist/core/behaviorDefinition/versions.d.ts +1 -1
  16. package/dist/core/behaviorDefinition/versions.js +1 -1
  17. package/dist/core/behaviorImplementation/versions.d.ts +1 -1
  18. package/dist/core/behaviorImplementation/versions.js +1 -1
  19. package/dist/core/enhancement/versions.js +1 -1
  20. package/dist/core/functionGroup/create.js +1 -1
  21. package/dist/core/functionInclude/versions.js +1 -1
  22. package/dist/core/functionModule/versions.js +1 -1
  23. package/dist/core/scalarFunction/versions.d.ts +1 -1
  24. package/dist/core/scalarFunction/versions.js +1 -1
  25. package/dist/core/scalarFunctionImplementation/versions.d.ts +1 -1
  26. package/dist/core/scalarFunctionImplementation/versions.js +1 -1
  27. package/dist/core/service/AdtService.js +2 -2
  28. package/dist/core/service/lock.d.ts +1 -1
  29. package/dist/core/service/lock.js +1 -1
  30. package/dist/core/service/types.d.ts +2 -2
  31. package/dist/core/serviceDefinition/versions.d.ts +1 -1
  32. package/dist/core/serviceDefinition/versions.js +1 -1
  33. package/dist/core/structure/versions.d.ts +1 -1
  34. package/dist/core/structure/versions.js +1 -1
  35. package/dist/core/tabletype/versions.d.ts +1 -1
  36. package/dist/core/tabletype/versions.js +1 -1
  37. package/dist/core/transformation/versions.d.ts +1 -1
  38. package/dist/core/transformation/versions.js +1 -1
  39. package/dist/core/transport/AdtRequest.d.ts +2 -2
  40. package/dist/core/transport/AdtRequest.js +2 -2
  41. package/dist/core/transport/objects.d.ts +1 -1
  42. package/dist/core/transport/objects.js +1 -1
  43. package/dist/endpoints/objects.d.ts +5 -0
  44. package/dist/endpoints/objects.d.ts.map +1 -1
  45. package/dist/endpoints/objects.js +6 -1
  46. package/dist/index.d.ts +1 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +3 -1
  49. package/dist/runtime/debugger/abap.d.ts.map +1 -1
  50. package/dist/runtime/debugger/abap.js +28 -40
  51. package/dist/runtime/debugger/batchPayload.d.ts +2 -16
  52. package/dist/runtime/debugger/batchPayload.d.ts.map +1 -1
  53. package/dist/runtime/debugger/batchPayload.js +5 -28
  54. package/dist/runtime/dumps/read.d.ts +1 -1
  55. package/dist/runtime/dumps/read.js +1 -1
  56. package/dist/runtime/index.d.ts +1 -0
  57. package/dist/runtime/index.d.ts.map +1 -1
  58. package/dist/runtime/index.js +3 -0
  59. package/docs/usage/BATCH.md +160 -0
  60. package/docs/usage/CLIENT_API_REFERENCE.md +6 -6
  61. package/docs/usage/ERRATA.md +1 -1
  62. package/docs/usage/STATEFUL_SESSION_GUIDE.md +2 -2
  63. package/package.json +1 -1
@@ -0,0 +1,160 @@
1
+ # Recording batch transport
2
+
3
+ `AdtBatchConnection` implements `IAbapConnection`. Each member still issues one
4
+ request; the wrapper records it and settles its promise after `execute()`.
5
+ Do not await recorded calls before executing: their answers do not exist yet.
6
+ The constructor requires `endpoint: 'communication' | 'debugger'` and accepts
7
+ `logger?: ILogger`. It delegates connection initialization, base URL and session
8
+ identity to the wrapped connection. Session switches while recording are local.
9
+
10
+ ```typescript
11
+ import { AdtBatchConnection, AdtClient, utilDocuments } from '@mcp-abap-adt/adt-clients';
12
+ import { analyseActivation, utilActivationRunId } from '@mcp-abap-adt/adt-strategies';
13
+
14
+ const batch = new AdtBatchConnection(connection, { endpoint: 'communication' });
15
+ const domains = new AdtClient(batch).getDomain();
16
+ // configs: { domainName, packageName, transportRequest, description }[]
17
+ const created = configs.map(config => domains.create(config));
18
+ const locked = configs.map(config => domains.lock(config));
19
+ await batch.execute();
20
+ const handles = await Promise.all(created.map(async (pending, i) => {
21
+ const creation = await pending;
22
+ if (!creation.ok) throw new Error(creation.getError().message);
23
+ const lock = await locked[i];
24
+ if (!lock.ok) throw new Error(lock.getError().message);
25
+ return lock.getResult().value;
26
+ }));
27
+ const writes = configs.map((config, i) =>
28
+ domains.updateMetadata(config, {
29
+ source: documents[i], // Full domain XML, including datatype CHAR and length 000001.
30
+ lockHandle: handles[i],
31
+ }),
32
+ );
33
+ const unlocked = configs.map((config, i) => domains.unlock(config, handles[i]));
34
+ await batch.execute();
35
+ for (const answer of await Promise.all([...writes, ...unlocked])) {
36
+ if (!answer.ok) throw new Error(answer.getError().message);
37
+ }
38
+ const utils = new AdtClient(connection).getUtils({
39
+ ...utilDocuments,
40
+ activation: utilActivationRunId, // Reads the run id from Location.
41
+ });
42
+ const started = await utils.activateObjectsGroup(
43
+ configs.map(config => ({ type: 'DOMA', name: config.domainName })),
44
+ false,
45
+ { analyse: analyseActivation },
46
+ );
47
+ if (!started.ok) throw new Error(started.getError().message);
48
+ const runId = started.getResult().value;
49
+ if (!runId) throw new Error('Activation did not return a run id');
50
+ const deadline = Date.now() + 300_000;
51
+ // Polling and waiting belong to the consumer.
52
+ for (;;) {
53
+ const run = await utils.getActivationRun(runId);
54
+ if (!run.ok) throw new Error(run.getError().message);
55
+ const body = String(run.getResult().value);
56
+ const status = /\b(?:\w+:)?status\s*=\s*["']([^"']*)["']/.exec(body)?.[1];
57
+ if (Date.now() >= deadline) throw new Error(`Activation ${runId} timed out`);
58
+ if (status === 'finished') break;
59
+ if (status === 'error' || status === 'failed') throw new Error(body);
60
+ await new Promise(resolve => setTimeout(resolve, 1000));
61
+ }
62
+ const activation = await utils.getActivationResults(runId, { analyse: analyseActivation });
63
+ if (!activation.ok) throw new Error(activation.getError().message);
64
+ const results = String(activation.getResult().value);
65
+ if (!/activationExecuted\s*=\s*["']true["']/.test(results) || /type\s*=\s*["']E["']/.test(results)) {
66
+ throw new Error(`Activation refused: ${results}`);
67
+ }
68
+ ```
69
+
70
+ A lock handle comes from the lock envelope; placeholders cannot refer to another
71
+ part's answer. A wrong UNLOCK handle can answer 200 while leaving the lock held.
72
+ Use the real handles and inspect activation messages, including `type="E"`.
73
+ The transport provides no transaction or rollback; callers own recovery and
74
+ cleanup after partial failures.
75
+
76
+ At an attached debugger stop, use the attached connection and its stateful session:
77
+
78
+ ```typescript
79
+ import { AbapDebugger, AdtBatchConnection } from '@mcp-abap-adt/adt-clients';
80
+
81
+ const batch = new AdtBatchConnection(debugConnection, { endpoint: 'debugger' });
82
+ const debuggerApi = new AbapDebugger(batch);
83
+ const pending = [
84
+ // Debugger recordings default to the attached stateful session.
85
+ debuggerApi.step('stepOver'),
86
+ debuggerApi.getStack(),
87
+ debuggerApi.getVariables(variableIds), // IDs obtained at the stop.
88
+ ];
89
+ await batch.execute();
90
+ const answers = await Promise.all(pending);
91
+ for (const answer of answers) {
92
+ if (!answer.ok) throw new Error(answer.getError().message);
93
+ }
94
+ ```
95
+
96
+ `execute()` returns the last outer wire response. Empty execution returns a synthetic
97
+ `{ status: 204, statusText: 'Empty batch', headers: {}, data: '' }` without sending.
98
+ Recorded request promises receive inner responses, in order: 2xx resolves; other
99
+ statuses reject with an Error carrying `response: { status, statusText, headers,
100
+ data }`, matching direct HTTP failure handling. Members keep their usual
101
+ `IAdtResponse` and strategy behavior. Envelope/parse failures stop execution immediately and reject `execute()` and
102
+ parts in the failed envelope. Unsent runs and queued follow-ups reject with a
103
+ library error saying the request was not sent because an earlier envelope failed;
104
+ that error carries the cause, without a SAP response. Missing parts reject with a part-count error. A count
105
+ mismatch also rejects `execute()`. Attach rejection handlers before execution when
106
+ using raw connection promises. Each execute sends follow-up envelopes for requests recorded during settlement,
107
+ including the automatic 406/415 corrected retry. It yields one event-loop turn
108
+ after settling each envelope to let member continuations record requests. It
109
+ returns the last outer response once that queue is empty. A limit of 100 envelopes
110
+ rejects execution and all remaining requests rather than looping forever.
111
+ Envelope failures also reject queued follow-ups. Concurrent executes are rejected;
112
+ calls made after execution resolves belong to the next execute.
113
+
114
+ On communication/batch, `execute()` partitions the queue into consecutive runs
115
+ of LOCKs, UNLOCKs, or stateless requests and sends them in order, without
116
+ reordering requests. Stateful actions are parsed from the URL query or request
117
+ `params`. Other stateful requests use a separate `stateful` group; no ordinary
118
+ member currently records one besides LOCK/UNLOCK. Debugger requests retain their
119
+ single stateful group.
120
+ For ordinary client members, LOCK and UNLOCK run stateful; creates, writes, reads,
121
+ and deletes run stateless. Group creates before locks, and writes before unlocks,
122
+ to avoid one envelope per alternating request. One execute can send several
123
+ envelopes and provides no transaction across them. Inner HTTP failures remain
124
+ per-part failures and do not stop later runs, allowing negotiation retries.
125
+
126
+ Stateful parts carry `X-sap-adt-sessiontype: stateful`. The wrapped session mode
127
+ is restored afterward. Connections exposing `getSessionMode()` or
128
+ `getSessionType()` supply the previous mode; minimum-contract connections without
129
+ a getter are assumed stateless. An explicit local `setSessionType` applies only
130
+ to the next recorded request. Recording then returns to the endpoint default:
131
+ communication is always stateless, independent of the wrapped connection's mode;
132
+ debugger is always stateful because it uses an attached debugging session.
133
+
134
+ Measurements on 2026-10-15 on an on-premise system and ABAP Cloud accepted mixed GET + POST on
135
+ `/sap/bc/adt/communication/batch`, including a 404 part beside successful parts.
136
+ The mixed-method 500 behind the 2026-09 removal was on `/debugger/batch`.
137
+ Debugger step + stack + variables worked at a live stop on both systems; keep
138
+ its member-specific variable Accept headers. RFC, payload limits and per-part
139
+ authorization remain unmeasured.
140
+
141
+ Several steps fit one debugger envelope: five `stepOver` + `getVariables` pairs
142
+ after an initial read (11 parts) answered every part 200, and the variable
143
+ history matched the one read step by step. Measured on 2026-10-16:
144
+
145
+ | Way | Requests | On premise | ABAP Cloud |
146
+ |---|---|---|---|
147
+ | One envelope, one `execute()` | 1 | ~130–180 ms | ~570–610 ms |
148
+ | One `execute()` per step | 6 | ~520 ms | ~2.7–3.6 s |
149
+ | Plain `AbapDebugger`, no batch | 11 | ~1.1 s | ~6.4–6.8 s |
150
+
151
+ Domains on the on-premise system: 100 created, locked, written and unlocked
152
+ through the batch connection, then group-activated, in 19.3 s against 111.9 s
153
+ for the same 500 requests one at a time.
154
+
155
+ Integration configuration uses `batch.test_cases`: `domains` (params `count`,
156
+ default 100; `name_prefix`, default `ZAC_BD_`; optional `package_name` and
157
+ `transport_request`, otherwise environment defaults) and `debugger_client`.
158
+ Both are disabled by default and support onprem/cloud. The debugger case uses
159
+ the existing `runtime_debugger.adt_abap_debugger` params and debugger_batch fixture.
160
+ The domain test writes timings to a temporary JSON file and logs its path via ILogger.
@@ -734,7 +734,7 @@ had:
734
734
  - **A task created explicitly through `createTask()` is born unclassified,
735
735
  and the creating call cannot change that.** Measured against BTP ABAP on
736
736
  2026-09-23: `tm:type` passed to `newtask` is accepted and ignored, and every
737
- task on that system read back as `Unclassified`. That system is a trial with
737
+ task on that system read back as `Unclassified`. That ABAP Cloud system has
738
738
  no transport system configured, so the CTS edit flow never creates a request
739
739
  there and every request and task is made by hand. All of them came through
740
740
  `newtask`, from this library's tests and from mcp-abap-adt's, and until
@@ -807,8 +807,9 @@ const request = client.getRequest({
807
807
 
808
808
  The `readObjects()` and `createTask()` examples above assume that set.
809
809
 
810
- **On a batch client** `list({ configUri })` records normally and resolves after
811
- `batchExecute()`, like any other member: it is one request now, so there is no
810
+ **On a batch connection** (an `AdtClient` built on `AdtBatchConnection`, see
811
+ [BATCH.md](BATCH.md)) `list({ configUri })` records normally and resolves after
812
+ `batch.execute()`, like any other member: it is one request now, so there is no
812
813
  first answer a batch would have to deliver early.
813
814
 
814
815
  **Migration from filter parameters.** There is no server-side filtering to
@@ -1883,8 +1884,7 @@ if (done.ok) console.log(done.getResult().value.findingStats); // "0,0,1"
1883
1884
  `findingStats` is the server's `FINDING_STATS` triple verbatim, for example
1884
1885
  `"0,0,1"`. It is still not parsed into named counts, but the ordering is no
1885
1886
  longer a single observation: a worklist with one priority-3 finding read
1886
- `"0,0,1"`, and a worklist with one priority-2 finding read `"0,1,0"` (BTP
1887
- trial, 2026-09-08, `ZAC_SHR_ATC_DIRTY` under `ABAP_CLOUD_DEVELOPMENT_DEFAULT`).
1887
+ `"0,0,1"`, and a worklist with one priority-2 finding read `"0,1,0"` (the ABAP Cloud system, 2026-09-08, `ZAC_SHR_ATC_DIRTY` under `ABAP_CLOUD_DEVELOPMENT_DEFAULT`).
1888
1888
  Two points fitting `(priority 1, priority 2, priority 3)` and no other ordering
1889
1889
  that survives both. Position one has still never been seen non-zero, which is
1890
1890
  why this stays a string: `{ errors, warnings, infos }` would name a severity
@@ -1978,7 +1978,7 @@ const recentDumps = await dumps.list({
1978
1978
  });
1979
1979
 
1980
1980
  // Filter by user
1981
- const userDumps = await dumps.listByUser('CB9980000423', {
1981
+ const userDumps = await dumps.listByUser('DEVELOPER', {
1982
1982
  inlinecount: 'allpages',
1983
1983
  top: 50,
1984
1984
  from: '20260401000000',
@@ -61,7 +61,7 @@ editing"*.
61
61
  **What Eclipse does.** It meets the same `403` on its own LOCK and publishes
62
62
  anyway:
63
63
 
64
- - Eclipse ADT 3.60.3 against the BTP trial, 2026-09-05: `LOCK` → `200` on its
64
+ - Eclipse ADT 3.60.3 against the ABAP Cloud system, 2026-09-05: `LOCK` → `200` on its
65
65
  *"stateful, enqueue"* session; `POST …/odatav4/publishjobs` → `200`,
66
66
  stateless; `UNLOCK` → `200` only on *"Closing editor"*; a second `LOCK` before
67
67
  the unpublish → `403` while the first was still held.
@@ -21,8 +21,8 @@ This guide explains how `@mcp-abap-adt/adt-clients` manages ADT sessions for CRU
21
21
  since 23.0.2 — before that, `AdtInclude`, `AdtService` and `AdtMessageClass`
22
22
  did not. A caller with its own `IAbapConnection` needs the same: the cookie
23
23
  on the `LOCK` and the `UNLOCK`.
24
- - This is Eclipse's model, measured at two scales. A full run against the cloud
25
- trial: 803 requests, of which exactly 100 carry `x-sap-adt-sessiontype:
24
+ - This is Eclipse's model, measured at two scales. A full run against
25
+ the ABAP Cloud system: 803 requests, of which exactly 100 carry `x-sap-adt-sessiontype:
26
26
  stateful` — the 50 `LOCK`s and the 50 `UNLOCK`s, and nothing else. A probe
27
27
  doing two write windows on one class: 12 requests and 4 such headers, again
28
28
  the locks and the unlocks alone. The ratio is what the rule predicts, and the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/adt-clients",
3
- "version": "32.0.0",
3
+ "version": "32.2.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",