@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
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ /**
3
+ * The credentials a connector can be handed.
4
+ *
5
+ * Each is the authentication half of one of the connection classes that used to
6
+ * carry both halves, lifted out unchanged — the header a `BasicAuthProvider`
7
+ * builds is byte for byte what `BaseAbapConnection` built, and the TLS options a
8
+ * `CertificateAuthProvider` returns are what `CertificateAbapConnection`
9
+ * returned. Nothing about how a credential works changed; only who owns it.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.CertificateAuthProvider = exports.SamlAuthProvider = exports.TokenAuthProvider = exports.BasicAuthProvider = void 0;
13
+ /** Username and password, as `Basic base64(user:pass)`. */
14
+ class BasicAuthProvider {
15
+ username;
16
+ password;
17
+ kind = 'basic';
18
+ /** Nothing to get ready: this credential is complete as constructed. */
19
+ async prepare() { }
20
+ /** Not cookies. This one authenticates with a header. */
21
+ cookies() {
22
+ return null;
23
+ }
24
+ /** No TLS material: this credential lives in a header, not in the transport. */
25
+ transportMaterial() {
26
+ return {};
27
+ }
28
+ constructor(username, password) {
29
+ this.username = username;
30
+ this.password = password;
31
+ }
32
+ async authorizationHeader() {
33
+ return `Basic ${Buffer.from(`${this.username ?? ''}:${this.password ?? ''}`).toString('base64')}`;
34
+ }
35
+ }
36
+ exports.BasicAuthProvider = BasicAuthProvider;
37
+ /**
38
+ * A bearer token, kept current by whoever issued it.
39
+ *
40
+ * Takes an `ITokenRefresher` — the contract `@mcp-abap-adt/auth-broker` already
41
+ * produces and `@mcp-abap-adt/auth-providers` already implements twelve ways.
42
+ * This package depends on neither: it speaks the contract, and the consumer
43
+ * brings the implementation, exactly as it does for `IAbapConnection`.
44
+ *
45
+ * **This carries no session recovery.** What happens to a session when the
46
+ * credential behind it is renewed mid-flight is the connection's business and
47
+ * stays there; see `JwtAbapConnection`, which still owns that machinery.
48
+ */
49
+ class TokenAuthProvider {
50
+ source;
51
+ kind = 'token';
52
+ /** Nothing to get ready: this credential is complete as constructed. */
53
+ async prepare() { }
54
+ /** Not cookies. This one authenticates with a header. */
55
+ cookies() {
56
+ return null;
57
+ }
58
+ /** No TLS material: this credential lives in a header, not in the transport. */
59
+ transportMaterial() {
60
+ return {};
61
+ }
62
+ /**
63
+ * A token, or something that can produce one.
64
+ *
65
+ * A bare string is a token with no renewal behind it — honest, and fine for a
66
+ * short task. An `ITokenRefresher` or a function is a provider that checks
67
+ * expiry and renews on its own, which is where this belongs in a long-lived
68
+ * process.
69
+ */
70
+ constructor(source) {
71
+ this.source = source;
72
+ }
73
+ /**
74
+ * Asked every time, and nothing kept.
75
+ *
76
+ * The provider behind this already caches the token, knows when it expires,
77
+ * and renews before handing one back. A second cache here would serve the
78
+ * stale one and hide exactly the renewal the provider exists to do — which
79
+ * is what the first version of this class did.
80
+ */
81
+ async authorizationHeader() {
82
+ const token = typeof this.source === 'string'
83
+ ? this.source
84
+ : typeof this.source === 'function'
85
+ ? await this.source()
86
+ : await this.source.getToken();
87
+ return token ? `Bearer ${token}` : '';
88
+ }
89
+ /**
90
+ * The token was refused, so force a new one.
91
+ *
92
+ * Declared through `IRenewableCredential` rather than as an optional member
93
+ * of every credential, so a consumer narrows to it: a password has nothing
94
+ * behind it to ask again, and a SAML session was negotiated elsewhere.
95
+ *
96
+ * **Nothing in this package calls it.** A token that EXPIRED is replaced
97
+ * inside `authorizationHeader()` above, which is asked per request — nobody
98
+ * decides anything. This is the other case, a token the source still believes
99
+ * in and the server refuses, and whether a refusal meant that is a judgement
100
+ * made with what the caller knows. So the refusal surfaces, and this is the
101
+ * seam the caller decides with.
102
+ *
103
+ * `getToken()` is documented to return the cached token while it believes it
104
+ * is still valid — which is exactly the situation after a 401 on a token the
105
+ * provider has not yet noticed is dead. `refreshToken()` is the contract's
106
+ * answer for that, and this is the only place it is called.
107
+ */
108
+ async renew() {
109
+ if (typeof this.source === 'string' || typeof this.source === 'function') {
110
+ // Nothing to force: a fixed token has no source, and a function is asked
111
+ // afresh every time anyway.
112
+ return;
113
+ }
114
+ await this.source.refreshToken();
115
+ }
116
+ }
117
+ exports.TokenAuthProvider = TokenAuthProvider;
118
+ /**
119
+ * A SAML session, already negotiated, presented as cookies.
120
+ *
121
+ * No `Authorization` header at all — the cookies are the credential, and they
122
+ * are added by the connection alongside its own.
123
+ */
124
+ class SamlAuthProvider {
125
+ sessionCookies;
126
+ kind = 'saml';
127
+ /** Nothing to get ready: this credential is complete as constructed. */
128
+ async prepare() { }
129
+ /** No TLS material: this credential lives in a header, not in the transport. */
130
+ transportMaterial() {
131
+ return {};
132
+ }
133
+ constructor(sessionCookies) {
134
+ this.sessionCookies = sessionCookies;
135
+ }
136
+ /** Not a header: this credential authenticates with the cookies below. */
137
+ async authorizationHeader() {
138
+ return null;
139
+ }
140
+ /** The cookies to present. */
141
+ cookies() {
142
+ return this.sessionCookies;
143
+ }
144
+ }
145
+ exports.SamlAuthProvider = SamlAuthProvider;
146
+ /**
147
+ * Client certificate: the credential lives in the TLS handshake.
148
+ *
149
+ * `prepare()` is where the material is read, which is why it exists at all —
150
+ * building an agent before it is loaded is what used to reject a connect
151
+ * before a single request went out.
152
+ */
153
+ class CertificateAuthProvider {
154
+ loader;
155
+ config;
156
+ kind = 'certificate';
157
+ /** Not cookies. This one authenticates with a header. */
158
+ cookies() {
159
+ return null;
160
+ }
161
+ material = null;
162
+ constructor(loader, config) {
163
+ this.loader = loader;
164
+ this.config = config;
165
+ }
166
+ async prepare() {
167
+ if (!this.material) {
168
+ this.material = await this.loader.load(this.config);
169
+ }
170
+ }
171
+ /** Not a header: this credential authenticates through TLS. */
172
+ async authorizationHeader() {
173
+ return null;
174
+ }
175
+ transportMaterial() {
176
+ if (!this.material) {
177
+ throw new Error('CertificateAuthProvider: certificate material not loaded. connect() prepares it; a request before that has nothing to present.');
178
+ }
179
+ const { cert, key, pfx, passphrase } = this.material;
180
+ return { cert, key, pfx, passphrase };
181
+ }
182
+ }
183
+ exports.CertificateAuthProvider = CertificateAuthProvider;
@@ -3,14 +3,41 @@ import type { SapConfig } from '../config/sapConfig.js';
3
3
  import type { ILogger } from '../logger.js';
4
4
  import { SessionLifecycle } from '../session/SessionLifecycle.js';
5
5
  import type { AbapConnection, AbapRequestOptions } from './AbapConnection.js';
6
+ import { type IAdtSessionContext, type IAdtTransport } from './IAdtTransport.js';
6
7
  /**
7
8
  * Declares the capabilities explicitly rather than satisfying them by accident.
8
9
  * `AbapConnection` is the base contract every transport honours; these two are
9
10
  * the HTTP session's own, and naming them means a signature that drifts from
10
11
  * the published contract fails to compile here instead of at the consumer.
12
+ *
13
+ * **This gives the consumer instruments; it does not decide for it.** `connect()`
14
+ * opens one session and `disconnect()` closes it. How many connections to hold,
15
+ * how long to hold them and when to let go stays with the caller — there are no
16
+ * thresholds here, no pooling and no eviction, because none of that is knowable
17
+ * from inside a single connection. A session this one did not open is not its
18
+ * business: the session limit is per user and the pool is shared, so a SAP GUI
19
+ * logon of the same user sits in the same list.
20
+ *
21
+ * **Nothing the server decides is treated as something to count on.** Whether it
22
+ * issues a session cookie, whether it still holds a session it issued, how many
23
+ * it will tolerate, how fast it answers a logoff — all of that is its own and
24
+ * may differ by system and release. So each is observed and reported, never
25
+ * relied upon: the logoff is best effort under a bound the caller sets, a
26
+ * missing session cookie is a warning rather than a rule, and no code here
27
+ * counts sessions or predicts the next answer from the last one.
11
28
  */
12
29
  declare abstract class AbstractAbapConnection implements AbapConnection, ISessionLifecycleAware {
13
30
  private readonly config;
31
+ /**
32
+ * Required, and constructed by the caller.
33
+ *
34
+ * There is no default to fall back to and nothing is worked out from the
35
+ * config: the wire is a fact about the deployment, and a library that
36
+ * picked one would be guessing at the thing this design exists to stop
37
+ * guessing at. It also carries what the caller alone can wire — the
38
+ * credential's TLS material, the client — which is why it arrives built.
39
+ */
40
+ readonly transport: IAdtTransport;
14
41
  protected readonly logger: ILogger | null;
15
42
  /**
16
43
  * Owns session state, admission and teardown ordering. Composed rather than
@@ -18,40 +45,86 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
18
45
  * transports share this unit instead of a base class.
19
46
  */
20
47
  protected readonly lifecycle: SessionLifecycle;
21
- private axiosInstance;
22
- private csrfToken;
23
- private cookies;
24
- private cookieStore;
25
48
  private baseUrl;
26
49
  private sessionId;
27
50
  private sessionMode;
28
- private skipSessionType;
29
51
  /**
30
52
  * When true, requests are treated as part of an uninterruptible critical
31
53
  * section (e.g. a lock → modify → unlock chain). In this state a short
32
- * per-request timeout must NOT abort the request mid-flight, because
33
- * aborting the socket drops the stateful ADT session and orphans the lock
34
- * handle (leaving the object locked and inactive). While set, makeAdtRequest
54
+ * per-request timeout must NOT abort the request mid-flight: the abort ends
55
+ * nothing server-side — only the server ends an ABAP session, on an idle
56
+ * timeout this side cannot see — it ends what this side KNOWS. Whether the
57
+ * change was applied becomes unknowable, and the handle `unlock` needs is
58
+ * lost while the lock lives on in that session. While set, makeAdtRequest
35
59
  * raises the effective timeout to CRITICAL_SECTION_TIMEOUT (a large ceiling)
36
60
  * so the request runs to completion instead of being interrupted.
37
61
  */
38
62
  private inCriticalSection;
39
63
  /** Reference count for nested beginCriticalSection()/endCriticalSection() pairs. */
40
64
  private criticalSectionDepth;
41
- protected constructor(config: SapConfig, logger: ILogger | null, sessionId?: string, options?: {
42
- skipSessionType?: boolean;
43
- });
65
+ /** The default release deadline, validated once at construction. */
66
+ /**
67
+ * The logoff for the session this connection last held, while it is on its
68
+ * way. One, because a connection holds one session: `connect()` opens it and
69
+ * `disconnect()` closes it, and how many connections to run is the caller's
70
+ * business, not something to be tracked here.
71
+ *
72
+ * Carries the session id, not just the promise, so a release still in flight
73
+ * for an EARLIER session is recognised as not being this one's — reusing it
74
+ * was what left the second session of a reconnect never released at all.
75
+ *
76
+ * The id is the `SAP_SESSIONID` value — the ABAP session, the one locks are
77
+ * bound to — never the cookie header: that header also carries `sap-XSRF_*`,
78
+ * which rotates within one and the same session, so comparing headers made a
79
+ * session stop recognising itself after a token refresh.
80
+ */
81
+ /**
82
+ * How this server opens and gives back a session, decided by asking it rather
83
+ * than by guessing which system it is. Set at establishment; until then the
84
+ * on-prem mechanism, which is the one that needs no resource.
85
+ */
86
+ /**
87
+ * The application server this session lives on, as the server named it.
88
+ *
89
+ * A session belongs to ONE application server. On a multi-node system a
90
+ * request that lands on another gets another session — and a lock held on the
91
+ * first is then dead through nobody's fault and no inactivity. Eclipse pins
92
+ * itself with these headers; without them every request is a fresh throw of
93
+ * the dice.
94
+ *
95
+ * Learned from `sap-adt-saplb` on a response, sent back as `saplb`. Cleared
96
+ * with the rest of the session state: it names a server for a session that no
97
+ * longer exists.
98
+ */
99
+ /**
100
+ * Whether the preflight opened a session of its own.
101
+ *
102
+ * Distinct from "there are cookies": a failed establishment often leaves a
103
+ * cookie from the 401 that rejected it, and that is debris, not a session.
104
+ * Only a preflight answered with a session address opened one, and only that
105
+ * is worth saying goodbye to when establishment then fails.
106
+ */
107
+ protected constructor(config: SapConfig,
108
+ /**
109
+ * Required, and constructed by the caller.
110
+ *
111
+ * There is no default to fall back to and nothing is worked out from the
112
+ * config: the wire is a fact about the deployment, and a library that
113
+ * picked one would be guessing at the thing this design exists to stop
114
+ * guessing at. It also carries what the caller alone can wire — the
115
+ * credential's TLS material, the client — which is why it arrives built.
116
+ */
117
+ transport: IAdtTransport, logger: ILogger | null, sessionId?: string);
44
118
  /**
45
119
  * Set session type (stateful or stateless)
46
120
  * Controls whether x-sap-adt-sessiontype: stateful header is added to requests
47
121
  * - stateful: SAP maintains session state between requests (locks, transactions)
48
122
  * - stateless: Each request is independent
49
123
  *
50
- * When skipSessionType is enabled (via constructor options), this is a no-op:
51
- * the x-sap-adt-sessiontype header will never be sent. This is needed for
52
- * older BASIS versions (e.g. 7.40) where the stateful header causes locks
53
- * to be stored in ABAP session memory instead of the global enqueue table,
54
- * resulting in HTTP 423 on subsequent PUT requests.
124
+ * Whether the header actually goes out is the WIRE's: a system where the
125
+ * stateful header makes the server store locks in session memory instead of
126
+ * the enqueue table — BASIS 7.40 — is a different deployment, and a
127
+ * deployment is a transport, not a flag on the connection.
55
128
  */
56
129
  setSessionType(type: 'stateful' | 'stateless'): void;
57
130
  /**
@@ -65,9 +138,11 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
65
138
  * in a finally, AFTER unlocking). While in a critical section, a short
66
139
  * per-request timeout is not applied — makeAdtRequest uses a large ceiling
67
140
  * (CRITICAL_SECTION_TIMEOUT, env SAP_TIMEOUT_CRITICAL) instead — so a slow
68
- * PUT/activate/unlock is not aborted mid-flight. Aborting mid-flight tears
69
- * down the socket, which drops the stateful ADT session and orphans the
70
- * lock handle, leaving the object locked and inactive.
141
+ * PUT/activate/unlock is not aborted mid-flight. An abort does not end the
142
+ * ABAP session — that session is the server's, and only the server ends it,
143
+ * on an idle timeout this side cannot influence — it strands the caller:
144
+ * the outcome is unknown and the handle `unlock` needs is gone, while the
145
+ * lock survives inside the ABAP session that owns it.
71
146
  *
72
147
  * Nesting is reference-counted so nested begin/end pairs are safe.
73
148
  */
@@ -97,6 +172,27 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
97
172
  * preparation and fetch the CSRF token; connect() owns everything around it.
98
173
  */
99
174
  protected abstract establishSession(): Promise<void>;
175
+ /**
176
+ * Gets the credential ready before anything is sent.
177
+ *
178
+ * A no-op for the auth types whose credential is already in hand — basic
179
+ * builds a header from the configuration, JWT carries a token it was given.
180
+ * It exists for the ones that have to fetch or load theirs, because the
181
+ * preflight now runs BEFORE `establishSession()` and needs a credential to
182
+ * go out with: a certificate connection reads its material there, and
183
+ * without this the preflight throws `certificate material not loaded` while
184
+ * assembling the transport — before a single request is made, on every
185
+ * system, cloud or on-prem.
186
+ *
187
+ * Must be idempotent: `establishSession()` may prepare the same credential
188
+ * again, and does.
189
+ *
190
+ * Kerberos deliberately does NOT implement it. Minting the SPNEGO token this
191
+ * early changes when the exchange happens, and that connection is not
192
+ * production-tested — its preflight fails the way it already did, is caught
193
+ * inside the strategy, and the connection falls back to ICF as before.
194
+ */
195
+ protected prepareCredential(): Promise<void>;
100
196
  /**
101
197
  * Establishes the session, once, under the lifecycle.
102
198
  *
@@ -110,16 +206,37 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
110
206
  /**
111
207
  * Tears the session down. Never throws, and always settles.
112
208
  *
113
- * It waits for NOTHING. Deciding when to disconnect is the caller's, and so is
114
- * preparing for it — finishing chains, releasing locks. Waiting here on a
115
- * request whose caller chose no timeout is what made a teardown unbounded, and
116
- * an unbounded teardown blocks every later transition on the serialized tail.
209
+ * Tells the server the session is no longer needed, then clears the local
210
+ * state. **When the server actually reclaims it is the server's business** —
211
+ * possibly not until the next connect asks it for one — and nothing here
212
+ * waits for that or depends on it. What matters is that the session stops
213
+ * being counted against the user, which dropping the cookie alone does not
214
+ * achieve: the server keeps it until its own timeout, so a process that
215
+ * connects repeatedly leaves one behind every time. Measured on S/4HANA on-prem, 25 connects in a row: with the logoff,
216
+ * 24-25 of them were given a session; without it, 2. A connection that gets
217
+ * no session still answers `200` to a LOCK and hands back a handle the next
218
+ * request cannot use, so the leak surfaces as a half-written object rather
219
+ * than as anything about sessions.
220
+ *
221
+ * **Nothing is waited for.** The logoff is dispatched, not awaited: this
222
+ * method notifies, and whether the server has acted on the notice is not
223
+ * something it reports. Finishing chains and releasing locks stay the
224
+ * caller's to do BEFORE calling — waiting here on a request that carries no
225
+ * timeout by design is what made a teardown unbounded, which blocks every
226
+ * later transition on the serialized tail.
117
227
  *
118
- * Requests already in flight run to completion untouched. Generation fencing
119
- * (see `SessionLifecycle.isCurrent`) keeps their results from reaching this
120
- * connection afterwards.
228
+ * **Requests already in flight are not waited for, and the logoff ends the
229
+ * session they are running on** — so they will start failing against a
230
+ * session that no longer exists. That is the caller having asked to
231
+ * disconnect, not a race, and it is a change from the version that only
232
+ * dropped the cookie. Generation fencing (see `SessionLifecycle.isCurrent`)
233
+ * keeps their results from reaching this connection either way.
121
234
  *
122
- * Sends no ADT session-close — see the design's D2.
235
+ * **Nothing to wait for.** This TELLS the server the session is finished and
236
+ * does not act on the answer — whether and when the session is freed is the
237
+ * server's affair. Waiting for a reply nobody reads was also the one thing
238
+ * that could make a teardown unbounded, since the goodbye carries no request
239
+ * timeout by design.
123
240
  */
124
241
  disconnect(): Promise<void>;
125
242
  isConnected(): boolean;
@@ -131,14 +248,9 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
131
248
  * session cookie. Use {@link isConnected} for connection state.
132
249
  *
133
250
  * It follows that null → non-null is not a replacement but an identity being
134
- * learned; only a CHANGED value means the session was replaced.
251
+ * learned; only a CHANGED value means the session we had is gone.
135
252
  */
136
253
  getSessionIdentity(): string | null;
137
- /**
138
- * Discards the session at a caller's request: cancels queued recoveries and
139
- * queues the cleanup rather than tearing down under a live request.
140
- */
141
- reset(): void;
142
254
  /**
143
255
  * Re-establishes the session for a request that is recovering from a
144
256
  * credential renewal, then lets that request retry.
@@ -166,9 +278,40 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
166
278
  * Shared by connect() and recoverSession() rather than written twice —
167
279
  * the two drifted apart once already, and a third caller would drift again.
168
280
  */
281
+ /**
282
+ * What a session strategy is given: enough to make one request and to prove
283
+ * the session is ours, and nothing else. It cannot reach session state, so a
284
+ * strategy can neither mark this connection connected nor tear it down.
285
+ */
286
+ /**
287
+ * Ask the server to open a session before the establishing call needs one.
288
+ *
289
+ * The strategy is chosen by what the server publishes, not by which system we
290
+ * believe it to be: ABAP Cloud offers a session resource and issues
291
+ * `SAP_SESSIONID` to whoever asks for one — with `x-sap-security-session:
292
+ * create` — while on-prem has no such resource and its session arrives with
293
+ * the establishing request. Believing cloud simply "issues no SAP_SESSIONID"
294
+ * is what happens when nobody asks.
295
+ */
296
+ /**
297
+ * What a wire needs from this connection to get a session, or give one back.
298
+ *
299
+ * A snapshot in the sense that matters: a close is dispatched without being
300
+ * awaited, so nothing here may read the connection again once the teardown
301
+ * has cleared it.
302
+ */
303
+ protected sessionContext(): IAdtSessionContext;
169
304
  private establishAndCommit;
170
305
  /** The teardown epoch, for a recovery to capture before it starts. */
171
306
  protected get teardownEpoch(): number;
307
+ /**
308
+ * Which session the connection is on now.
309
+ *
310
+ * Moves whenever the session does. A response that comes back carrying an
311
+ * older one belongs to a session that has already been replaced, and must not
312
+ * be acted on as if it said something about the current one.
313
+ */
314
+ protected get sessionGeneration(): number;
172
315
  /**
173
316
  * Raises a session-lost teardown from inside request handling.
174
317
  *
@@ -200,7 +343,7 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
200
343
  * turns "your lock is dead" back into "your request 403'd", which is the very
201
344
  * information the caller needs and the only one it cannot recover itself.
202
345
  */
203
- private isSessionVerdict;
346
+ protected isSessionVerdict(error: unknown): boolean;
204
347
  /**
205
348
  * Folds a response into the session state AND acts on what it means, in one
206
349
  * step.
@@ -210,7 +353,7 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
210
353
  * later check reads `unchanged`. That is one call site forgetting, and it
211
354
  * happened — on the error path and on every retry response.
212
355
  */
213
- private observeResponse;
356
+ protected observeResponse(headers?: Record<string, unknown>, generation?: number): void;
214
357
  /**
215
358
  * Acts on what a response said about the session identity.
216
359
  *
@@ -231,7 +374,7 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
231
374
  /**
232
375
  * Whether the server is telling us the session it was given no longer exists.
233
376
  *
234
- * The E19 shape was HTTP 400 with "Session not found", answered in ~60 ms
377
+ * One on-prem system answered HTTP 400 with "Session not found", answered in ~60 ms
235
378
  * with the cookie present — which is why identity comparison cannot see this:
236
379
  * the cookie, and therefore the fingerprint, is completely unchanged. The
237
380
  * exact match is landscape-specific and is one of the live probes this design
@@ -255,19 +398,17 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
255
398
  private performRequest;
256
399
  protected abstract buildAuthorizationHeader(): string;
257
400
  /**
258
- * Fetch CSRF token from SAP system
259
- * Protected method for use by concrete implementations in their connect() method
401
+ * Ask the wire to establish itself, and hand back what it earned.
402
+ *
403
+ * The exchange itself is the transport's — it is the HTTP wire that has a
404
+ * token to earn and an endpoint to earn it from, and the RFC wire that has
405
+ * neither. What stays here is the part that is about the SESSION rather than
406
+ * the wire: fencing the answer by generation, and letting the identity policy
407
+ * read what it means.
260
408
  */
261
- protected fetchCsrfToken(url: string, retryCount?: number, retryDelay?: number,
409
+ protected fetchCsrfToken(_url: string, retryCount?: number, retryDelay?: number,
262
410
  /** Fences the response effects; omitted during connect(), which has no lease. */
263
411
  generation?: number): Promise<string>;
264
- /**
265
- * Fetch CSRF token from a specific endpoint with retries
266
- */
267
- private fetchCsrfTokenFromEndpoint;
268
- /**
269
- * Get CSRF token (protected for use by subclasses)
270
- */
271
412
  protected getCsrfToken(): string | null;
272
413
  /**
273
414
  * Set CSRF token (protected for use by subclasses)
@@ -277,27 +418,38 @@ declare abstract class AbstractAbapConnection implements AbapConnection, ISessio
277
418
  * Get cookies (protected for use by subclasses)
278
419
  */
279
420
  protected getCookies(): string | null;
280
- protected setInitialCookies(cookies: string): void;
281
421
  /**
282
- * Folds a response's cookies into the jar and classifies what that means for
283
- * the session identity. Returns the classification rather than acting on it:
284
- * cookie parsing stays free of policy, and no exception fires in the middle
285
- * of a state update. The caller decides.
422
+ * Seed the wire with cookies the caller already holds — a SAML session, which
423
+ * IS the credential rather than something a logon call earns.
424
+ *
425
+ * Handed over as a response would deliver them, because the wire owns the jar
426
+ * and how it stores them is its business, not this class's.
286
427
  */
287
- private updateCookiesFromResponse;
428
+ protected setInitialCookies(cookies: string): void;
288
429
  /**
289
- * Subclasses override to inject extra https.Agent options (e.g. mTLS cert/key/pfx).
290
- * The returned options are merged with the base options (rejectUnauthorized).
430
+ * Subclasses override to inject extra https.Agent options (e.g. mTLS
431
+ * cert/key/pfx). The returned options are merged with the base options
432
+ * (rejectUnauthorized).
291
433
  */
292
434
  protected getHttpsAgentOptions(): import('node:https').AgentOptions;
293
- private getAxiosInstance;
294
- private ensureFreshCsrfToken;
435
+ /**
436
+ * Make sure the wire is ready to carry a mutation.
437
+ *
438
+ * What ready MEANS is the wire's: HTTP holds a CSRF token and returns at once
439
+ * when it already has one; an RFC conversation has nothing to earn and does
440
+ * nothing. Demanding a token back was an HTTP assumption, and over RFC it
441
+ * raised `No CSRF token in response headers` before every write — swallowed
442
+ * by the caller, but logged as an error and repeated on the next one.
443
+ */
444
+ private ensureWireReady;
295
445
  /**
296
446
  * Clear SAP-side session state when SAP rejects the cached CSRF token + session
297
447
  * cookies (HTTP 401 on a mutation while a cached token exists). This forces the
298
448
  * next request path to fetch a fresh token and a fresh SAP_SESSIONID cookie.
299
449
  *
300
- * Distinct from reset(): this leaves the axios instance and interceptors in place.
450
+ * Distinct from disconnect(): this leaves the axios instance and interceptors
451
+ * in place, and tells the server nothing — it is a request-level repair, not a
452
+ * teardown.
301
453
  */
302
454
  private invalidateSession;
303
455
  private shouldRetryCsrf;
@@ -1 +1 @@
1
- {"version":3,"file":"AbstractAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbstractAbapConnection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AbstractAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/AbstractAbapConnection.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,sBAAsB,EAE5B,MAAM,0BAA0B,CAAC;AAMlC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAEL,gBAAgB,EAEjB,MAAM,gCAAgC,CAAC;AAExC,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9E,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAGnB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,uBAAe,sBACb,YAAW,cAAc,EAAE,sBAAsB;IAsE/C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB;;;;;;;;OAQG;IACH,QAAQ,CAAC,SAAS,EAAE,aAAa;IACjC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI;IA/E3C;;;;OAIG;IACH,SAAS,CAAC,QAAQ,CAAC,SAAS,mBAA0B;IAEtD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,WAAW,CAAyC;IAC5D;;;;;;;;;;OAUG;IACH,OAAO,CAAC,iBAAiB,CAAS;IAClC,oFAAoF;IACpF,OAAO,CAAC,oBAAoB,CAAK;IACjC,oEAAoE;IACpE;;;;;;;;;;;;;;OAcG;IACH;;;;OAIG;IACH;;;;;;;;;;;;OAYG;IACH;;;;;;;OAOG;IAEH,SAAS,aACU,MAAM,EAAE,SAAS;IAClC;;;;;;;;OAQG;IACM,SAAS,EAAE,aAAa,EACd,MAAM,EAAE,OAAO,GAAG,IAAI,EACzC,SAAS,CAAC,EAAE,MAAM;IAoBpB;;;;;;;;;;OAUG;IACH,cAAc,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI;IAOpD;;OAEG;IACH,cAAc,IAAI,WAAW,GAAG,UAAU;IAI1C;;;;;;;;;;;;;;OAcG;IACH,oBAAoB,IAAI,IAAI;IAQ5B;;;;OAIG;IACH,kBAAkB,IAAI,IAAI;IAY1B;;OAEG;IACH,mBAAmB,IAAI,OAAO;IAI9B;;;OAGG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAKrC;;OAEG;IACH,YAAY,IAAI,MAAM,GAAG,IAAI;IAI7B,SAAS,IAAI,SAAS;IAItB;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAEpD;;;;;;;;;;;;;;;;;;;OAmBG;cACa,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAElD;;;;;;;;OAQG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAkCG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAkCjC,WAAW,IAAI,OAAO;IAItB;;;;;;;;;OASG;IACH,kBAAkB,IAAI,MAAM,GAAG,IAAI;IAInC;;;;;;;;;;;;OAYG;cACa,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpE;;;;;;;;;;;;OAYG;IACH;;;;OAIG;IAEH;;;;;;;;;OASG;IAEH;;;;;;OAMG;IACH,SAAS,CAAC,cAAc,IAAI,kBAAkB;YAUhC,kBAAkB;IAqJhC,sEAAsE;IACtE,SAAS,KAAK,aAAa,IAAI,MAAM,CAEpC;IAED;;;;;;OAMG;IACH,SAAS,KAAK,iBAAiB,IAAI,MAAM,CAExC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,SAAS,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAShD,6DAA6D;IAC7D,SAAS,CAAC,cAAc,IAAI,IAAI;IAIhC;;;;;;;OAOG;IACH,SAAS,CAAC,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IASnD;;;;;;;;OAQG;IACH,SAAS,CAAC,eAAe,CACvB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,UAAU,CAAC,EAAE,MAAM,GAClB,IAAI;IA2BP;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,mBAAmB;IAc3B;;;;;;;;OAQG;IACH,OAAO,CAAC,qBAAqB;IAc7B,+EAA+E;IAC/E,OAAO,CAAC,iBAAiB;IASzB;;;;;;;OAOG;IACH,SAAS,CAAC,kBAAkB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAI7C,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAI7B,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAejD,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACnC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAchB,cAAc;IA8W5B,SAAS,CAAC,QAAQ,CAAC,wBAAwB,IAAI,MAAM;IAErD;;;;;;;;OAQG;cACa,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,UAAU,GAAE,MAAgC,EAC5C,UAAU,GAAE,MAAgC;IAC5C,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC;IAqBlB,SAAS,CAAC,YAAY,IAAI,MAAM,GAAG,IAAI;IAIvC;;OAEG;IACH,SAAS,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAMlD;;OAEG;IACH,SAAS,CAAC,UAAU,IAAI,MAAM,GAAG,IAAI;IAIrC;;;;;;OAMG;IACH,SAAS,CAAC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAMlD;;;;OAIG;IACH,SAAS,CAAC,oBAAoB,IAAI,OAAO,YAAY,EAAE,YAAY;IAInE;;;;;;;;OAQG;YACW,eAAe;IAW7B;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;IAiBzB,OAAO,CAAC,eAAe;CA6BxB;AAED,OAAO,EAAE,sBAAsB,EAAE,CAAC"}