@mcp-abap-adt/connection 4.0.0 → 6.0.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 (87) hide show
  1. package/CHANGELOG.md +407 -1
  2. package/README.md +239 -53
  3. package/dist/auth/providers.d.ts +124 -0
  4. package/dist/auth/providers.d.ts.map +1 -0
  5. package/dist/auth/providers.js +183 -0
  6. package/dist/connection/AbstractAbapConnection.d.ts +209 -57
  7. package/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
  8. package/dist/connection/AbstractAbapConnection.js +459 -457
  9. package/dist/connection/AdtCloudConnector.d.ts +34 -0
  10. package/dist/connection/AdtCloudConnector.d.ts.map +1 -0
  11. package/dist/connection/AdtCloudConnector.js +27 -0
  12. package/dist/connection/AdtOnPremConnector.d.ts +43 -0
  13. package/dist/connection/AdtOnPremConnector.d.ts.map +1 -0
  14. package/dist/connection/AdtOnPremConnector.js +28 -0
  15. package/dist/connection/CloudHttpTransport.d.ts +37 -0
  16. package/dist/connection/CloudHttpTransport.d.ts.map +1 -0
  17. package/dist/connection/CloudHttpTransport.js +145 -0
  18. package/dist/connection/CredentialAbapConnection.d.ts +55 -0
  19. package/dist/connection/CredentialAbapConnection.d.ts.map +1 -0
  20. package/dist/connection/CredentialAbapConnection.js +128 -0
  21. package/dist/connection/HttpTransport.d.ts +178 -0
  22. package/dist/connection/HttpTransport.d.ts.map +1 -0
  23. package/dist/connection/HttpTransport.js +402 -0
  24. package/dist/connection/IAdtTransport.d.ts +232 -0
  25. package/dist/connection/IAdtTransport.d.ts.map +1 -0
  26. package/dist/connection/IAdtTransport.js +28 -0
  27. package/dist/connection/LegacyOnPremHttpTransport.d.ts +40 -0
  28. package/dist/connection/LegacyOnPremHttpTransport.d.ts.map +1 -0
  29. package/dist/connection/LegacyOnPremHttpTransport.js +57 -0
  30. package/dist/connection/OnPremHttpTransport.d.ts +45 -0
  31. package/dist/connection/OnPremHttpTransport.d.ts.map +1 -0
  32. package/dist/connection/OnPremHttpTransport.js +91 -0
  33. package/dist/connection/RfcTransport.d.ts +89 -0
  34. package/dist/connection/RfcTransport.d.ts.map +1 -0
  35. package/dist/connection/RfcTransport.js +256 -0
  36. package/dist/connection/rfcConversation.d.ts +44 -0
  37. package/dist/connection/rfcConversation.d.ts.map +1 -0
  38. package/dist/connection/rfcConversation.js +71 -0
  39. package/dist/index.d.ts +10 -7
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +30 -18
  42. package/dist/session/SessionLifecycle.d.ts +17 -2
  43. package/dist/session/SessionLifecycle.d.ts.map +1 -1
  44. package/dist/session/SessionLifecycle.js +17 -2
  45. package/dist/utils/cookies.d.ts +12 -0
  46. package/dist/utils/cookies.d.ts.map +1 -0
  47. package/dist/utils/cookies.js +24 -0
  48. package/dist/utils/timeouts.d.ts +6 -3
  49. package/dist/utils/timeouts.d.ts.map +1 -1
  50. package/dist/utils/timeouts.js +6 -3
  51. package/docs/INDEX.md +5 -2
  52. package/docs/INSTALLATION.md +28 -6
  53. package/docs/JWT_AUTH_TOOLS.md +20 -4
  54. package/docs/MIGRATION-2.0.md +1 -1
  55. package/docs/MIGRATION-5.0.md +116 -0
  56. package/docs/MIGRATION-6.0.md +359 -0
  57. package/docs/SCOPE.md +1 -1
  58. package/docs/STATEFUL_SESSION_GUIDE.md +155 -20
  59. package/docs/USAGE.md +322 -111
  60. package/examples/basic-connection.js +15 -3
  61. package/examples/jwt-with-token-refresh.js +15 -7
  62. package/examples/saml-connection.js +15 -2
  63. package/package.json +12 -10
  64. package/dist/__tests__/helpers/session.d.ts +0 -15
  65. package/dist/__tests__/helpers/session.d.ts.map +0 -1
  66. package/dist/__tests__/helpers/session.js +0 -19
  67. package/dist/connection/BaseAbapConnection.d.ts +0 -23
  68. package/dist/connection/BaseAbapConnection.d.ts.map +0 -1
  69. package/dist/connection/BaseAbapConnection.js +0 -75
  70. package/dist/connection/CertificateAbapConnection.d.ts +0 -25
  71. package/dist/connection/CertificateAbapConnection.d.ts.map +0 -1
  72. package/dist/connection/CertificateAbapConnection.js +0 -79
  73. package/dist/connection/JwtAbapConnection.d.ts +0 -115
  74. package/dist/connection/JwtAbapConnection.d.ts.map +0 -1
  75. package/dist/connection/JwtAbapConnection.js +0 -358
  76. package/dist/connection/KerberosAbapConnection.d.ts +0 -24
  77. package/dist/connection/KerberosAbapConnection.d.ts.map +0 -1
  78. package/dist/connection/KerberosAbapConnection.js +0 -120
  79. package/dist/connection/RfcAbapConnection.d.ts +0 -49
  80. package/dist/connection/RfcAbapConnection.d.ts.map +0 -1
  81. package/dist/connection/RfcAbapConnection.js +0 -331
  82. package/dist/connection/SamlAbapConnection.d.ts +0 -25
  83. package/dist/connection/SamlAbapConnection.d.ts.map +0 -1
  84. package/dist/connection/SamlAbapConnection.js +0 -75
  85. package/dist/connection/connectionFactory.d.ts +0 -9
  86. package/dist/connection/connectionFactory.d.ts.map +0 -1
  87. package/dist/connection/connectionFactory.js +0 -32
package/README.md CHANGED
@@ -17,8 +17,8 @@ ABAP connection layer for MCP ABAP ADT server. Provides a unified interface for
17
17
  - Session headers management (cookies, CSRF tokens)
18
18
  - Session state persistence is handled by `@mcp-abap-adt/auth-broker` package
19
19
  - 🏗️ **Clean Architecture**:
20
- - Abstract base class for common HTTP/session logic
21
- - Auth-type specific implementations (BaseAbapConnection, JwtAbapConnection, SamlAbapConnection)
20
+ - One connector per SYSTEM (`AdtOnPremConnector`, `AdtCloudConnector`), handed an auth provider
21
+ - Authentication is a parameter, not a subclass — nothing about the system is inferred from it
22
22
  - Proper separation of concerns - no JWT logic in base class
23
23
  - 🔌 **Realtime Transport Scaffold**:
24
24
  - Generic `GenericWebSocketTransport` with pluggable WS factory
@@ -39,21 +39,41 @@ The package uses a clean separation of concerns:
39
39
  - CSRF token fetching with retry
40
40
  - Auth-agnostic - knows nothing about Basic or JWT
41
41
 
42
- - **`BaseAbapConnection`** (concrete, exported):
43
- - Basic Authentication implementation
44
- - `connect()` establishes the session (required before any request)
45
- - Suitable for on-premise SAP systems
46
-
47
- - **`JwtAbapConnection`** (concrete, exported):
48
- - JWT/OAuth2 Authentication implementation
49
- - `connect()` establishes the session with the JWT token (required before any request)
50
- - Suitable for SAP BTP ABAP Environment
51
- - Token refresh handled by auth-broker package
52
-
53
- - **`SamlAbapConnection`** (concrete, exported):
54
- - Session-cookie-based authentication (`authType: "saml"`)
55
- - Uses existing SSO/SAML session cookies
56
- - Fetches CSRF token and executes ADT requests in same HTTP model
42
+ - **`AdtOnPremConnector`** (concrete, exported):
43
+ - An on-prem system: the session arrives with the establishing call, and the platform's
44
+ ICF logoff is how it is given back
45
+ - Takes an auth provider — basic, SAML, certificate, a bearer token, whatever you hold
46
+
47
+ - **`AdtCloudConnector`** (concrete, exported):
48
+ - An ABAP Cloud system: a session is a resource, opened at
49
+ `/sap/bc/adt/core/http/sessions` and given back by `DELETE` on the address it publishes
50
+ - Takes an auth provider, same as above
51
+
52
+ **Which one you take is how you say where you are dialling.** Nothing is probed:
53
+ the session resource answers on on-prem too, and its `DELETE` there leaves the
54
+ session open while the logoff removes it — so asking the server would pick the
55
+ mechanism that releases nothing.
56
+
57
+ - **Auth providers** (`BasicAuthProvider`, `TokenAuthProvider`, `SamlAuthProvider`,
58
+ `CertificateAuthProvider`):
59
+ - What a connection authenticates with, passed in
60
+ - A token provider renews on its own, and the connector asks it per request — which
61
+ is how a token that expired between two requests is replaced with nobody
62
+ deciding to replace it
63
+ - A `401` **surfaces**. Whether a refusal meant "the token is stale" or "these
64
+ credentials are refused" is a judgement made with what you know, so the
65
+ connector does not answer it for you. A credential that can be told to get a
66
+ new one says so through `IRenewableCredential`, which you narrow to
67
+
68
+ - **Transports** (`OnPremHttpTransport`, `CloudHttpTransport`, `RfcTransport`):
69
+ - What a request travels over, and everything that is true of that wire.
70
+ `HttpTransport` keeps the cookie jar, the CSRF token and the affinity
71
+ headers; `RfcTransport` translates into `SADT_REST_RFC_ENDPOINT` and keeps a
72
+ conversation that IS the session
73
+ - On-prem is where this is a real choice; ABAP Cloud has one wire and its
74
+ connector takes no such parameter
75
+ - `rfcConversationFrom(config)` builds what `RfcTransport` needs, deriving
76
+ `ashost` and `sysnr` and loading the SDK only when a conversation opens
57
77
 
58
78
  - **`GenericWebSocketTransport`** (concrete, exported):
59
79
  - Transport abstraction for realtime WS message flows
@@ -113,6 +133,7 @@ This package interacts with external packages **ONLY through interfaces**:
113
133
 
114
134
  - 📦 **[Installation Guide](./docs/INSTALLATION.md)** - Setup and installation instructions
115
135
  - 📚 **[Usage Guide](./docs/USAGE.md)** - Detailed usage examples and API documentation
136
+ - 🚚 **[Migration to 6.0.0](./docs/MIGRATION-6.0.md)** - the factory and the per-credential classes are removed; RFC is a transport, not a class
116
137
  - 🚚 **[Migration to 4.0.0](./docs/MIGRATION-4.0.md)** - a 401 refreshes the token, a 403 reaches you with the server's message; the synthesised "JWT token has expired" is gone
117
138
  - 🚚 **[Migration: the explicit session lifecycle](./docs/MIGRATION-2.0.md)** - `connect()` is now required; start here if you are coming from 1.x
118
139
  - 💡 **[Examples](./examples/)** - Working code examples
@@ -139,7 +160,13 @@ For detailed installation instructions, see [Installation Guide](./docs/INSTALLA
139
160
  ### Basic Usage (On-Premise)
140
161
 
141
162
  ```typescript
142
- import { createAbapConnection, SapConfig } from "@mcp-abap-adt/connection";
163
+ import {
164
+ AdtOnPremConnector,
165
+ BasicAuthProvider,
166
+ OnPremHttpTransport,
167
+ SapConfig,
168
+ getTimeout,
169
+ } from "@mcp-abap-adt/connection";
143
170
 
144
171
  const config: SapConfig = {
145
172
  url: "https://your-sap-system.com",
@@ -157,21 +184,37 @@ const logger = {
157
184
  debug: (msg: string, meta?: any) => console.debug(msg, meta),
158
185
  };
159
186
 
160
- // Create connection
161
- const connection = createAbapConnection(config, logger);
187
+ // Which system you are dialling is the class you take; which credential it
188
+ // authenticates with is the object you hand it. Neither is detected.
189
+ const connection = new AdtOnPremConnector(
190
+ config,
191
+ new BasicAuthProvider(config.username!, config.password!),
192
+ new OnPremHttpTransport(() => ({}), logger, {
193
+ client: config.client,
194
+ baseUrl: config.url,
195
+ }),
196
+ logger,
197
+ );
162
198
  await connection.connect(); // required before any request
163
199
 
164
200
  // Make ADT request
165
201
  const response = await connection.makeAdtRequest({
166
202
  method: "GET",
167
203
  url: "/sap/bc/adt/programs/programs/your-program",
204
+ timeout: getTimeout("default"),
168
205
  });
169
206
  ```
170
207
 
171
208
  ### Cloud Usage (JWT/OAuth2)
172
209
 
173
210
  ```typescript
174
- import { createAbapConnection, SapConfig } from "@mcp-abap-adt/connection";
211
+ import {
212
+ AdtCloudConnector,
213
+ CloudHttpTransport,
214
+ SapConfig,
215
+ TokenAuthProvider,
216
+ getTimeout,
217
+ } from "@mcp-abap-adt/connection";
175
218
 
176
219
  // JWT configuration
177
220
  const config: SapConfig = {
@@ -188,21 +231,77 @@ const logger = {
188
231
  debug: (msg: string, meta?: any) => console.debug(msg, meta),
189
232
  };
190
233
 
191
- // Logger is optional - if not provided, no logging output
192
- const connection = createAbapConnection(config, logger);
234
+ // Logger is optional - if not provided, no logging output.
235
+ // A bare string is a token with nothing behind it. Hand `TokenAuthProvider` an
236
+ // `ITokenRefresher` instead and it checks expiry and renews on its own, which
237
+ // is what you want in anything long-lived.
238
+ const connection = new AdtCloudConnector(
239
+ config,
240
+ new TokenAuthProvider(config.jwtToken!),
241
+ new CloudHttpTransport(() => ({}), logger, {
242
+ client: config.client,
243
+ baseUrl: config.url,
244
+ }),
245
+ logger,
246
+ );
193
247
  await connection.connect();
194
248
 
195
- // Note: Token refresh is handled by @mcp-abap-adt/auth-broker package
249
+ // Note: obtaining and refreshing tokens is @mcp-abap-adt/auth-broker's job
196
250
  const response = await connection.makeAdtRequest({
197
251
  method: "GET",
198
252
  url: "/sap/bc/adt/programs/programs/your-program",
253
+ timeout: getTimeout("default"),
199
254
  });
200
255
  ```
201
256
 
257
+ ### On-Premise over RFC
258
+
259
+ The same ADT calls, over `SADT_REST_RFC_ENDPOINT` — the function module Eclipse
260
+ ADT itself uses through JCo — instead of over HTTP. Worth taking on a system
261
+ where stateful HTTP sessions are not usable: an RFC conversation is one ABAP
262
+ session for its whole lifetime, which is the way past `423 invalid lock handle`
263
+ on BASIS < 7.50.
264
+
265
+ Needs the SAP NW RFC SDK on the machine and `npm install @mcp-abap-adt/sap-rfc-lite`.
266
+
267
+ ```typescript
268
+ import {
269
+ AdtOnPremConnector,
270
+ BasicAuthProvider,
271
+ RfcTransport,
272
+ rfcConversationFrom,
273
+ } from "@mcp-abap-adt/connection";
274
+
275
+ const connection = new AdtOnPremConnector(
276
+ config,
277
+ new BasicAuthProvider(config.username!, config.password!),
278
+ new RfcTransport(rfcConversationFrom(config), logger),
279
+ logger,
280
+ );
281
+
282
+ await connection.connect();
283
+ // Everything above the wire is the same: makeAdtRequest, setSessionType,
284
+ // disconnect. What differs is where the session lives — see below.
285
+ ```
286
+
287
+ **Where to look for it.** An HTTP session is an ICF session and appears in
288
+ **SM05**. An RFC conversation is a gateway client: it appears in **SMGW → Logged
289
+ on Clients** as `NWRFC`, and never in SM05, because there is no ICM in that
290
+ path. Looking for one in the other monitor and finding nothing is not a fault.
291
+
292
+ There is no cloud equivalent: ABAP Cloud has one wire, and `AdtCloudConnector`
293
+ takes no transport parameter at all.
294
+
202
295
  ### SSO Usage (SAML Session Cookies)
203
296
 
204
297
  ```typescript
205
- import { createAbapConnection, SapConfig } from "@mcp-abap-adt/connection";
298
+ import {
299
+ AdtOnPremConnector,
300
+ OnPremHttpTransport,
301
+ SamlAuthProvider,
302
+ SapConfig,
303
+ getTimeout,
304
+ } from "@mcp-abap-adt/connection";
206
305
 
207
306
  const config: SapConfig = {
208
307
  url: "https://your-sap-system.com",
@@ -210,47 +309,79 @@ const config: SapConfig = {
210
309
  sessionCookies: "MYSAPSSO2=...; SAP_SESSIONID=...",
211
310
  };
212
311
 
213
- const connection = createAbapConnection(config, logger);
312
+ // The cookies ARE the credential here — there is no Authorization header at all.
313
+ const connection = new AdtOnPremConnector(
314
+ config,
315
+ new SamlAuthProvider(config.sessionCookies!),
316
+ new OnPremHttpTransport(() => ({}), logger, {
317
+ client: config.client,
318
+ baseUrl: config.url,
319
+ }),
320
+ logger,
321
+ );
214
322
  await connection.connect();
215
323
 
216
324
  const response = await connection.makeAdtRequest({
217
325
  method: "GET",
218
326
  url: "/sap/bc/adt/programs/programs/your-program",
327
+ timeout: getTimeout("default"),
219
328
  });
220
329
  ```
221
330
 
222
331
  ### Cloud Usage with Automatic Token Refresh
223
332
 
224
- For automatic token refresh on **401** errors, inject `ITokenRefresher`:
333
+ Give `TokenAuthProvider` an `ITokenRefresher` and the provider replaces an
334
+ **expired** token on its own — it is asked per request and checks expiry before
335
+ answering, so nobody decides to renew. A token the source still believes in and
336
+ the server refuses is the other half, and that one **surfaces**:
225
337
 
226
338
  ```typescript
227
- import { JwtAbapConnection, SapConfig } from "@mcp-abap-adt/connection";
339
+ import {
340
+ AdtCloudConnector,
341
+ CloudHttpTransport,
342
+ SapConfig,
343
+ TokenAuthProvider,
344
+ getTimeout,
345
+ } from "@mcp-abap-adt/connection";
228
346
  import type { ITokenRefresher } from "@mcp-abap-adt/interfaces";
229
347
 
230
348
  // Token refresher provides token acquisition and refresh
231
349
  // (created by @mcp-abap-adt/auth-broker or custom implementation)
350
+ const currentAccessToken = 'the access token you already hold';
351
+ const exchangeRefreshToken = async () => 'a freshly exchanged access token';
232
352
  const tokenRefresher: ITokenRefresher = {
233
- getToken: async () => { /* return current token */ },
234
- refreshToken: async () => { /* refresh and return new token */ },
353
+ getToken: async () => currentAccessToken, // the one you hold
354
+ refreshToken: async () => exchangeRefreshToken(), // a new one, and cache it
235
355
  };
236
356
 
237
- // JWT configuration
238
357
  const config: SapConfig = {
239
358
  url: "https://your-instance.abap.cloud.sap",
240
359
  authType: "jwt",
241
- jwtToken: await tokenRefresher.getToken(), // Get initial token
242
360
  };
243
361
 
244
- // Create connection with token refresher - 401 handled automatically
245
- const connection = new JwtAbapConnection(config, logger, undefined, tokenRefresher);
362
+ // The connector says which SYSTEM this is; the provider says how to
363
+ // authenticate. Neither decides the other.
364
+ const connection = new AdtCloudConnector(
365
+ config,
366
+ new TokenAuthProvider(tokenRefresher),
367
+ new CloudHttpTransport(() => ({}), logger, {
368
+ client: config.client,
369
+ baseUrl: config.url,
370
+ }),
371
+ logger,
372
+ );
246
373
  await connection.connect();
247
374
 
248
- // Requests automatically retry with refreshed token on auth errors. A refresh
249
- // replaces the SAP session, so if a lock window is open the request fails with
250
- // ADT_SESSION_REPLACED instead of continuing on a session your lock is not in.
375
+ // On a 401 nothing here decides to get a new credential: the refusal reaches
376
+ // you. Whether it meant "stale" is a judgement made with what you know, and
377
+ // `renew()` is the seam you make it with. The session is untouched — a refused
378
+ // reaches you. A refresh replaces the SAP session, so if a lock window is open
379
+ // the request fails with ADT_SESSION_REPLACED rather than continuing on a
380
+ // session your lock is not in.
251
381
  const response = await connection.makeAdtRequest({
252
382
  method: "GET",
253
383
  url: "/sap/bc/adt/programs/programs/your-program",
384
+ timeout: getTimeout("default"),
254
385
  });
255
386
  ```
256
387
 
@@ -271,9 +402,22 @@ See [MIGRATION-4.0.md](./docs/MIGRATION-4.0.md).
271
402
  For operations that require session state (e.g., object modifications), you can enable stateful sessions:
272
403
 
273
404
  ```typescript
274
- import { createAbapConnection } from "@mcp-abap-adt/connection";
275
-
276
- const connection = createAbapConnection(config, logger);
405
+ import {
406
+ AdtOnPremConnector,
407
+ BasicAuthProvider,
408
+ OnPremHttpTransport,
409
+ getTimeout,
410
+ } from "@mcp-abap-adt/connection";
411
+
412
+ const connection = new AdtOnPremConnector(
413
+ config,
414
+ new BasicAuthProvider(config.username!, config.password!),
415
+ new OnPremHttpTransport(() => ({}), logger, {
416
+ client: config.client,
417
+ baseUrl: config.url,
418
+ }),
419
+ logger,
420
+ );
277
421
  await connection.connect();
278
422
 
279
423
  // Enable stateful session mode (adds x-sap-adt-sessiontype: stateful header)
@@ -284,6 +428,7 @@ await connection.makeAdtRequest({
284
428
  method: "POST",
285
429
  url: "/sap/bc/adt/objects/domains",
286
430
  data: { /* domain data */ },
431
+ timeout: getTimeout("default"),
287
432
  });
288
433
 
289
434
  // Note: Session state persistence is handled by @mcp-abap-adt/auth-broker package
@@ -292,7 +437,7 @@ await connection.makeAdtRequest({
292
437
  ### Custom Logger
293
438
 
294
439
  ```typescript
295
- import { ILogger } from "@mcp-abap-adt/connection";
440
+ import { AdtOnPremConnector, BasicAuthProvider, ILogger, OnPremHttpTransport } from "@mcp-abap-adt/connection";
296
441
 
297
442
  class MyLogger implements ILogger {
298
443
  info(message: string, meta?: any): void {
@@ -321,7 +466,15 @@ class MyLogger implements ILogger {
321
466
  }
322
467
 
323
468
  const logger = new MyLogger();
324
- const connection = createAbapConnection(config, logger);
469
+ const connection = new AdtOnPremConnector(
470
+ config,
471
+ new BasicAuthProvider(config.username!, config.password!),
472
+ new OnPremHttpTransport(() => ({}), logger, {
473
+ client: config.client,
474
+ baseUrl: config.url,
475
+ }),
476
+ logger,
477
+ );
325
478
  ```
326
479
 
327
480
  ## CLI Tool
@@ -411,6 +564,8 @@ type SapConfig = {
411
564
  Main interface for ABAP connections.
412
565
 
413
566
  ```typescript
567
+ import { AbapRequestOptions } from '@mcp-abap-adt/connection';
568
+ import type { AxiosResponse } from 'axios';
414
569
  // The shared contract (IAbapConnection), what every connection provides:
415
570
  interface AbapConnection {
416
571
  connect(): Promise<void>; // REQUIRED before any request; rejects on failure
@@ -421,10 +576,12 @@ interface AbapConnection {
421
576
  }
422
577
  ```
423
578
 
424
- The HTTP connection classes carry the rest of the session lifecycle. It is on the
425
- shared contract as a **capability atom** in `@mcp-abap-adt/interfaces` rather than
426
- as methods on `IAbapConnection`, which is why `RfcAbapConnection` — a transport
427
- that owns no HTTP session — is unaffected by its existence:
579
+ The connectors carry the rest of the session lifecycle. It is on the shared
580
+ contract as a **capability atom** in `@mcp-abap-adt/interfaces` rather than as
581
+ methods on `IAbapConnection`, so a consumer that only carries requests is
582
+ unaffected by its existence. Note that a connection over RFC has the whole of it
583
+ — what an RFC conversation has none of is a session RESOURCE to open and close
584
+ by address, which is an empty mechanism, not an absent lifecycle:
428
585
 
429
586
  ```typescript
430
587
  // ISessionLifecycleAware
@@ -466,16 +623,35 @@ interface ILogger {
466
623
 
467
624
  ### Functions
468
625
 
469
- #### `createAbapConnection(config, logger?, sessionId?)`
626
+ #### `rfcConversationFrom(config)`
627
+
628
+ What `RfcTransport` is constructed with. Derives `ashost` from the url and
629
+ `sysnr` from the HTTP port by the SAP convention that `80XX` is the ICM port for
630
+ system `XX`, which `SAP_SYSNR` overrides for a port that follows no convention.
470
631
 
471
- Factory function to create an ABAP connection instance.
632
+ The SAP NW RFC SDK is loaded when a conversation opens, not when this is called,
633
+ so a machine without it fails at `connect()` with a message saying what to
634
+ install rather than at construction.
635
+
636
+ ```text
637
+ function rfcConversationFrom(config: SapConfig): () => IRfcConversation;
638
+ function rfcParamsFrom(config: SapConfig): RfcConnectionParams;
639
+ ```
472
640
 
473
641
  ```typescript
474
- function createAbapConnection(
475
- config: SapConfig,
476
- logger?: ILogger | null,
477
- sessionId?: string
478
- ): AbapConnection;
642
+ import {
643
+ AdtOnPremConnector,
644
+ BasicAuthProvider,
645
+ RfcTransport,
646
+ rfcConversationFrom,
647
+ } from "@mcp-abap-adt/connection";
648
+
649
+ const connection = new AdtOnPremConnector(
650
+ config,
651
+ new BasicAuthProvider(config.username!, config.password!),
652
+ new RfcTransport(rfcConversationFrom(config), logger),
653
+ logger,
654
+ );
479
655
  ```
480
656
 
481
657
  #### `CSRF_CONFIG` and `CSRF_ERROR_MESSAGES`
@@ -502,6 +678,12 @@ import { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from '@mcp-abap-adt/connection';
502
678
  ```typescript
503
679
  import { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from '@mcp-abap-adt/connection';
504
680
 
681
+ // Whatever HTTP client your own connection class is built on.
682
+ const yourHttpClient = {
683
+ get: async (url: string, config: { headers: Record<string, string> }) =>
684
+ ({ headers: {} as Record<string, string> }),
685
+ };
686
+
505
687
  async function fetchCsrfToken(baseUrl: string): Promise<string> {
506
688
  const csrfUrl = `${baseUrl}${CSRF_CONFIG.ENDPOINT}`;
507
689
 
@@ -533,6 +715,10 @@ async function fetchCsrfToken(baseUrl: string): Promise<string> {
533
715
  await new Promise(resolve => setTimeout(resolve, CSRF_CONFIG.RETRY_DELAY));
534
716
  }
535
717
  }
718
+
719
+ // Unreachable: the last attempt either returns or throws above. Stated so the
720
+ // function has a return type the compiler can agree with.
721
+ throw new Error(CSRF_ERROR_MESSAGES.NOT_IN_HEADERS);
536
722
  }
537
723
  ```
538
724
 
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The credentials a connector can be handed.
3
+ *
4
+ * Each is the authentication half of one of the connection classes that used to
5
+ * carry both halves, lifted out unchanged — the header a `BasicAuthProvider`
6
+ * builds is byte for byte what `BaseAbapConnection` built, and the TLS options a
7
+ * `CertificateAuthProvider` returns are what `CertificateAbapConnection`
8
+ * returned. Nothing about how a credential works changed; only who owns it.
9
+ */
10
+ import type { IAuthProvider, ICertificateMaterial, ICertificateMaterialLoader, IRenewableCredential, ISapConfig, ITokenRefresher } from '@mcp-abap-adt/interfaces';
11
+ /** Username and password, as `Basic base64(user:pass)`. */
12
+ export declare class BasicAuthProvider implements IAuthProvider {
13
+ private readonly username;
14
+ private readonly password;
15
+ readonly kind = "basic";
16
+ /** Nothing to get ready: this credential is complete as constructed. */
17
+ prepare(): Promise<void>;
18
+ /** Not cookies. This one authenticates with a header. */
19
+ cookies(): string | null;
20
+ /** No TLS material: this credential lives in a header, not in the transport. */
21
+ transportMaterial(): ICertificateMaterial;
22
+ constructor(username: string, password: string);
23
+ authorizationHeader(): Promise<string | null>;
24
+ }
25
+ /**
26
+ * A bearer token, kept current by whoever issued it.
27
+ *
28
+ * Takes an `ITokenRefresher` — the contract `@mcp-abap-adt/auth-broker` already
29
+ * produces and `@mcp-abap-adt/auth-providers` already implements twelve ways.
30
+ * This package depends on neither: it speaks the contract, and the consumer
31
+ * brings the implementation, exactly as it does for `IAbapConnection`.
32
+ *
33
+ * **This carries no session recovery.** What happens to a session when the
34
+ * credential behind it is renewed mid-flight is the connection's business and
35
+ * stays there; see `JwtAbapConnection`, which still owns that machinery.
36
+ */
37
+ export declare class TokenAuthProvider implements IRenewableCredential {
38
+ private readonly source;
39
+ readonly kind = "token";
40
+ /** Nothing to get ready: this credential is complete as constructed. */
41
+ prepare(): Promise<void>;
42
+ /** Not cookies. This one authenticates with a header. */
43
+ cookies(): string | null;
44
+ /** No TLS material: this credential lives in a header, not in the transport. */
45
+ transportMaterial(): ICertificateMaterial;
46
+ /**
47
+ * A token, or something that can produce one.
48
+ *
49
+ * A bare string is a token with no renewal behind it — honest, and fine for a
50
+ * short task. An `ITokenRefresher` or a function is a provider that checks
51
+ * expiry and renews on its own, which is where this belongs in a long-lived
52
+ * process.
53
+ */
54
+ constructor(source: string | ITokenRefresher | (() => Promise<string>));
55
+ /**
56
+ * Asked every time, and nothing kept.
57
+ *
58
+ * The provider behind this already caches the token, knows when it expires,
59
+ * and renews before handing one back. A second cache here would serve the
60
+ * stale one and hide exactly the renewal the provider exists to do — which
61
+ * is what the first version of this class did.
62
+ */
63
+ authorizationHeader(): Promise<string | null>;
64
+ /**
65
+ * The token was refused, so force a new one.
66
+ *
67
+ * Declared through `IRenewableCredential` rather than as an optional member
68
+ * of every credential, so a consumer narrows to it: a password has nothing
69
+ * behind it to ask again, and a SAML session was negotiated elsewhere.
70
+ *
71
+ * **Nothing in this package calls it.** A token that EXPIRED is replaced
72
+ * inside `authorizationHeader()` above, which is asked per request — nobody
73
+ * decides anything. This is the other case, a token the source still believes
74
+ * in and the server refuses, and whether a refusal meant that is a judgement
75
+ * made with what the caller knows. So the refusal surfaces, and this is the
76
+ * seam the caller decides with.
77
+ *
78
+ * `getToken()` is documented to return the cached token while it believes it
79
+ * is still valid — which is exactly the situation after a 401 on a token the
80
+ * provider has not yet noticed is dead. `refreshToken()` is the contract's
81
+ * answer for that, and this is the only place it is called.
82
+ */
83
+ renew(): Promise<void>;
84
+ }
85
+ /**
86
+ * A SAML session, already negotiated, presented as cookies.
87
+ *
88
+ * No `Authorization` header at all — the cookies are the credential, and they
89
+ * are added by the connection alongside its own.
90
+ */
91
+ export declare class SamlAuthProvider implements IAuthProvider {
92
+ private readonly sessionCookies;
93
+ readonly kind = "saml";
94
+ /** Nothing to get ready: this credential is complete as constructed. */
95
+ prepare(): Promise<void>;
96
+ /** No TLS material: this credential lives in a header, not in the transport. */
97
+ transportMaterial(): ICertificateMaterial;
98
+ constructor(sessionCookies: string);
99
+ /** Not a header: this credential authenticates with the cookies below. */
100
+ authorizationHeader(): Promise<string | null>;
101
+ /** The cookies to present. */
102
+ cookies(): string;
103
+ }
104
+ /**
105
+ * Client certificate: the credential lives in the TLS handshake.
106
+ *
107
+ * `prepare()` is where the material is read, which is why it exists at all —
108
+ * building an agent before it is loaded is what used to reject a connect
109
+ * before a single request went out.
110
+ */
111
+ export declare class CertificateAuthProvider implements IAuthProvider {
112
+ private readonly loader;
113
+ private readonly config;
114
+ readonly kind = "certificate";
115
+ /** Not cookies. This one authenticates with a header. */
116
+ cookies(): string | null;
117
+ private material;
118
+ constructor(loader: ICertificateMaterialLoader, config: ISapConfig);
119
+ prepare(): Promise<void>;
120
+ /** Not a header: this credential authenticates through TLS. */
121
+ authorizationHeader(): Promise<string | null>;
122
+ transportMaterial(): ICertificateMaterial;
123
+ }
124
+ //# sourceMappingURL=providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/auth/providers.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,oBAAoB,EACpB,0BAA0B,EAC1B,oBAAoB,EACpB,UAAU,EACV,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAElC,2DAA2D;AAC3D,qBAAa,iBAAkB,YAAW,aAAa;IAiBnD,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAjB3B,QAAQ,CAAC,IAAI,WAAW;IAExB,wEAAwE;IAClE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAE9B,yDAAyD;IACzD,OAAO,IAAI,MAAM,GAAG,IAAI;IAIxB,gFAAgF;IAChF,iBAAiB,IAAI,oBAAoB;gBAKtB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM;IAG7B,mBAAmB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAGpD;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAkB,YAAW,oBAAoB;IAyB1D,OAAO,CAAC,QAAQ,CAAC,MAAM;IAxBzB,QAAQ,CAAC,IAAI,WAAW;IAExB,wEAAwE;IAClE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAE9B,yDAAyD;IACzD,OAAO,IAAI,MAAM,GAAG,IAAI;IAIxB,gFAAgF;IAChF,iBAAiB,IAAI,oBAAoB;IAIzC;;;;;;;OAOG;gBAEgB,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,CAAC,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAG7E;;;;;;;OAOG;IACG,mBAAmB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAUnD;;;;;;;;;;;;;;;;;;OAkBG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ7B;AAED;;;;;GAKG;AACH,qBAAa,gBAAiB,YAAW,aAAa;IAWxC,OAAO,CAAC,QAAQ,CAAC,cAAc;IAV3C,QAAQ,CAAC,IAAI,UAAU;IAEvB,wEAAwE;IAClE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAE9B,gFAAgF;IAChF,iBAAiB,IAAI,oBAAoB;gBAIZ,cAAc,EAAE,MAAM;IAEnD,0EAA0E;IACpE,mBAAmB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAInD,8BAA8B;IAC9B,OAAO,IAAI,MAAM;CAGlB;AAED;;;;;;GAMG;AACH,qBAAa,uBAAwB,YAAW,aAAa;IAWzD,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAXzB,QAAQ,CAAC,IAAI,iBAAiB;IAE9B,yDAAyD;IACzD,OAAO,IAAI,MAAM,GAAG,IAAI;IAIxB,OAAO,CAAC,QAAQ,CAAqC;gBAGlC,MAAM,EAAE,0BAA0B,EAClC,MAAM,EAAE,UAAU;IAG/B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B,+DAA+D;IACzD,mBAAmB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAInD,iBAAiB,IAAI,oBAAoB;CAS1C"}