@mcp-abap-adt/core 2.1.2 → 2.1.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.
Files changed (54) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/lib/utils.d.ts.map +1 -1
  3. package/dist/lib/utils.js +0 -5
  4. package/dist/lib/utils.js.map +1 -1
  5. package/docs/README.md +1 -2
  6. package/docs/architecture/CONNECTION_ISOLATION.md +0 -1
  7. package/docs/architecture/README.md +1 -1
  8. package/docs/architecture/STATEFUL_SESSION_GUIDE.md +28 -717
  9. package/docs/deployment/DOCKER.md +1 -18
  10. package/docs/development/roadmaps/archive/test_locking_roadmap.md +0 -3
  11. package/docs/installation/CLINE_CONFIGURATION.md +1 -32
  12. package/docs/installation/platforms/INSTALL_LINUX.md +1 -1
  13. package/docs/installation/platforms/INSTALL_MACOS.md +1 -1
  14. package/docs/installation/platforms/INSTALL_WINDOWS.md +1 -1
  15. package/docs/user-guide/AVAILABLE_TOOLS.md +2 -2
  16. package/docs/user-guide/CLIENT_CONFIGURATION.md +1 -1
  17. package/docs/user-guide/HANDLERS_MANAGEMENT.md +2 -2
  18. package/docs/user-guide/README.md +0 -2
  19. package/package.json +1 -2
  20. package/bin/README.md +0 -125
  21. package/bin/lock-object.js +0 -240
  22. package/docs/user-guide/scenarios/COMMON_PATTERNS.md +0 -603
  23. package/docs/user-guide/scenarios/CREATING_CDS_VIEWS.md +0 -441
  24. package/docs/user-guide/scenarios/CREATING_CLASSES.md +0 -456
  25. package/docs/user-guide/scenarios/CREATING_FUNCTION_GROUPS.md +0 -582
  26. package/docs/user-guide/scenarios/README.md +0 -51
  27. package/docs/user-guide/scenarios/SESSION_MANAGEMENT.md +0 -335
  28. package/docs/user-guide/usage/README.md +0 -137
  29. package/docs/user-guide/usage/rap-business-objects/README.md +0 -28
  30. package/docs/user-guide/usage/rap-business-objects/creating-rap-bo.md +0 -189
  31. package/docs/user-guide/usage/rap-business-objects/deferred-activation.md +0 -82
  32. package/docs/user-guide/usage/simple-objects/README.md +0 -23
  33. package/docs/user-guide/usage/simple-objects/classes-high.md +0 -32
  34. package/docs/user-guide/usage/simple-objects/classes-low.md +0 -165
  35. package/docs/user-guide/usage/simple-objects/data-elements-high.md +0 -48
  36. package/docs/user-guide/usage/simple-objects/data-elements-low.md +0 -144
  37. package/docs/user-guide/usage/simple-objects/domains-high.md +0 -32
  38. package/docs/user-guide/usage/simple-objects/domains-low.md +0 -144
  39. package/docs/user-guide/usage/simple-objects/function-groups-high.md +0 -32
  40. package/docs/user-guide/usage/simple-objects/function-groups-low.md +0 -144
  41. package/docs/user-guide/usage/simple-objects/function-modules-high.md +0 -32
  42. package/docs/user-guide/usage/simple-objects/function-modules-low.md +0 -160
  43. package/docs/user-guide/usage/simple-objects/interfaces-high.md +0 -32
  44. package/docs/user-guide/usage/simple-objects/interfaces-low.md +0 -160
  45. package/docs/user-guide/usage/simple-objects/programs-high.md +0 -32
  46. package/docs/user-guide/usage/simple-objects/programs-low.md +0 -160
  47. package/docs/user-guide/usage/simple-objects/service-definitions-high.md +0 -32
  48. package/docs/user-guide/usage/simple-objects/service-definitions-low.md +0 -160
  49. package/docs/user-guide/usage/simple-objects/structures-high.md +0 -32
  50. package/docs/user-guide/usage/simple-objects/structures-low.md +0 -160
  51. package/docs/user-guide/usage/simple-objects/tables-high.md +0 -32
  52. package/docs/user-guide/usage/simple-objects/tables-low.md +0 -165
  53. package/docs/user-guide/usage/simple-objects/views-high.md +0 -32
  54. package/docs/user-guide/usage/simple-objects/views-low.md +0 -160
@@ -1,737 +1,48 @@
1
- # Stateful Session Management in SAP ADT API
1
+ # Stateful ADT Session Flow
2
2
 
3
- ## Overview
3
+ This guide describes the **stateful ADT request flow** required for write operations
4
+ (lock/update/unlock) against ABAP via ADT. It is about **protocol behavior**, not
5
+ file-based session persistence.
4
6
 
5
- This guide (root/server perspective) explains how MCP handlers coordinate **stateful ADT sessions**.
6
- Proper session management is **critical** for operations that modify ABAP objects (update source code, create/modify dictionary objects, etc.).
7
+ ## Why Stateful Matters
7
8
 
8
- ### Related Guides
9
+ Write operations must share:
10
+ - the same `sap-adt-connection-id` across LOCK/PUT/UNLOCK
11
+ - preserved cookies/CSRF tokens between requests
9
12
 
10
- | View | Location |
11
- |------|----------|
12
- | Server / MCP handlers (this file) | `doc/architecture/STATEFUL_SESSION_GUIDE.md` |
13
- | ADT clients / Builders | `@mcp-abap-adt/adt-clients` npm package documentation |
14
- | Connection layer / HTTP session | `@mcp-abap-adt/connection` npm package documentation |
13
+ If these are not consistent, SAP rejects the lock handle or returns CSRF errors.
15
14
 
16
- Use all three when you need the full picture (HTTP session ↔ ADT session ↔ handler workflow).
15
+ ## Required Headers
17
16
 
18
- ---
17
+ Each request in the flow must include:
19
18
 
20
- ## Table of Contents
21
-
22
- 1. [Stateful Sessions](#stateful-sessions)
23
- 2. [Lock Mechanism](#lock-mechanism)
24
- 3. [Cookie Management](#cookie-management)
25
- 4. [Complete Workflow Examples](#complete-workflow-examples)
26
- 5. [Common Pitfalls](#common-pitfalls)
27
- 6. [Troubleshooting](#troubleshooting)
28
-
29
- ---
30
-
31
- ## Stateful Sessions
32
-
33
- ### What is a Stateful Session?
34
-
35
- A stateful session in SAP ADT API ensures that multiple related requests (LOCK → PUT → UNLOCK) are executed within the same SAP session context. This is required for:
36
- - Lock acquisition and release
37
- - Modifying ABAP objects
38
- - Transport request handling
39
- - Consistent transaction context
40
-
41
- ### How to Create a Stateful Session
42
-
43
- #### 1. Generate Session ID
44
-
45
- Each stateful session requires a unique `sap-adt-connection-id`:
46
-
47
- ```typescript
48
- import * as crypto from 'crypto';
49
-
50
- function generateSessionId(): string {
51
- return crypto.randomUUID().replace(/-/g, '');
52
- }
53
- ```
54
-
55
- Example: `a1b2c3d4e5f6789012345678901234567890abcd`
56
-
57
- #### 2. Generate Request ID
58
-
59
- Each individual request within a session needs a unique `sap-adt-request-id`:
60
-
61
- ```typescript
62
- function generateRequestId(): string {
63
- return crypto.randomUUID().replace(/-/g, '');
64
- }
65
- ```
66
-
67
- #### 3. Required Headers for Stateful Requests
68
-
69
- Every request in a stateful session MUST include these headers:
70
-
71
- ```typescript
72
- const headers = {
73
- 'sap-adt-connection-id': sessionId, // Same for all requests in session
74
- 'sap-adt-request-id': requestId, // Unique for each request
75
- 'x-sap-adt-sessiontype': 'stateful', // Declares stateful session
76
- 'X-sap-adt-profiling': 'server-time', // Optional: performance profiling
77
- // + any operation-specific headers
78
- };
79
- ```
80
-
81
- **CRITICAL**:
82
- - `sap-adt-connection-id` MUST be the **same** for all requests (LOCK, PUT, UNLOCK) in one operation
83
- - `sap-adt-request-id` MUST be **different** for each request
84
- - Cookies MUST be preserved between requests (handled automatically by BaseAbapConnection)
85
-
86
- ---
87
-
88
- ## Lock Mechanism
89
-
90
- ### Lock Workflow
91
-
92
- All modify operations follow this pattern:
93
-
94
- ```
95
- 1. LOCK - Acquire exclusive lock on object
96
- 2. PUT - Modify object (upload source, change metadata, etc.)
97
- 3. UNLOCK - Release lock
98
- ```
99
-
100
- **CRITICAL**: If lock is not released (due to error or crash), object remains locked until:
101
- - Session timeout
102
- - Manual unlock via transaction SM12
103
- - System restart
104
-
105
- ### Step 1: Lock Object
106
-
107
- > 🆕 Prefer using `new LockClient(connection).lock({ objectType: 'class', objectName: 'ZCL_TEST' })`.
108
- > It wraps the workflow below, logs `[LOCK] ...` lines, and records lock handles in `.locks/active-locks.json` for recovery.
109
-
110
- #### LOCK Request
111
-
112
- **Method**: `POST`
113
-
114
- **URL Pattern**:
115
- ```
116
- /sap/bc/adt/<object-path>?_action=LOCK&accessMode=MODIFY
117
- ```
118
-
119
- Examples:
120
- - Class: `/sap/bc/adt/oo/classes/zcl_test?_action=LOCK&accessMode=MODIFY`
121
- - Program: `/sap/bc/adt/programs/programs/z_test?_action=LOCK&accessMode=MODIFY`
122
- - Table: `/sap/bc/adt/ddic/tables/ztable?_action=LOCK&accessMode=MODIFY`
123
-
124
- **Headers**:
125
- ```http
126
- sap-adt-connection-id: <sessionId>
127
- sap-adt-request-id: <requestId>
128
- x-sap-adt-sessiontype: stateful
129
- Accept: application/vnd.sap.as+xml;charset=UTF-8;dataname=com.sap.adt.lock.result;q=0.8, application/vnd.sap.as+xml;charset=UTF-8;dataname=com.sap.adt.lock.result2;q=0.9
130
- ```
131
-
132
- **Response**: XML with lock information
133
-
134
- ```xml
135
- <?xml version="1.0" encoding="utf-8"?>
136
- <asx:abap version="1.0" xmlns:asx="http://www.sap.com/abapxml">
137
- <asx:values>
138
- <DATA>
139
- <LOCK_HANDLE>BD53F3688D0F164CA3ADF06FD43C39E1CC1C3B32</LOCK_HANDLE>
140
- <CORRNR>E19K905635</CORRNR>
141
- <CORRUSER>OKYSLYTSIA</CORRUSER>
142
- <CORRTEXT>Transport description</CORRTEXT>
143
- <IS_LOCAL/>
144
- <IS_LINK_UP/>
145
- <MODIFICATION_SUPPORT>NoModification</MODIFICATION_SUPPORT>
146
- <LINK_UP_MODE/>
147
- <CORR_LOCKS/>
148
- <CORR_CONTENTS/>
149
- <SCOPE_MESSAGES/>
150
- </DATA>
151
- </asx:values>
152
- </asx:abap>
153
- ```
154
-
155
- **Extract Lock Information**:
156
-
157
- ```typescript
158
- import { XMLParser } from 'fast-xml-parser';
159
-
160
- const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' });
161
- const result = parser.parse(response.data);
162
-
163
- const lockHandle = result?.['asx:abap']?.['asx:values']?.['DATA']?.['LOCK_HANDLE'];
164
- const corrNr = result?.['asx:abap']?.['asx:values']?.['DATA']?.['CORRNR'];
165
- ```
166
-
167
- **CRITICAL**:
168
- - `LOCK_HANDLE` is required for PUT and UNLOCK operations
169
- - `CORRNR` (transport number) is required for PUT operation
170
- - If `LOCK_HANDLE` is empty/null, throw error immediately - don't proceed!
171
-
172
- ### Step 2: Modify Object (PUT)
173
-
174
- #### PUT Request
175
-
176
- **Method**: `PUT`
177
-
178
- **URL Pattern** (with lock parameters):
179
- ```
180
- /sap/bc/adt/<object-path>?lockHandle=<LOCK_HANDLE>&corrNr=<CORRNR>
181
19
  ```
182
-
183
- Example:
184
- ```
185
- PUT /sap/bc/adt/oo/classes/zcl_test/source/main?lockHandle=BD53F3688D0F164CA3ADF06FD43C39E1CC1C3B32&corrNr=E19K905635
186
- ```
187
-
188
- **Headers**:
189
- ```http
190
- sap-adt-connection-id: <sessionId> # SAME as in LOCK request
191
- sap-adt-request-id: <requestId> # NEW unique ID
20
+ sap-adt-connection-id: <session-id> # same for all requests
21
+ sap-adt-request-id: <request-id> # unique per request
192
22
  x-sap-adt-sessiontype: stateful
193
- Content-Type: text/plain; charset=utf-8
194
- Accept: text/plain
195
- ```
196
-
197
- **Body**: New source code (plain text)
198
-
199
- ```abap
200
- CLASS zcl_test DEFINITION
201
- PUBLIC
202
- FINAL
203
- CREATE PUBLIC.
204
-
205
- PUBLIC SECTION.
206
- METHODS: constructor.
207
- ENDCLASS.
208
-
209
- CLASS zcl_test IMPLEMENTATION.
210
- METHOD constructor.
211
- WRITE: / 'Hello from updated class'.
212
- ENDMETHOD.
213
- ENDCLASS.
214
- ```
215
-
216
- **CRITICAL**:
217
- - Lock handle and transport number are passed as **URL parameters**, NOT headers
218
- - `sap-adt-connection-id` MUST be the same as in LOCK request
219
- - Content-Type MUST be `text/plain; charset=utf-8`
220
-
221
- ### Step 3: Unlock Object
222
-
223
- #### UNLOCK Request
224
-
225
- **Method**: `POST`
226
-
227
- **URL Pattern**:
228
- ```
229
- /sap/bc/adt/<object-path>?_action=UNLOCK&lockHandle=<LOCK_HANDLE>
230
- ```
231
-
232
- Example:
233
- ```
234
- POST /sap/bc/adt/oo/classes/zcl_test?_action=UNLOCK&lockHandle=BD53F3688D0F164CA3ADF06FD43C39E1CC1C3B32
235
- ```
236
-
237
- **Headers**:
238
- ```http
239
- sap-adt-connection-id: <sessionId> # SAME as in LOCK and PUT
240
- sap-adt-request-id: <requestId> # NEW unique ID
241
- x-sap-adt-sessiontype: stateful
242
- ```
243
-
244
- **CRITICAL**:
245
- - Always unlock in `finally` block or error handler
246
- - If unlock fails, object remains locked
247
- - Lock handle is passed as URL parameter
248
-
249
- ---
250
-
251
- ## Cookie Management
252
-
253
- ### Why Cookies Matter
254
-
255
- SAP ADT API uses cookies for:
256
- - Session authentication (SAP_SESSIONID)
257
- - Client context (sap-usercontext)
258
- - Context ID for stateful operations (sap-contextid)
259
-
260
- **CRITICAL**: Cookies MUST be preserved and sent with every request in a stateful session.
261
-
262
- ### How Cookies Work in Our Implementation
263
-
264
- #### BaseAbapConnection handles cookies automatically:
265
-
266
- 1. **CSRF Token Request** - First request creates SAP session and receives cookies:
267
- ```typescript
268
- // In BaseAbapConnection.ts
269
- const response = await axios.head(url, { headers: { 'x-csrf-token': 'fetch' } });
270
-
271
- // Extract cookies from response
272
- const cookies = response.headers['set-cookie'];
273
- // Store in this.csrfToken and this.cookies
274
- ```
275
-
276
- 2. **Subsequent Requests** - Cookies are automatically added:
277
- ```typescript
278
- // In BaseAbapConnection.makeAdtRequest()
279
- const headers = {
280
- 'x-csrf-token': this.csrfToken,
281
- 'Cookie': this.cookies, // Automatically added
282
- ...userHeaders
283
- };
284
- ```
285
-
286
- ### Cookie Flow Example
287
-
288
- ```
289
- Request 1 (CSRF fetch):
290
- → HEAD /sap/bc/adt/discovery
291
- Headers: x-csrf-token: fetch
292
- ← Response:
293
- x-csrf-token: AbCdEf123456
294
- Set-Cookie: SAP_SESSIONID_E19_100=xyz...; sap-usercontext=...
295
-
296
- Request 2 (LOCK):
297
- → POST /sap/bc/adt/oo/classes/zcl_test?_action=LOCK
298
- Headers:
299
- x-csrf-token: AbCdEf123456
300
- Cookie: SAP_SESSIONID_E19_100=xyz...; sap-usercontext=...
301
- sap-adt-connection-id: session123
302
- ← Response: Lock acquired
303
-
304
- Request 3 (PUT):
305
- → PUT /sap/bc/adt/oo/classes/zcl_test/source/main?lockHandle=...
306
- Headers:
307
- x-csrf-token: AbCdEf123456
308
- Cookie: SAP_SESSIONID_E19_100=xyz...; sap-usercontext=...
309
- sap-adt-connection-id: session123 # SAME as Request 2
310
- ← Response: Source updated
311
-
312
- Request 4 (UNLOCK):
313
- → POST /sap/bc/adt/oo/classes/zcl_test?_action=UNLOCK
314
- Headers:
315
- x-csrf-token: AbCdEf123456
316
- Cookie: SAP_SESSIONID_E19_100=xyz...; sap-usercontext=...
317
- sap-adt-connection-id: session123 # SAME as Request 2 and 3
318
- ← Response: Lock released
319
- ```
320
-
321
- ### Manual Cookie Management (if needed)
322
-
323
- If you need to manage cookies manually:
324
-
325
- ```typescript
326
- // Extract cookies from response
327
- const setCookieHeader = response.headers['set-cookie'];
328
- if (setCookieHeader) {
329
- const cookies = Array.isArray(setCookieHeader)
330
- ? setCookieHeader.join('; ')
331
- : setCookieHeader;
332
-
333
- // Store and reuse in subsequent requests
334
- this.cookies = cookies;
335
- }
336
-
337
- // Add cookies to next request
338
- const headers = {
339
- 'Cookie': this.cookies,
340
- // ... other headers
341
- };
342
23
  ```
343
24
 
344
- **CRITICAL**: Never mix cookies from different SAP sessions!
345
-
346
- ---
25
+ ## Standard Flow
347
26
 
348
- ## Complete Workflow Examples
27
+ 1) **LOCK**
28
+ `POST /sap/bc/adt/<object-path>?_action=LOCK&accessMode=MODIFY`
349
29
 
350
- ### Example 1: Update Class Source Code
30
+ Response includes `LOCK_HANDLE` and `CORRNR` (transport).
351
31
 
352
- ```typescript
353
- import * as crypto from 'crypto';
354
- import { XMLParser } from 'fast-xml-parser';
32
+ 2) **PUT / UPDATE**
33
+ `PUT /sap/bc/adt/<object-path>?lockHandle=<LOCK_HANDLE>&corrNr=<CORRNR>`
355
34
 
356
- async function updateClassSource(className: string, newSourceCode: string) {
357
- // Generate session ID - SAME for all requests
358
- const sessionId = crypto.randomUUID().replace(/-/g, '');
359
- let lockHandle: string | null = null;
360
-
361
- try {
362
- // Step 1: LOCK
363
- const lockUrl = `/sap/bc/adt/oo/classes/${className.toLowerCase()}?_action=LOCK&accessMode=MODIFY`;
364
- const lockHeaders = {
365
- 'sap-adt-connection-id': sessionId,
366
- 'sap-adt-request-id': crypto.randomUUID().replace(/-/g, ''),
367
- 'x-sap-adt-sessiontype': 'stateful',
368
- 'Accept': 'application/vnd.sap.as+xml;charset=UTF-8;dataname=com.sap.adt.lock.result2;q=0.9'
369
- };
370
-
371
- const lockResponse = await makeAdtRequest(lockUrl, 'POST', lockHeaders);
372
-
373
- // Parse lock handle and transport number
374
- const parser = new XMLParser({ ignoreAttributes: false });
375
- const result = parser.parse(lockResponse.data);
376
- lockHandle = result?.['asx:abap']?.['asx:values']?.['DATA']?.['LOCK_HANDLE'];
377
- const corrNr = result?.['asx:abap']?.['asx:values']?.['DATA']?.['CORRNR'];
378
-
379
- if (!lockHandle) {
380
- throw new Error('Failed to obtain lock handle');
381
- }
382
-
383
- console.log(`✓ Lock acquired: ${lockHandle}`);
384
-
385
- // Step 2: PUT (upload source)
386
- let putUrl = `/sap/bc/adt/oo/classes/${className.toLowerCase()}/source/main?lockHandle=${lockHandle}`;
387
- if (corrNr) {
388
- putUrl += `&corrNr=${corrNr}`;
389
- }
390
-
391
- const putHeaders = {
392
- 'sap-adt-connection-id': sessionId, // SAME session ID!
393
- 'sap-adt-request-id': crypto.randomUUID().replace(/-/g, ''),
394
- 'x-sap-adt-sessiontype': 'stateful',
395
- 'Content-Type': 'text/plain; charset=utf-8',
396
- 'Accept': 'text/plain'
397
- };
398
-
399
- await makeAdtRequest(putUrl, 'PUT', putHeaders, newSourceCode);
400
- console.log(`✓ Source code updated`);
401
-
402
- // Step 3: UNLOCK
403
- const unlockUrl = `/sap/bc/adt/oo/classes/${className.toLowerCase()}?_action=UNLOCK&lockHandle=${lockHandle}`;
404
- const unlockHeaders = {
405
- 'sap-adt-connection-id': sessionId, // SAME session ID!
406
- 'sap-adt-request-id': crypto.randomUUID().replace(/-/g, ''),
407
- 'x-sap-adt-sessiontype': 'stateful'
408
- };
409
-
410
- await makeAdtRequest(unlockUrl, 'POST', unlockHeaders);
411
- lockHandle = null;
412
- console.log(`✓ Lock released`);
413
-
414
- } catch (error) {
415
- // CRITICAL: Always unlock on error
416
- if (lockHandle) {
417
- try {
418
- const unlockUrl = `/sap/bc/adt/oo/classes/${className.toLowerCase()}?_action=UNLOCK&lockHandle=${lockHandle}`;
419
- await makeAdtRequest(unlockUrl, 'POST', {
420
- 'sap-adt-connection-id': sessionId,
421
- 'sap-adt-request-id': crypto.randomUUID().replace(/-/g, ''),
422
- 'x-sap-adt-sessiontype': 'stateful'
423
- });
424
- console.log('Lock released after error');
425
- } catch (unlockError) {
426
- console.error('Failed to unlock after error:', unlockError);
427
- }
428
- }
429
- throw error;
430
- }
431
- }
432
- ```
433
-
434
- ### Example 2: Update Program Source Code
435
-
436
- ```typescript
437
- async function updateProgramSource(programName: string, newSourceCode: string) {
438
- const sessionId = crypto.randomUUID().replace(/-/g, '');
439
- let lockHandle: string | null = null;
440
-
441
- try {
442
- // Step 1: LOCK
443
- const lockUrl = `/sap/bc/adt/programs/programs/${programName.toLowerCase()}?_action=LOCK&accessMode=MODIFY`;
444
- const lockResponse = await makeAdtRequest(lockUrl, 'POST', {
445
- 'sap-adt-connection-id': sessionId,
446
- 'sap-adt-request-id': crypto.randomUUID().replace(/-/g, ''),
447
- 'x-sap-adt-sessiontype': 'stateful',
448
- 'Accept': 'application/vnd.sap.as+xml;charset=UTF-8;dataname=com.sap.adt.lock.result2;q=0.9'
449
- });
450
-
451
- const parser = new XMLParser({ ignoreAttributes: false });
452
- const result = parser.parse(lockResponse.data);
453
- lockHandle = result?.['asx:abap']?.['asx:values']?.['DATA']?.['LOCK_HANDLE'];
454
- const corrNr = result?.['asx:abap']?.['asx:values']?.['DATA']?.['CORRNR'];
455
-
456
- if (!lockHandle) {
457
- throw new Error('Failed to obtain lock handle');
458
- }
459
-
460
- // Step 2: PUT
461
- let putUrl = `/sap/bc/adt/programs/programs/${programName.toLowerCase()}/source/main?lockHandle=${lockHandle}`;
462
- if (corrNr) {
463
- putUrl += `&corrNr=${corrNr}`;
464
- }
465
-
466
- await makeAdtRequest(putUrl, 'PUT', {
467
- 'sap-adt-connection-id': sessionId,
468
- 'sap-adt-request-id': crypto.randomUUID().replace(/-/g, ''),
469
- 'x-sap-adt-sessiontype': 'stateful',
470
- 'Content-Type': 'text/plain; charset=utf-8',
471
- 'Accept': 'text/plain'
472
- }, newSourceCode);
473
-
474
- // Step 3: UNLOCK
475
- const unlockUrl = `/sap/bc/adt/programs/programs/${programName.toLowerCase()}?_action=UNLOCK&lockHandle=${lockHandle}`;
476
- await makeAdtRequest(unlockUrl, 'POST', {
477
- 'sap-adt-connection-id': sessionId,
478
- 'sap-adt-request-id': crypto.randomUUID().replace(/-/g, ''),
479
- 'x-sap-adt-sessiontype': 'stateful'
480
- });
481
- lockHandle = null;
482
-
483
- } catch (error) {
484
- if (lockHandle) {
485
- try {
486
- const unlockUrl = `/sap/bc/adt/programs/programs/${programName.toLowerCase()}?_action=UNLOCK&lockHandle=${lockHandle}`;
487
- await makeAdtRequest(unlockUrl, 'POST', {
488
- 'sap-adt-connection-id': sessionId,
489
- 'sap-adt-request-id': crypto.randomUUID().replace(/-/g, ''),
490
- 'x-sap-adt-sessiontype': 'stateful'
491
- });
492
- } catch (unlockError) {
493
- console.error('Failed to unlock:', unlockError);
494
- }
495
- }
496
- throw error;
497
- }
498
- }
499
- ```
500
-
501
- ---
35
+ 3) **UNLOCK**
36
+ `POST /sap/bc/adt/<object-path>?_action=UNLOCK&lockHandle=<LOCK_HANDLE>`
502
37
 
503
38
  ## Common Pitfalls
504
39
 
505
- ### 1. ❌ Different Session IDs for LOCK/PUT/UNLOCK
506
-
507
- ```typescript
508
- // WRONG - Different session IDs
509
- const lockSessionId = generateSessionId();
510
- await lock(lockSessionId);
511
-
512
- const putSessionId = generateSessionId(); // ❌ NEW ID!
513
- await put(putSessionId); // Will fail with "invalid lock handle"
514
- ```
515
-
516
- ```typescript
517
- // CORRECT - Same session ID
518
- const sessionId = generateSessionId();
519
- await lock(sessionId);
520
- await put(sessionId); // ✓ Same ID
521
- await unlock(sessionId); // ✓ Same ID
522
- ```
523
-
524
- ### 2. ❌ Lock Handle in Headers Instead of URL
525
-
526
- ```typescript
527
- // WRONG
528
- const headers = {
529
- 'sap-adt-lockhandle': lockHandle // ❌ Wrong place!
530
- };
531
- await put('/sap/bc/adt/oo/classes/zcl_test/source/main', headers);
532
- ```
533
-
534
- ```typescript
535
- // CORRECT
536
- const url = `/sap/bc/adt/oo/classes/zcl_test/source/main?lockHandle=${lockHandle}&corrNr=${corrNr}`;
537
- await put(url); // ✓ In URL
538
- ```
539
-
540
- ### 3. ❌ Not Unlocking on Error
541
-
542
- ```typescript
543
- // WRONG
544
- try {
545
- await lock();
546
- await put();
547
- await unlock();
548
- } catch (error) {
549
- throw error; // ❌ Object stays locked!
550
- }
551
- ```
552
-
553
- ```typescript
554
- // CORRECT
555
- let lockHandle = null;
556
- try {
557
- lockHandle = await lock();
558
- await put();
559
- await unlock();
560
- lockHandle = null;
561
- } catch (error) {
562
- if (lockHandle) {
563
- await unlock(); // ✓ Always unlock
564
- }
565
- throw error;
566
- }
567
- ```
568
-
569
- ### 4. ❌ Missing Transport Number in PUT
570
-
571
- ```typescript
572
- // WRONG
573
- const url = `/sap/bc/adt/oo/classes/zcl_test/source/main?lockHandle=${lockHandle}`;
574
- // ❌ Missing corrNr!
575
- ```
576
-
577
- ```typescript
578
- // CORRECT
579
- const url = `/sap/bc/adt/oo/classes/zcl_test/source/main?lockHandle=${lockHandle}&corrNr=${corrNr}`;
580
- // ✓ Both parameters
581
- ```
582
-
583
- ### 5. ❌ Not Checking Lock Handle
584
-
585
- ```typescript
586
- // WRONG
587
- const lockHandle = result?.['asx:abap']?.['asx:values']?.['DATA']?.['LOCK_HANDLE'];
588
- await put(lockHandle); // ❌ May be null/undefined!
589
- ```
590
-
591
- ```typescript
592
- // CORRECT
593
- const lockHandle = result?.['asx:abap']?.['asx:values']?.['DATA']?.['LOCK_HANDLE'];
594
- if (!lockHandle) {
595
- throw new Error('Failed to obtain lock handle');
596
- }
597
- await put(lockHandle); // ✓ Validated
598
- ```
599
-
600
- ### 6. ❌ Mixing Cookies from Different Sessions
601
-
602
- ```typescript
603
- // WRONG - Using global connection for stateful operations
604
- const connection1 = new Connection();
605
- await connection1.lock();
606
-
607
- const connection2 = new Connection(); // ❌ Different cookies!
608
- await connection2.put(); // Will fail
609
- ```
610
-
611
- ```typescript
612
- // CORRECT - Use same connection instance
613
- const connection = new Connection();
614
- await connection.lock();
615
- await connection.put(); // ✓ Same cookies
616
- await connection.unlock();
617
- ```
618
-
619
- ---
620
-
621
- ## Troubleshooting
622
-
623
- ### Error: "Resource is not locked (invalid lock handle)"
624
-
625
- **Symptoms**: HTTP 423, `ExceptionResourceInvalidLockHandle`
626
-
627
- **Causes**:
628
- 1. Lock handle not passed correctly in PUT URL
629
- 2. Different `sap-adt-connection-id` between LOCK and PUT
630
- 3. Cookies not preserved between requests
631
- 4. Lock handle is null/empty
632
-
633
- **Solutions**:
634
- - ✓ Verify lock handle is in URL: `?lockHandle=...&corrNr=...`
635
- - ✓ Check same `sap-adt-connection-id` in all requests
636
- - ✓ Enable DEBUG logging to see actual requests
637
- - ✓ Verify lock handle extracted from XML response
638
-
639
- ### Error: "User X is currently editing object Y"
640
-
641
- **Symptoms**: HTTP error, `ExceptionResourceNoAccess`
642
-
643
- **Causes**:
644
- 1. Object is locked by another user or session
645
- 2. Previous lock was not released (crash, error)
646
-
647
- **Solutions**:
648
- - ✓ Check SM12 transaction in SAP for active locks
649
- - ✓ Release stale locks manually via SM12
650
- - ✓ Wait for session timeout (usually 15-30 minutes)
651
- - ✓ Always use try/finally for unlock
652
-
653
- ### Error: "Session terminated" or "CSRF token invalid"
654
-
655
- **Symptoms**: HTTP 403, CSRF token errors
656
-
657
- **Causes**:
658
- 1. SAP session expired
659
- 2. CSRF token not refreshed
660
- 3. Cookies lost between requests
661
-
662
- **Solutions**:
663
- - ✓ Fetch new CSRF token before operations
664
- - ✓ Verify cookies are preserved in connection
665
- - ✓ Check SAP session timeout settings
666
-
667
- ### Debugging Tips
668
-
669
- #### Enable Debug Logging
670
-
671
- ```bash
672
- export DEBUG=true # Linux/Mac
673
- $env:DEBUG = "true" # PowerShell
674
- ```
675
-
676
- #### Inspect Requests
677
-
678
- ```typescript
679
- logger.info(`LOCK URL: ${lockUrl}`);
680
- logger.info(`Session ID: ${sessionId}`);
681
- logger.info(`Lock response: ${JSON.stringify(lockResponse.data)}`);
682
- logger.info(`Lock handle: ${lockHandle}`);
683
- logger.info(`PUT URL: ${putUrl}`);
684
- ```
685
-
686
- #### Check Parsed XML
687
-
688
- ```typescript
689
- const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' });
690
- const result = parser.parse(response.data);
691
- console.log('Parsed XML:', JSON.stringify(result, null, 2));
692
- ```
693
-
694
- #### Verify Headers
695
-
696
- ```typescript
697
- console.log('Request headers:', JSON.stringify(headers, null, 2));
698
- console.log('Response headers:', JSON.stringify(response.headers, null, 2));
699
- ```
700
-
701
- ---
702
-
703
- ## Summary Checklist
704
-
705
- When implementing stateful operations:
706
-
707
- - [ ] Generate **one** session ID for entire operation
708
- - [ ] Generate **unique** request ID for each request
709
- - [ ] Add stateful headers to all requests:
710
- - [ ] `sap-adt-connection-id`
711
- - [ ] `sap-adt-request-id`
712
- - [ ] `x-sap-adt-sessiontype: stateful`
713
- - [ ] LOCK: Extract `LOCK_HANDLE` and `CORRNR` from XML response
714
- - [ ] LOCK: Verify lock handle is not null/empty
715
- - [ ] PUT: Pass `lockHandle` and `corrNr` as **URL parameters**
716
- - [ ] PUT: Use correct Content-Type (`text/plain; charset=utf-8`)
717
- - [ ] UNLOCK: Always execute in finally/error handler
718
- - [ ] UNLOCK: Pass `lockHandle` as **URL parameter**
719
- - [ ] Cookies: Preserved automatically by BaseAbapConnection
720
- - [ ] Error handling: Always unlock on errors
721
- - [ ] Logging: Enable DEBUG for troubleshooting
722
-
723
- ---
724
-
725
- ## References
726
-
727
- - Eclipse ADT API Documentation
728
- - SAP Note: ABAP Development Tools (ADT)
729
- - Transaction SM12: Lock Management
730
- - BaseAbapConnection.ts - Cookie and CSRF management
731
- - `src/handlers/class/high/handleUpdateClass.ts` - Complete implementation example
40
+ - **Different session IDs** between LOCK/PUT/UNLOCK
41
+ - **New cookies/CSRF** (missing carry-over between requests)
42
+ - **Missing lockHandle or corrNr** in update call
732
43
 
733
- ---
44
+ ## Notes
734
45
 
735
- **Last Updated**: 2025-11-08
736
- **Author**: MCP ABAP ADT Development Team
737
- **Version**: 1.0
46
+ - Session state may be managed in-memory or by external stores (auth-broker/stores),
47
+ but the ADT flow always requires the same `sap-adt-connection-id` and cookies
48
+ across the stateful sequence.