@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
@@ -26,17 +26,47 @@ The connection layer **does not** decide when to lock/unlock objects—that logi
26
26
 
27
27
  ## Enabling Stateful Sessions
28
28
 
29
+ Every example below builds on this much, so it is stated once:
30
+
29
31
  ```ts
30
- import { createAbapConnection } from '@mcp-abap-adt/connection';
32
+ import type { SapConfig } from '@mcp-abap-adt/connection';
33
+
34
+ const config: SapConfig = {
35
+ url: 'https://your-sap-server.com',
36
+ authType: 'basic',
37
+ username: 'your-username',
38
+ password: 'your-password',
39
+ client: '100',
40
+ };
41
+ const user = config.username!;
42
+ const pass = config.password!;
43
+ const logger = console;
44
+ ```
31
45
 
32
- const connection = createAbapConnection(config, logger);
46
+ ```ts
47
+ import {
48
+ AdtOnPremConnector,
49
+ BasicAuthProvider,
50
+ OnPremHttpTransport,
51
+ getTimeout,
52
+ } from '@mcp-abap-adt/connection';
53
+
54
+ const connection = new AdtOnPremConnector(
55
+ config,
56
+ new BasicAuthProvider(config.username!, config.password!),
57
+ new OnPremHttpTransport(() => ({}), logger, {
58
+ client: config.client,
59
+ baseUrl: config.url,
60
+ }),
61
+ logger,
62
+ );
33
63
  await connection.connect(); // required before any request
34
64
 
35
65
  // Enable stateful session mode (adds x-sap-adt-sessiontype: stateful header)
36
66
  connection.setSessionType('stateful');
37
67
 
38
68
  // Now all requests share the same session (cookies, CSRF token)
39
- await connection.makeAdtRequest({ method: 'GET', url: '/sap/bc/adt/discovery' });
69
+ await connection.makeAdtRequest({ method: 'GET', url: '/sap/bc/adt/discovery' , timeout: getTimeout('default') });
40
70
 
41
71
  // Switch back to stateless
42
72
  connection.setSessionType('stateless');
@@ -47,11 +77,11 @@ connection.setSessionType('stateless');
47
77
  ## Knowing Which Session You Are In
48
78
 
49
79
  ```ts
50
- import { BaseAbapConnection } from '@mcp-abap-adt/connection';
80
+ import { AdtOnPremConnector, BasicAuthProvider, OnPremHttpTransport } from '@mcp-abap-adt/connection';
51
81
 
52
82
  // getSessionIdentity() is on the HTTP connection classes, NOT on the
53
- // IAbapConnection type that createAbapConnection() returns.
54
- const connection = new BaseAbapConnection(config, logger);
83
+ // bare IAbapConnection type a caller may hand you.
84
+ const connection = new AdtOnPremConnector(config, new BasicAuthProvider(user, pass), new OnPremHttpTransport(() => ({}), logger, { client: config.client, baseUrl: config.url }), logger);
55
85
  await connection.connect();
56
86
 
57
87
  // Which SAP session this connection is talking to. Changes only when the
@@ -82,11 +112,13 @@ Every ADT request issued through `makeAdtRequest` automatically:
82
112
 
83
113
  This logic is transparent to callers (Builders, handlers, CLI scripts).
84
114
 
85
- On a JWT connection one case is **not** transparent, and cannot be: a 401 that leads to a token
86
- refresh also replaces the SAP session, because the renewed credential cannot keep the old one.
87
- Inside a lock window that surfaces as `ADT_SESSION_REPLACED` rather than a request quietly
88
- continuing on a session your lock is not in. A 403 never does this — it is an authorization
89
- answer, not a credential one, and nothing is torn down for it.
115
+ On a JWT connection a 401 is **not** handled here at all, and that is the point: since 6.0.0 the
116
+ refusal surfaces and the session is left alone. Nothing replaces the credential behind you, so
117
+ nothing replaces the SAP session behind you either — a lock window is not torn down by an
118
+ authentication answer. A 403 never did this: it is an authorization answer, not a credential one.
119
+
120
+ If you decide the refusal meant a stale token, `renew()` and reconnect are yours to call — and a
121
+ reconnect is a NEW session, so do it outside a lock window rather than inside one.
90
122
 
91
123
  ---
92
124
 
@@ -102,22 +134,125 @@ answer, not a credential one, and nothing is torn down for it.
102
134
 
103
135
  ---
104
136
 
137
+ ## What This Layer Actually Solves
138
+
139
+ The link to the ABAP session is not guaranteed, and that is the whole problem:
140
+
141
+ - the ABAP session can be terminated by the system while this client still
142
+ believes it has one — the next lock-bound request answers
143
+ `400 "Session not found"`;
144
+ - or it is never created at all, because the system will not open another one
145
+ for this user right now.
146
+
147
+ Both are reported, neither is worked around. `connect()` fails with the reason
148
+ when no session was opened; a session lost afterwards surfaces as
149
+ `ADT_SESSION_REPLACED` rather than a request quietly continuing somewhere it
150
+ does not belong. What to do about either — wait, retry, reconnect, release
151
+ sessions the user still holds, carry on read-only — is the caller's decision,
152
+ made with the cookies and the answers in hand.
153
+
154
+ ## A Lock Lives In The ABAP Session
155
+
156
+ ### Three lifetimes, and a dropped connection ends none of them
157
+
158
+ Before anything else, because conflating these is the mistake that produces
159
+ wrong fixes:
160
+
161
+ | | what it is | what ends it |
162
+ |---|---|---|
163
+ | **the connection** | one TCP socket | you, the network, a timeout firing. **Ends nothing on the server** |
164
+ | **the HTTP session** | the ICF conversation, held by the cookie jar on this side | dropping the cookies, or a logoff. Nothing about it is in doubt |
165
+ | **the ABAP session** | the server-side context named by `SAP_SESSIONID_<SID>_<CLIENT>` — the roll area a stateful request runs in | **only the server.** Its own idle timeout, which this side can neither read nor influence, or an explicit logoff |
166
+ | **the lock** | an enqueue entry, **owned by the ABAP session** | that ABAP session ending. **When it goes, its locks go with it** |
167
+
168
+ The two middle rows are the ones worth keeping apart, because everything that
169
+ goes wrong here goes wrong between them. The lock does not belong to the socket
170
+ and does not belong to the cookies — it belongs to the **ABAP session**, and
171
+ that session is the server's. This side never ends one: it can ask (a logoff),
172
+ and otherwise waits for a timeout it cannot see.
173
+
174
+ A closed socket is just a client that stopped listening. The ABAP session
175
+ neither knows nor cares — which is why `SM04` shows sessions left behind by
176
+ connections that never said they were finished, and why this package sends an
177
+ explicit goodbye at all.
178
+
179
+ And **a lock is never stranded by something dying** — the opposite. If the ABAP
180
+ session ended, the enqueue entry went with it and there is nothing left to
181
+ strand. A lock is stranded when that session **survives** while the caller no
182
+ longer holds the handle needed to unlock it: both sit there, held, until the
183
+ server's own timeout releases them together.
184
+
185
+ This is what makes an aborted request expensive, and it is not what it looks
186
+ like. Aborting costs you **knowledge**, not a session: whether the modification
187
+ was applied becomes unknowable, and the handle `unlock` needs is gone, while the
188
+ lock is still very much held. That — not any teardown — is why
189
+ `beginCriticalSection()` raises the effective timeout to a large ceiling for the
190
+ duration of a `lock → modify → unlock` chain.
191
+
192
+ ### Two sessions, and they are not the same thing
193
+
194
+ There are two sessions here, and they are not the same thing:
195
+
196
+ - the **HTTP session** — the conversation this client is having. It exists as
197
+ long as the cookies do, and nothing about it is in doubt;
198
+ - the **ABAP session** — named by `SAP_SESSIONID_<SID>_<CLIENT>`, and the thing
199
+ locks are bound to.
200
+
201
+ The doubt is only ever about the second. It may not have been created, or it may
202
+ have been terminated while this client still holds the cookies and believes it
203
+ has one — and a lock is dead in either case, whether or not the client noticed.
204
+
205
+ Two ways to lose one, and they are different problems:
206
+
207
+ - **The server never opened one.** No `SAP_SESSIONID` came back, so there is no
208
+ ABAP session known to this connection and nothing a lock could be bound to —
209
+ while the HTTP side is perfectly fine, which is why it does not look like a
210
+ failure at all. `connect()` refuses rather than handing back a connection
211
+ whose first lock would be dead on arrival. Sessions are limited per user and shared with every other tool
212
+ logged on as them, so this says nothing about your code — it says the system
213
+ would not open another one right now.
214
+ - **It timed out while you were quiet.** The timeout is an idle one, and it is
215
+ the *silence* that spends it, not the elapsed time. Measured on an on-prem
216
+ system with a 30-minute window: a small request once a minute kept one session
217
+ alive for 45 minutes with its identity unchanged, straight past the mark.
218
+
219
+ So a long chain under a lock is safe while it is doing something, and at risk
220
+ while it waits. **Any request in the session resets the window** — a poll, a
221
+ read, a status check. There is deliberately no keepalive timer in this package:
222
+ holding a session alive means holding a scarce, shared slot, and deciding to do
223
+ that belongs to the caller who knows why the session is worth keeping.
224
+
225
+ **The cookies are the session.** Nothing else ties a caller to one, so a second
226
+ connection given the same cookie jar works in the same ABAP session and can use
227
+ the locks taken in it — that is what makes handing them over a way to continue
228
+ someone else's work. It cuts both ways: `disconnect()` ends the session for
229
+ everyone holding those cookies, not just for the object it was called on, and no
230
+ connection can see the copies. Deciding who may hold them, and who is allowed to
231
+ close, belongs to whoever passes them around.
232
+
233
+ The server never tells the client how long it has: the session cookie carries no
234
+ expiry and no response header mentions one. The only honest signals are the ones
235
+ you get by asking — `getSessionIdentity()` for which session you are in, and
236
+ `ADT_SESSION_REPLACED` when the one you were in is gone.
237
+
105
238
  ## Troubleshooting
106
239
 
107
- - **CSRF token errors**: discard the session and establish a new one. `reset()`
108
- does that, but it lives on the HTTP connection classes and is **not** on the
109
- `IAbapConnection` type `createAbapConnection()` returns — reach it through a
110
- concrete type:
240
+ - **CSRF token errors**: discard the session and establish a new one. That is
241
+ `disconnect()` followed by `connect()` — there is no local-only discard, because
242
+ dropping the cookie leaves the ABAP session open on the server. Both live on
243
+ the HTTP connection classes and are **not** on the `IAbapConnection` type
244
+ a bare `IAbapConnection` carries — reach them through a connector type, or
245
+ through the `ISessionLifecycleAware` atom:
111
246
 
112
247
  ```ts
113
- import { BaseAbapConnection } from '@mcp-abap-adt/connection';
248
+ import { AdtOnPremConnector, BasicAuthProvider, OnPremHttpTransport } from '@mcp-abap-adt/connection';
114
249
 
115
- const connection = new BaseAbapConnection(config, logger);
116
- connection.reset(); // queues the cleanup; refuses requests meanwhile
117
- await connection.connect(); // a new session, explicitly
250
+ const connection = new AdtOnPremConnector(config, new BasicAuthProvider(user, pass), new OnPremHttpTransport(() => ({}), logger, { client: config.client, baseUrl: config.url }), logger);
251
+ await connection.disconnect(); // ends the session on the server, then clears
252
+ await connection.connect(); // a new session, explicitly
118
253
  ```
119
254
  - **Session expired**: reauthenticate to obtain a new session.
120
- - **Multiple connections**: each `createAbapConnection` instance maintains its own cookie jar; share the instance if you need continuity.
255
+ - **Multiple connections**: each connector holds its own wire, and the wire holds the cookie jar; share the instance if you need continuity.
121
256
 
122
257
  ---
123
258