@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.
- package/README.md +29 -1
- package/dist/batch/AdtBatchConnection.d.ts +29 -0
- package/dist/batch/AdtBatchConnection.d.ts.map +1 -0
- package/dist/batch/AdtBatchConnection.js +155 -0
- package/dist/batch/index.d.ts +2 -0
- package/dist/batch/index.d.ts.map +1 -0
- package/dist/batch/index.js +5 -0
- package/dist/batch/wire.d.ts +10 -0
- package/dist/batch/wire.d.ts.map +1 -0
- package/dist/batch/wire.js +88 -0
- package/dist/core/accessControl/versions.d.ts +1 -1
- package/dist/core/accessControl/versions.js +1 -1
- package/dist/core/appendStructure/versions.d.ts +1 -1
- package/dist/core/appendStructure/versions.js +1 -1
- package/dist/core/behaviorDefinition/versions.d.ts +1 -1
- package/dist/core/behaviorDefinition/versions.js +1 -1
- package/dist/core/behaviorImplementation/versions.d.ts +1 -1
- package/dist/core/behaviorImplementation/versions.js +1 -1
- package/dist/core/enhancement/versions.js +1 -1
- package/dist/core/functionGroup/create.js +1 -1
- package/dist/core/functionInclude/versions.js +1 -1
- package/dist/core/functionModule/versions.js +1 -1
- package/dist/core/scalarFunction/versions.d.ts +1 -1
- package/dist/core/scalarFunction/versions.js +1 -1
- package/dist/core/scalarFunctionImplementation/versions.d.ts +1 -1
- package/dist/core/scalarFunctionImplementation/versions.js +1 -1
- package/dist/core/service/AdtService.js +2 -2
- package/dist/core/service/lock.d.ts +1 -1
- package/dist/core/service/lock.js +1 -1
- package/dist/core/service/types.d.ts +2 -2
- package/dist/core/serviceDefinition/versions.d.ts +1 -1
- package/dist/core/serviceDefinition/versions.js +1 -1
- package/dist/core/structure/versions.d.ts +1 -1
- package/dist/core/structure/versions.js +1 -1
- package/dist/core/tabletype/versions.d.ts +1 -1
- package/dist/core/tabletype/versions.js +1 -1
- package/dist/core/transformation/versions.d.ts +1 -1
- package/dist/core/transformation/versions.js +1 -1
- package/dist/core/transport/AdtRequest.d.ts +2 -2
- package/dist/core/transport/AdtRequest.js +2 -2
- package/dist/core/transport/objects.d.ts +1 -1
- package/dist/core/transport/objects.js +1 -1
- package/dist/endpoints/objects.d.ts +5 -0
- package/dist/endpoints/objects.d.ts.map +1 -1
- package/dist/endpoints/objects.js +6 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/runtime/debugger/abap.d.ts.map +1 -1
- package/dist/runtime/debugger/abap.js +28 -40
- package/dist/runtime/debugger/batchPayload.d.ts +2 -16
- package/dist/runtime/debugger/batchPayload.d.ts.map +1 -1
- package/dist/runtime/debugger/batchPayload.js +5 -28
- package/dist/runtime/dumps/read.d.ts +1 -1
- package/dist/runtime/dumps/read.js +1 -1
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +3 -0
- package/docs/usage/BATCH.md +160 -0
- package/docs/usage/CLIENT_API_REFERENCE.md +6 -6
- package/docs/usage/ERRATA.md +1 -1
- package/docs/usage/STATEFUL_SESSION_GUIDE.md +2 -2
- 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
|
|
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
|
|
811
|
-
|
|
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"` (
|
|
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('
|
|
1981
|
+
const userDumps = await dumps.listByUser('DEVELOPER', {
|
|
1982
1982
|
inlinecount: 'allpages',
|
|
1983
1983
|
top: 50,
|
|
1984
1984
|
from: '20260401000000',
|
package/docs/usage/ERRATA.md
CHANGED
|
@@ -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
|
|
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
|
|
25
|
-
|
|
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
|