@mcp-abap-adt/connection 5.0.0 → 6.0.1

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 (92) hide show
  1. package/CHANGELOG.md +243 -1
  2. package/README.md +204 -48
  3. package/bin/sap-abap-auth.js +101 -13
  4. package/dist/auth/providers.d.ts +38 -9
  5. package/dist/auth/providers.d.ts.map +1 -1
  6. package/dist/auth/providers.js +46 -3
  7. package/dist/connection/AbstractAbapConnection.d.ts +83 -136
  8. package/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
  9. package/dist/connection/AbstractAbapConnection.js +241 -763
  10. package/dist/connection/AdtCloudConnector.d.ts +12 -7
  11. package/dist/connection/AdtCloudConnector.d.ts.map +1 -1
  12. package/dist/connection/AdtCloudConnector.js +2 -6
  13. package/dist/connection/AdtOnPremConnector.d.ts +20 -7
  14. package/dist/connection/AdtOnPremConnector.d.ts.map +1 -1
  15. package/dist/connection/AdtOnPremConnector.js +2 -6
  16. package/dist/connection/CloudHttpTransport.d.ts +37 -0
  17. package/dist/connection/CloudHttpTransport.d.ts.map +1 -0
  18. package/dist/{session/CloudSecuritySessionStrategy.js → connection/CloudHttpTransport.js} +59 -49
  19. package/dist/connection/CredentialAbapConnection.d.ts +8 -41
  20. package/dist/connection/CredentialAbapConnection.d.ts.map +1 -1
  21. package/dist/connection/CredentialAbapConnection.js +44 -111
  22. package/dist/connection/HttpTransport.d.ts +178 -0
  23. package/dist/connection/HttpTransport.d.ts.map +1 -0
  24. package/dist/connection/HttpTransport.js +402 -0
  25. package/dist/connection/IAdtTransport.d.ts +232 -0
  26. package/dist/connection/IAdtTransport.d.ts.map +1 -0
  27. package/dist/connection/IAdtTransport.js +28 -0
  28. package/dist/connection/LegacyOnPremHttpTransport.d.ts +40 -0
  29. package/dist/connection/LegacyOnPremHttpTransport.d.ts.map +1 -0
  30. package/dist/connection/LegacyOnPremHttpTransport.js +57 -0
  31. package/dist/connection/OnPremHttpTransport.d.ts +45 -0
  32. package/dist/connection/OnPremHttpTransport.d.ts.map +1 -0
  33. package/dist/connection/OnPremHttpTransport.js +91 -0
  34. package/dist/connection/RfcTransport.d.ts +89 -0
  35. package/dist/connection/RfcTransport.d.ts.map +1 -0
  36. package/dist/connection/RfcTransport.js +270 -0
  37. package/dist/connection/rfcConversation.d.ts +44 -0
  38. package/dist/connection/rfcConversation.d.ts.map +1 -0
  39. package/dist/connection/rfcConversation.js +71 -0
  40. package/dist/index.d.ts +7 -8
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +21 -18
  43. package/dist/utils/timeouts.d.ts +6 -19
  44. package/dist/utils/timeouts.d.ts.map +1 -1
  45. package/dist/utils/timeouts.js +6 -22
  46. package/docs/INDEX.md +5 -2
  47. package/docs/INSTALLATION.md +28 -10
  48. package/docs/JWT_AUTH_TOOLS.md +20 -4
  49. package/docs/MIGRATION-6.0.md +359 -0
  50. package/docs/SCOPE.md +1 -1
  51. package/docs/STATEFUL_SESSION_GUIDE.md +86 -17
  52. package/docs/USAGE.md +260 -119
  53. package/examples/basic-connection.js +15 -3
  54. package/examples/jwt-with-token-refresh.js +15 -7
  55. package/examples/saml-connection.js +15 -2
  56. package/package.json +12 -11
  57. package/dist/__tests__/helpers/session.d.ts +0 -15
  58. package/dist/__tests__/helpers/session.d.ts.map +0 -1
  59. package/dist/__tests__/helpers/session.js +0 -19
  60. package/dist/auth/IAuthProvider.d.ts +0 -84
  61. package/dist/auth/IAuthProvider.d.ts.map +0 -1
  62. package/dist/auth/IAuthProvider.js +0 -21
  63. package/dist/connection/BaseAbapConnection.d.ts +0 -29
  64. package/dist/connection/BaseAbapConnection.d.ts.map +0 -1
  65. package/dist/connection/BaseAbapConnection.js +0 -81
  66. package/dist/connection/CertificateAbapConnection.d.ts +0 -35
  67. package/dist/connection/CertificateAbapConnection.d.ts.map +0 -1
  68. package/dist/connection/CertificateAbapConnection.js +0 -91
  69. package/dist/connection/JwtAbapConnection.d.ts +0 -131
  70. package/dist/connection/JwtAbapConnection.d.ts.map +0 -1
  71. package/dist/connection/JwtAbapConnection.js +0 -376
  72. package/dist/connection/KerberosAbapConnection.d.ts +0 -32
  73. package/dist/connection/KerberosAbapConnection.d.ts.map +0 -1
  74. package/dist/connection/KerberosAbapConnection.js +0 -128
  75. package/dist/connection/RfcAbapConnection.d.ts +0 -44
  76. package/dist/connection/RfcAbapConnection.d.ts.map +0 -1
  77. package/dist/connection/RfcAbapConnection.js +0 -324
  78. package/dist/connection/SamlAbapConnection.d.ts +0 -31
  79. package/dist/connection/SamlAbapConnection.d.ts.map +0 -1
  80. package/dist/connection/SamlAbapConnection.js +0 -81
  81. package/dist/connection/connectionFactory.d.ts +0 -25
  82. package/dist/connection/connectionFactory.d.ts.map +0 -1
  83. package/dist/connection/connectionFactory.js +0 -84
  84. package/dist/session/CloudSecuritySessionStrategy.d.ts +0 -32
  85. package/dist/session/CloudSecuritySessionStrategy.d.ts.map +0 -1
  86. package/dist/session/IcfSessionStrategy.d.ts +0 -27
  87. package/dist/session/IcfSessionStrategy.d.ts.map +0 -1
  88. package/dist/session/IcfSessionStrategy.js +0 -62
  89. package/dist/session/SessionStrategy.d.ts +0 -86
  90. package/dist/session/SessionStrategy.d.ts.map +0 -1
  91. package/dist/session/SessionStrategy.js +0 -33
  92. package/docs/superpowers/specs/2026-08-21-platform-connectors.md +0 -108
package/docs/USAGE.md CHANGED
@@ -13,10 +13,15 @@
13
13
 
14
14
  ## Quick Start
15
15
 
16
- ### Basic Usage with Factory
16
+ ### Basic Usage
17
17
 
18
18
  ```typescript
19
- import { createAbapConnection } from '@mcp-abap-adt/connection';
19
+ import {
20
+ AdtOnPremConnector,
21
+ BasicAuthProvider,
22
+ OnPremHttpTransport,
23
+ getTimeout,
24
+ } from '@mcp-abap-adt/connection';
20
25
  import { SapConfig } from '@mcp-abap-adt/connection';
21
26
 
22
27
  const config: SapConfig = {
@@ -35,12 +40,18 @@ const logger = {
35
40
  debug: (msg: string, meta?: any) => console.debug(msg, meta),
36
41
  };
37
42
 
38
- // Create connection (logger is optional)
39
- const connection = createAbapConnection(config, logger, undefined, undefined, {
40
- system: 'onprem', // or 'cloud' — said by you, never detected
41
- });
42
- // Or without logger:
43
- // const connection = createAbapConnection(config);
43
+ // Three things, all stated by you and none of them detected: which system
44
+ // (the class), which credential (the object), which wire (the transport, which
45
+ // has no default — you build it and hand it over).
46
+ const connection = new AdtOnPremConnector(
47
+ config,
48
+ new BasicAuthProvider(config.username!, config.password!),
49
+ new OnPremHttpTransport(() => ({}), logger, {
50
+ client: config.client,
51
+ baseUrl: config.url,
52
+ }),
53
+ logger, // optional; omit or pass null for no logging
54
+ );
44
55
 
45
56
  // Establish the session. This is REQUIRED: a request on a connection that was
46
57
  // never connected is refused with ADT_NOT_CONNECTED, and connect() rejects if
@@ -50,13 +61,14 @@ await connection.connect();
50
61
  const response = await connection.makeAdtRequest({
51
62
  method: 'GET',
52
63
  url: '/sap/bc/adt/repository/nodestructure',
64
+ timeout: getTimeout('default'),
53
65
  });
54
66
 
55
67
  console.log(response.data);
56
68
  ```
57
69
 
58
70
  Tearing the session down is `disconnect()`, but it is not on the
59
- `IAbapConnection` type this factory returns — see
71
+ `IAbapConnection` type a connector satisfies — see
60
72
  [Session Lifecycle](#session-lifecycle) for where it lives and how to reach it.
61
73
 
62
74
  ## Which system, and how you authenticate
@@ -79,6 +91,7 @@ For on-premise SAP systems using basic authentication:
79
91
  import {
80
92
  AdtOnPremConnector,
81
93
  BasicAuthProvider,
94
+ OnPremHttpTransport,
82
95
  } from '@mcp-abap-adt/connection';
83
96
 
84
97
  const config = {
@@ -92,6 +105,10 @@ const config = {
92
105
  const connection = new AdtOnPremConnector(
93
106
  config,
94
107
  new BasicAuthProvider(config.username, config.password),
108
+ new OnPremHttpTransport(() => ({}), logger, {
109
+ client: config.client,
110
+ baseUrl: config.url,
111
+ }),
95
112
  logger,
96
113
  );
97
114
  await connection.connect(); // required: nothing is established implicitly
@@ -105,7 +122,9 @@ For SAP BTP ABAP Environment. Token refresh belongs to
105
122
  ```typescript
106
123
  import {
107
124
  AdtCloudConnector,
125
+ CloudHttpTransport,
108
126
  TokenAuthProvider,
127
+ getTimeout,
109
128
  } from '@mcp-abap-adt/connection';
110
129
 
111
130
  const config = {
@@ -120,6 +139,10 @@ const config = {
120
139
  const connection = new AdtCloudConnector(
121
140
  config,
122
141
  new TokenAuthProvider('eyJhbGciOiJSUzI1NiIs...'),
142
+ new CloudHttpTransport(() => ({}), logger, {
143
+ client: config.client,
144
+ baseUrl: config.url,
145
+ }),
123
146
  logger,
124
147
  );
125
148
  await connection.connect(); // required: nothing is established implicitly
@@ -129,6 +152,7 @@ await connection.connect(); // required: nothing is established implicitly
129
152
  const response = await connection.makeAdtRequest({
130
153
  method: 'GET',
131
154
  url: '/sap/bc/adt/repository/nodestructure',
155
+ timeout: getTimeout('default'),
132
156
  });
133
157
  ```
134
158
 
@@ -142,6 +166,7 @@ them is refused with `ADT_NOT_CONNECTED`.
142
166
  ### GET Request
143
167
 
144
168
  ```typescript
169
+ import { getTimeout } from '@mcp-abap-adt/connection';
145
170
  const packages = await connection.makeAdtRequest({
146
171
  method: 'GET',
147
172
  url: '/sap/bc/adt/repository/nodestructure',
@@ -150,12 +175,14 @@ const packages = await connection.makeAdtRequest({
150
175
  parent_type: 'DEVC/K',
151
176
  withShortDescriptions: 'true',
152
177
  },
178
+ timeout: getTimeout('default'),
153
179
  });
154
180
  ```
155
181
 
156
182
  ### POST Request (Create Object)
157
183
 
158
184
  ```typescript
185
+ import { getTimeout } from '@mcp-abap-adt/connection';
159
186
  const classXml = `<?xml version="1.0" encoding="UTF-8"?>
160
187
  <class:abapClass xmlns:class="http://www.sap.com/adt/oo/classes"
161
188
  class:name="ZCL_MY_CLASS">
@@ -168,28 +195,35 @@ const response = await connection.makeAdtRequest({
168
195
  headers: { 'Content-Type': 'application/xml' },
169
196
  data: classXml,
170
197
  params: { package: 'ZTEST' },
198
+ timeout: getTimeout('default'),
171
199
  });
172
200
  ```
173
201
 
174
202
  ### PUT Request (Update Object)
175
203
 
176
204
  ```typescript
205
+ const classSourceCode = 'CLASS zcl_my_class DEFINITION ...';
206
+ const lockToken = 'the handle the lock call returned';
207
+ import { getTimeout } from '@mcp-abap-adt/connection';
177
208
  await connection.makeAdtRequest({
178
209
  method: 'PUT',
179
210
  url: '/sap/bc/adt/oo/classes/zcl_my_class/source/main',
180
211
  headers: { 'Content-Type': 'text/plain' },
181
- data: classSourceCode,
182
- params: { lockHandle: lockToken },
212
+ data: classSourceCode, // the new source, as a string
213
+ params: { lockHandle: lockToken }, // from the lock you took first
214
+ timeout: getTimeout('default'),
183
215
  });
184
216
  ```
185
217
 
186
218
  ### DELETE Request
187
219
 
188
220
  ```typescript
221
+ import { getTimeout } from '@mcp-abap-adt/connection';
189
222
  await connection.makeAdtRequest({
190
223
  method: 'DELETE',
191
224
  url: '/sap/bc/adt/oo/classes/zcl_my_class',
192
225
  params: { deleteOption: 'deleteAndLocalVersions' },
226
+ timeout: getTimeout('default'),
193
227
  });
194
228
  ```
195
229
 
@@ -200,13 +234,20 @@ await connection.makeAdtRequest({
200
234
  By default, connections are stateless - each request gets fresh cookies and CSRF tokens:
201
235
 
202
236
  ```typescript
203
- const connection = createAbapConnection(config, logger, undefined, undefined, {
204
- system: 'onprem', // or 'cloud' — said by you, never detected
205
- });
237
+ import { AdtOnPremConnector, BasicAuthProvider, OnPremHttpTransport, getTimeout } from '@mcp-abap-adt/connection';
238
+ const connection = new AdtOnPremConnector(
239
+ config,
240
+ new BasicAuthProvider(config.username!, config.password!),
241
+ new OnPremHttpTransport(() => ({}), logger, {
242
+ client: config.client,
243
+ baseUrl: config.url,
244
+ }),
245
+ logger,
246
+ );
206
247
  await connection.connect();
207
248
 
208
249
  // Each request is independent
209
- await connection.makeAdtRequest({ method: 'GET', url: '/sap/bc/adt/discovery' });
250
+ await connection.makeAdtRequest({ method: 'GET', url: '/sap/bc/adt/discovery' , timeout: getTimeout('default') });
210
251
  ```
211
252
 
212
253
  ### Stateful Mode (Session Headers)
@@ -214,16 +255,23 @@ await connection.makeAdtRequest({ method: 'GET', url: '/sap/bc/adt/discovery' })
214
255
  Enable stateful session mode for operations requiring consistent session state:
215
256
 
216
257
  ```typescript
217
- const connection = createAbapConnection(config, logger, undefined, undefined, {
218
- system: 'onprem', // or 'cloud' — said by you, never detected
219
- });
258
+ import { AdtOnPremConnector, BasicAuthProvider, OnPremHttpTransport, getTimeout } from '@mcp-abap-adt/connection';
259
+ const connection = new AdtOnPremConnector(
260
+ config,
261
+ new BasicAuthProvider(config.username!, config.password!),
262
+ new OnPremHttpTransport(() => ({}), logger, {
263
+ client: config.client,
264
+ baseUrl: config.url,
265
+ }),
266
+ logger,
267
+ );
220
268
  await connection.connect();
221
269
 
222
270
  // Enable stateful session mode (adds x-sap-adt-sessiontype: stateful header)
223
271
  connection.setSessionType('stateful');
224
272
 
225
273
  // Now all requests share the same session (cookies, CSRF token)
226
- await connection.makeAdtRequest({ method: 'GET', url: '/sap/bc/adt/discovery' });
274
+ await connection.makeAdtRequest({ method: 'GET', url: '/sap/bc/adt/discovery' , timeout: getTimeout('default') });
227
275
 
228
276
  // Check session mode
229
277
  console.log(connection.getSessionMode()); // 'stateful'
@@ -255,10 +303,12 @@ implied. Four things follow from it.
255
303
  > `IAbapConnection`: `ISessionLifecycleAware` — `disconnect()`, `isConnected()`,
256
304
  > `getSessionIdentity()`.
257
305
  >
258
- > The split is the point. `IAbapConnection` is the minimum every transport can
259
- > honour, and `RfcAbapConnection` has none of these — on RFC the session *is* the
260
- > open client, so it needs none. Requiring them of every connection would force a
261
- > transport with no HTTP session to implement a lie.
306
+ > The split is the point. `IAbapConnection` is the minimum any consumer of ADT
307
+ > can honour — a caller that only issues requests should not have to implement a
308
+ > teardown it never performs. Both connectors implement the atom, over either
309
+ > wire: an RFC conversation has the whole lifecycle, and what it has none of is a
310
+ > session RESOURCE to open and close by address, which is an empty mechanism
311
+ > rather than an absent lifecycle.
262
312
  >
263
313
  > So ask for the atom you need, not for a concrete class:
264
314
  >
@@ -269,24 +319,30 @@ implied. Four things follow from it.
269
319
  > **How you satisfy that parameter decides whether you get a compile-time
270
320
  > guarantee, and there is only one way that does.**
271
321
  >
272
- > Construct the connection through a concrete HTTP class and the compiler knows
273
- > it implements the atom, so passing an RFC connection is an error at the call
274
- > site:
322
+ > Construct a connector and the compiler knows it implements the atom, whichever
323
+ > wire you gave it:
275
324
  >
276
325
  > ```typescript
277
- > const conn = new AdtOnPremConnector(config, provider, logger);
278
- > tearDownAfter(conn); // ✅ checked
279
- > tearDownAfter(new RfcAbapConnection(cfg)); // ✅ compile error, as it should be
326
+ > const conn = new AdtOnPremConnector(config, provider, new OnPremHttpTransport(() => ({}), logger, { client: config.client, baseUrl: config.url }), logger);
327
+ > tearDownAfter(conn); // ✅ checked
328
+ >
329
+ > const overRfc = new AdtOnPremConnector(
330
+ > config,
331
+ > provider,
332
+ > new RfcTransport(rfcConversationFrom(config), logger),
333
+ > logger,
334
+ > );
335
+ > tearDownAfter(overRfc); // ✅ also checked — same class, different wire
280
336
  > ```
281
337
  >
282
- > `createAbapConnection()` cannot give you that. It returns `IAbapConnection` for
283
- > **every** config, RFC included, so the type carries no evidence either way and
284
- > the compiler rejects its result whatever the transport. Asserting past that —
285
- > `conn as IAbapConnection & ISessionLifecycleAware` — silences the error for the
286
- > HTTP case *and* for RFC, which then fails at runtime. An assertion is not a
287
- > check; it is a promise you make to the compiler on your own authority.
338
+ > What does NOT give you that is a bare `IAbapConnection` handed to you by
339
+ > somebody else: the type carries no evidence either way, and the compiler
340
+ > rejects it. Asserting past that — `conn as IAbapConnection &
341
+ > ISessionLifecycleAware` — silences the error whether or not the object has the
342
+ > methods. An assertion is not a check; it is a promise you make to the compiler
343
+ > on your own authority.
288
344
  >
289
- > When you only have an `IAbapConnection` — from the factory, or from a caller —
345
+ > When you only have an `IAbapConnection` — from a caller, or from a registry —
290
346
  > narrow it at runtime with a predicate:
291
347
  >
292
348
  > ```typescript
@@ -322,11 +378,15 @@ implied. Four things follow from it.
322
378
  ### connect() is required, and it tells the truth
323
379
 
324
380
  ```typescript
325
- import { AdtOnPremConnector, BasicAuthProvider } from '@mcp-abap-adt/connection';
381
+ import { AdtOnPremConnector, BasicAuthProvider, OnPremHttpTransport } from '@mcp-abap-adt/connection';
326
382
 
327
383
  const connection = new AdtOnPremConnector(
328
384
  config,
329
- new BasicAuthProvider(user, pass),
385
+ new BasicAuthProvider(config.username!, config.password!),
386
+ new OnPremHttpTransport(() => ({}), logger, {
387
+ client: config.client,
388
+ baseUrl: config.url,
389
+ }),
330
390
  logger,
331
391
  );
332
392
 
@@ -348,6 +408,13 @@ side instead of a condition that silently stops matching. The codes live in
348
408
 
349
409
  ```typescript
350
410
  import { ADT_SESSION_ERROR } from '@mcp-abap-adt/interfaces';
411
+ import { getTimeout } from '@mcp-abap-adt/connection';
412
+
413
+ const options = {
414
+ method: 'GET',
415
+ url: '/sap/bc/adt/repository/nodestructure',
416
+ timeout: getTimeout('default'),
417
+ };
351
418
 
352
419
  try {
353
420
  await connection.makeAdtRequest(options);
@@ -360,9 +427,9 @@ try {
360
427
  ```
361
428
 
362
429
  `AdtSessionErrorCode` comes from the same place, as does the capability interface
363
- this connection implements — `ISessionLifecycleAware`. Depend on that rather than
364
- on a concrete connection class where you can: an `RfcAbapConnection` is a valid
365
- `IAbapConnection` that does not implement it, so the atom you require is also the
430
+ this connection implements — `ISessionLifecycleAware`. Depend on the atom rather
431
+ than on a concrete class where you can: an `IAbapConnection` may come from
432
+ somewhere that implements only the minimum, so the atom you require is also the
366
433
  documentation of what your code actually needs.
367
434
 
368
435
  ### disconnect() waits for nothing
@@ -456,14 +523,29 @@ yours.
456
523
  Session IDs are auto-generated (UUID) when connection is created:
457
524
 
458
525
  ```typescript
459
- const connection = createAbapConnection(config, logger, undefined, undefined, {
460
- system: 'onprem', // or 'cloud' — said by you, never detected
461
- });
526
+ import { AdtOnPremConnector, BasicAuthProvider, OnPremHttpTransport } from '@mcp-abap-adt/connection';
527
+ const connection = new AdtOnPremConnector(
528
+ config,
529
+ new BasicAuthProvider(config.username!, config.password!),
530
+ new OnPremHttpTransport(() => ({}), logger, {
531
+ client: config.client,
532
+ baseUrl: config.url,
533
+ }),
534
+ logger,
535
+ );
462
536
  console.log(connection.getSessionId()); // e.g., '7f3a8b2c-...'
463
537
 
464
538
  // Or provide your own when creating connection
465
- const connection = createAbapConnection(config, logger, 'custom-session-123');
466
- console.log(connection.getSessionId()); // 'custom-session-123'
539
+ const connectionWithOwnId = new AdtOnPremConnector(
540
+ config,
541
+ new BasicAuthProvider(config.username!, config.password!),
542
+ new OnPremHttpTransport(() => ({}), logger, {
543
+ client: config.client,
544
+ baseUrl: config.url,
545
+ }),
546
+ logger,
547
+ 'custom-session-123');
548
+ console.log(connectionWithOwnId.getSessionId()); // 'custom-session-123'
467
549
  ```
468
550
 
469
551
  ### Switching Session Types
@@ -471,17 +553,24 @@ console.log(connection.getSessionId()); // 'custom-session-123'
471
553
  Dynamically switch between stateful and stateless modes:
472
554
 
473
555
  ```typescript
556
+ import { AdtOnPremConnector, BasicAuthProvider, OnPremHttpTransport, getTimeout } from '@mcp-abap-adt/connection';
474
557
  // Start in stateless mode (default)
475
- const connection = createAbapConnection(config, logger, undefined, undefined, {
476
- system: 'onprem', // or 'cloud' — said by you, never detected
477
- });
558
+ const connection = new AdtOnPremConnector(
559
+ config,
560
+ new BasicAuthProvider(config.username!, config.password!),
561
+ new OnPremHttpTransport(() => ({}), logger, {
562
+ client: config.client,
563
+ baseUrl: config.url,
564
+ }),
565
+ logger,
566
+ );
478
567
  await connection.connect();
479
568
 
480
569
  // Enable stateful for a series of operations
481
570
  connection.setSessionType('stateful');
482
571
 
483
572
  // Do stateful operations...
484
- await connection.makeAdtRequest({ method: 'POST', url: '...' });
573
+ await connection.makeAdtRequest({ method: 'POST', url: '...' , timeout: getTimeout('default') });
485
574
 
486
575
  // Switch back to stateless
487
576
  connection.setSessionType('stateless');
@@ -506,7 +595,7 @@ in a `finally`. `disconnect()` never throws, so it is safe there.
506
595
  ### Using Custom Logger
507
596
 
508
597
  ```typescript
509
- import { ILogger } from '@mcp-abap-adt/connection';
598
+ import { AdtOnPremConnector, BasicAuthProvider, ILogger, OnPremHttpTransport } from '@mcp-abap-adt/connection';
510
599
 
511
600
  class CustomLogger implements ILogger {
512
601
  info(message: string, meta?: any) {
@@ -539,9 +628,15 @@ class CustomLogger implements ILogger {
539
628
  }
540
629
 
541
630
  const logger = new CustomLogger();
542
- const connection = createAbapConnection(config, logger, undefined, undefined, {
543
- system: 'onprem', // or 'cloud' — said by you, never detected
544
- });
631
+ const connection = new AdtOnPremConnector(
632
+ config,
633
+ new BasicAuthProvider(config.username!, config.password!),
634
+ new OnPremHttpTransport(() => ({}), logger, {
635
+ client: config.client,
636
+ baseUrl: config.url,
637
+ }),
638
+ logger,
639
+ );
545
640
  ```
546
641
 
547
642
  ## Error Handling
@@ -549,16 +644,23 @@ const connection = createAbapConnection(config, logger, undefined, undefined, {
549
644
  ### Basic Error Handling
550
645
 
551
646
  ```typescript
647
+ import { getTimeout } from '@mcp-abap-adt/connection';
552
648
  try {
553
649
  await connection.makeAdtRequest({
554
650
  method: 'GET',
555
651
  url: '/sap/bc/adt/invalid/endpoint',
652
+ timeout: getTimeout('default'),
556
653
  });
557
654
  } catch (error) {
558
- if (error.response) {
559
- console.error(`HTTP ${error.response.status}:`, error.response.data);
655
+ const failure = error as {
656
+ code?: string;
657
+ response?: { status: number; data: unknown };
658
+ message?: string;
659
+ };
660
+ if (failure.response) {
661
+ console.error(`HTTP ${failure.response.status}:`, failure.response.data);
560
662
  } else {
561
- console.error('Network error:', error.message);
663
+ console.error('Network error:', failure.message);
562
664
  }
563
665
  }
564
666
  ```
@@ -580,23 +682,30 @@ When these errors occur, the connection:
580
682
  3. **Logs the error** with full context for troubleshooting
581
683
 
582
684
  ```typescript
685
+ import { getTimeout } from '@mcp-abap-adt/connection';
583
686
  try {
584
687
  await connection.makeAdtRequest({
585
688
  method: 'GET',
586
689
  url: '/sap/bc/adt/repository/nodestructure',
690
+ timeout: getTimeout('default'),
587
691
  });
588
692
  } catch (error) {
693
+ const failure = error as {
694
+ code?: string;
695
+ response?: { status: number; data: unknown };
696
+ message?: string;
697
+ };
589
698
  // Check for specific network error codes
590
- if (error.code === 'ECONNREFUSED') {
699
+ if (failure.code === 'ECONNREFUSED') {
591
700
  console.error('Cannot connect to SAP server - check VPN connection');
592
- } else if (error.code === 'ETIMEDOUT') {
701
+ } else if (failure.code === 'ETIMEDOUT') {
593
702
  console.error('Connection timeout - server not responding');
594
- } else if (error.code === 'ENOTFOUND') {
703
+ } else if (failure.code === 'ENOTFOUND') {
595
704
  console.error('Cannot resolve hostname - check SAP URL');
596
- } else if (error.response) {
597
- console.error(`HTTP ${error.response.status}:`, error.response.data);
705
+ } else if (failure.response) {
706
+ console.error(`HTTP ${failure.response.status}:`, failure.response.data);
598
707
  } else {
599
- console.error('Request failed:', error.message);
708
+ console.error('Request failed:', failure.message);
600
709
  }
601
710
  }
602
711
  ```
@@ -616,6 +725,8 @@ try {
616
725
  connection satisfies, including RFC:
617
726
 
618
727
  ```typescript
728
+ import { AbapRequestOptions } from '@mcp-abap-adt/connection';
729
+ import type { AxiosResponse } from 'axios';
619
730
  interface AbapConnection {
620
731
  connect(): Promise<void>; // REQUIRED before any request
621
732
  makeAdtRequest(options: AbapRequestOptions): Promise<AxiosResponse>;
@@ -625,27 +736,42 @@ interface AbapConnection {
625
736
  }
626
737
  ```
627
738
 
628
- Anything beyond that is **not** on the contract. `getSessionMode()`
629
- and the session lifecycle (`disconnect()`, `isConnected()`,
630
- `getSessionIdentity()`) live on the HTTP
631
- connection classes; `RfcAbapConnection` has some of them and not others, so
632
- reach for them through a concrete type rather than through what
633
- `createAbapConnection()` returns.
739
+ Anything beyond that is **not** on the contract. `getSessionMode()` and the
740
+ session lifecycle (`disconnect()`, `isConnected()`, `getSessionIdentity()`) live
741
+ on the connectors — both of them, over either wire — so reach for them through a
742
+ connector type or through the `ISessionLifecycleAware` atom, not through a bare
743
+ `IAbapConnection` somebody handed you.
634
744
 
635
745
  ### `AdtOnPremConnector` / `AdtCloudConnector`
636
746
 
637
747
  One per system, each handed an auth provider:
638
748
 
639
- ```typescript
640
- class AdtOnPremConnector extends CredentialAbapConnection {
749
+ ```text
750
+ class AdtOnPremConnector<
751
+ TCredential extends IAuthProvider = IAuthProvider,
752
+ TTransport extends IOnPremTransport = OnPremHttpTransport,
753
+ > {
754
+ constructor(
755
+ config: SapConfig,
756
+ credential: TCredential,
757
+ transport: TTransport,
758
+ logger?: ILogger | null,
759
+ sessionId?: string,
760
+ );
761
+ }
762
+
763
+ class AdtCloudConnector<
764
+ TCredential extends IAuthProvider = IAuthProvider,
765
+ TTransport extends ICloudTransport = CloudHttpTransport,
766
+ > {
641
767
  constructor(
642
768
  config: SapConfig,
643
- provider: IAuthProvider,
769
+ credential: TCredential,
770
+ transport: TTransport,
644
771
  logger?: ILogger | null,
645
772
  sessionId?: string,
646
773
  );
647
774
  }
648
- // AdtCloudConnector has the same shape.
649
775
  ```
650
776
 
651
777
  The difference is what each does with the session: on-prem takes it from the
@@ -653,23 +779,47 @@ establishing call and gives it back with the platform's ICF logoff; cloud opens
653
779
  one at `/sap/bc/adt/core/http/sessions` and gives it back by `DELETE` on the
654
780
  address the server publishes.
655
781
 
656
- ### `BaseAbapConnection` (Basic Auth) — deprecated
782
+ **Both take a transport, and neither defaults one.** What differs is the CHOICE,
783
+ and the type parameter is where that is said: `TTransport` is bound to
784
+ `IOnPremTransport` on one and to `ICloudTransport` on the other, so on-prem
785
+ admits HTTP or RFC while "cloud over RFC" does not compile — there is no such
786
+ deployment. The parameter also records what a connection was built with, so a
787
+ signature can ask for `AdtOnPremConnector<IAuthProvider, RfcTransport>` and be
788
+ given one, instead of taking any connection and casting.
657
789
 
658
- The previous shape, where the class stated the credential. Still works; see
659
- [Migration to 5.0](./MIGRATION-5.0.md).
790
+ ### `HttpTransport` / `RfcTransport`
660
791
 
661
- ```typescript
662
- class BaseAbapConnection extends AbstractAbapConnection {
663
- constructor(config: SapConfig, logger?: ILogger | null, sessionId?: string);
664
- }
792
+ What a request travels over, and everything true of that wire: addressing,
793
+ establishing, and whatever session state it keeps.
794
+
795
+ ```text
796
+ new HttpTransport(agentOptions?, logger?, { client?, baseUrl? })
797
+ new RfcTransport(connect: () => IRfcConversation, logger?)
665
798
  ```
666
799
 
800
+ `HttpTransport` is the ordinary wire, and you name it because the connector
801
+ takes no default. In practice you name one of its two subclasses — the session
802
+ mechanism is what differs, and `OnPremHttpTransport` / `CloudHttpTransport` are
803
+ what the connectors' type parameters admit. `RfcTransport`
804
+ you build with `rfcConversationFrom(config)`, which derives `ashost` and `sysnr`
805
+ and loads the SAP NW RFC SDK only when a conversation opens.
806
+
807
+ The two differ in what they have, not in what they are asked:
808
+
809
+ | | HTTP | RFC |
810
+ |---|---|---|
811
+ | session is | an ICF session, addressed by `SAP_SESSIONID` | the conversation itself |
812
+ | `establish()` | earns a CSRF token, and the cookies with it | nothing to earn |
813
+ | cookies | a jar, replayed on every request | none, ever |
814
+ | affinity | `sap-adt-saplb`, to stay on one app server | none |
815
+ | visible in | SM05 | SMGW → Logged on Clients |
816
+
667
817
  ### `CSRF_CONFIG` and `CSRF_ERROR_MESSAGES` (New in 0.1.13+)
668
818
 
669
819
  Exported constants for consistent CSRF token handling across different connection implementations:
670
820
 
671
- ```typescript
672
- import { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from '@mcp-abap-adt/connection';
821
+ ```text
822
+ // Imported from '@mcp-abap-adt/connection'; shapes, not runnable code.
673
823
 
674
824
  // CSRF_CONFIG structure:
675
825
  const config = {
@@ -726,7 +876,7 @@ export class CloudSdkAbapConnection {
726
876
  throw new Error(
727
877
  CSRF_ERROR_MESSAGES.FETCH_FAILED(
728
878
  CSRF_CONFIG.RETRY_COUNT + 1,
729
- error instanceof Error ? error.message : String(error)
879
+ error instanceof Error ? failure.message : String(error)
730
880
  )
731
881
  );
732
882
  }
@@ -740,52 +890,43 @@ export class CloudSdkAbapConnection {
740
890
  ```
741
891
 
742
892
 
743
- ### `JwtAbapConnection` (JWT/OAuth2) — deprecated
893
+ ### The credential, and how a refusal is classified
744
894
 
745
- For SAP BTP cloud systems. Token refresh is handled by `@mcp-abap-adt/auth-broker`:
895
+ For SAP BTP cloud systems, hand `AdtCloudConnector` a `TokenAuthProvider`. A bare
896
+ string is a token with nothing behind it; an `ITokenRefresher` is a provider that
897
+ checks expiry and renews on its own, which is what you want in anything
898
+ long-lived. Obtaining tokens in the first place is `@mcp-abap-adt/auth-broker`'s
899
+ job, not this package's.
746
900
 
747
- ```typescript
748
- class JwtAbapConnection extends AbstractAbapConnection {
749
- constructor(
750
- config: SapConfig,
751
- logger?: ILogger | null,
752
- sessionId?: string,
753
- tokenRefresher?: ITokenRefresher,
754
- );
755
- // Note: refreshToken() and canRefreshToken() methods removed in 0.2.0
756
- // Token acquisition itself belongs to @mcp-abap-adt/auth-broker
757
- }
901
+ ```text
902
+ new AdtCloudConnector(
903
+ config,
904
+ new TokenAuthProvider(refresher), // or a bare token string
905
+ new CloudHttpTransport(() => ({}), logger, { client: config.client, baseUrl: config.url }),
906
+ logger,
907
+ );
758
908
  ```
759
909
 
760
- **How failures are classified** (4.0.0 — see
761
- [MIGRATION-4.0.md](./MIGRATION-4.0.md)):
910
+ **How failures are classified** (6.0.0 — see
911
+ [MIGRATION-6.0.md](./MIGRATION-6.0.md)):
762
912
 
763
913
  | answer | what happens |
764
914
  |---|---|
765
- | **401** | with an injected `ITokenRefresher`, the token is refreshed, the SAP session re-established, and the request retried once. Without one, or when the retry is refused too, the server's error is rethrown |
915
+ | **401** | **surfaces.** Nothing here decides to get a new credential. An EXPIRED token is already replaced without anyone deciding — the provider is asked per request and checks expiry before answering — so a 401 is the other case: a credential the source still believes in and the server refuses. Whether that means "stale" is a judgement made with what you know, and `renew()` on an `IRenewableCredential` is the seam you make it with. The session is untouched: a refused credential is not a lost session |
766
916
  | **403** | propagates untouched. The server authenticated the caller and refused the action anyway, so a new token is the same caller — usually the body names the authorization object |
767
917
  | anything else | untouched |
768
918
 
769
- The connection never replaces the server's error with one of its own: `error.response.status` and
770
- `error.response.data` are always what SAP sent. Before 4.0.0 both 401 and 403 were reported as
771
- `JWT token has expired. Please re-authenticate.` with the original error discarded.
772
-
773
- Concurrent requests that meet the same expired token share **one** renewal — a single token fetch
774
- and a single session re-establishment between them, not one each.
919
+ The connection never replaces the server's error with one of its own:
920
+ `failure.response.status` and `failure.response.data` are always what SAP sent.
775
921
 
776
- ### `createAbapConnection()` Factory
777
-
778
- Recommended way to create connections:
779
-
780
- ```typescript
781
- function createAbapConnection(
782
- config: SapConfig,
783
- logger?: ILogger | null,
784
- sessionId?: string
785
- ): AbapConnection;
786
- ```
922
+ Concurrent requests that meet the same expired token share **one** renewal — a
923
+ single token fetch and a single session re-establishment between them, not one
924
+ each.
787
925
 
788
- Auto-detects auth type and returns appropriate connection instance.
926
+ **A refusal during `connect()` surfaces.** Nothing is renewed behind you there:
927
+ the provider already renews on expiry it can see, every time it is asked for a
928
+ header, so a refusal at establishment means the credential needs attention that
929
+ this library cannot give it.
789
930
 
790
931
  ### Configuration Types
791
932
 
@@ -817,11 +958,11 @@ See [examples/](../examples/) for complete working examples:
817
958
 
818
959
  ## Best Practices
819
960
 
820
- 1. **Use Factory Function**: Prefer `createAbapConnection()` over direct instantiation - it auto-detects auth type
961
+ 1. **State the three axes**: the connector says which system, the provider says which credential, the transport says which wire. Nothing is detected, and a connection that had to guess would guess wrong on the case that matters
821
962
  2. **Enable Stateful Mode**: Use `setSessionType('stateful')` for multi-request operations (locks, transactions)
822
963
  3. **Token Refresh**: For cloud systems, use `@mcp-abap-adt/auth-broker` for token refresh functionality
823
964
  4. **Session State Persistence**: Use `@mcp-abap-adt/auth-broker` for session state persistence
824
- 5. **Handle Errors Gracefully**: Wrap requests in try-catch blocks and check `error.response` for HTTP errors
965
+ 5. **Handle Errors Gracefully**: Wrap requests in try-catch blocks and check `failure.response` for HTTP errors
825
966
  6. **Use Proper Logging**: Implement custom logger for production systems with appropriate log levels (logger is optional)
826
967
  7. **Session ID Management**: Session IDs are auto-generated (UUID) or can be provided when creating connection
827
968
  8. **Switch Session Types**: Use `setSessionType()` to dynamically change between stateful/stateless modes