@mcp-abap-adt/connection 2.0.0 → 4.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.
@@ -197,22 +197,27 @@ class AbstractAbapConnection {
197
197
  });
198
198
  }
199
199
  /**
200
- * Tears the session down. Never throws: the report says what did not finish
201
- * rather than failing. Sends no ADT session-close — see the design's D2.
200
+ * Tears the session down. Never throws, and always settles.
201
+ *
202
+ * It waits for NOTHING. Deciding when to disconnect is the caller's, and so is
203
+ * preparing for it — finishing chains, releasing locks. Waiting here on a
204
+ * request whose caller chose no timeout is what made a teardown unbounded, and
205
+ * an unbounded teardown blocks every later transition on the serialized tail.
206
+ *
207
+ * Requests already in flight run to completion untouched. Generation fencing
208
+ * (see `SessionLifecycle.isCurrent`) keeps their results from reaching this
209
+ * connection afterwards.
210
+ *
211
+ * Sends no ADT session-close — see the design's D2.
202
212
  */
203
213
  async disconnect() {
214
+ // Synchronous, at the call: admission shuts and the generation moves before
215
+ // anything is queued, so a caller who has asked to disconnect cannot have
216
+ // requests still going through while this waits its turn.
204
217
  this.lifecycle.beginTeardown({ origin: 'caller', sessionLost: false });
205
- // The report travels THROUGH the transition, so a joining caller gets the
206
- // same one. Building it outside would leave every caller but the first
207
- // reporting an empty teardown — the abandoned locks reported to nobody.
208
- return this.lifecycle.transition('disconnect', async () => {
209
- const drained = await this.lifecycle.drain();
218
+ await this.lifecycle.transition('disconnect', async () => {
210
219
  this.clearSessionState();
211
220
  this.lifecycle.markDisconnected();
212
- return {
213
- abandonedWindows: drained.abandonedWindows,
214
- releasePending: false,
215
- };
216
221
  });
217
222
  }
218
223
  isConnected() {
@@ -231,13 +236,6 @@ class AbstractAbapConnection {
231
236
  getSessionIdentity() {
232
237
  return this.lifecycle.identity;
233
238
  }
234
- /** Opens a lock window. See the design: a lock outlives its request. */
235
- beginWindow(label) {
236
- return this.lifecycle.beginWindow(label);
237
- }
238
- endWindow(token) {
239
- this.lifecycle.endWindow(token);
240
- }
241
239
  /**
242
240
  * Discards the session at a caller's request: cancels queued recoveries and
243
241
  * queues the cleanup rather than tearing down under a live request.
@@ -245,7 +243,6 @@ class AbstractAbapConnection {
245
243
  reset() {
246
244
  this.lifecycle.beginTeardown({ origin: 'caller', sessionLost: true });
247
245
  void this.lifecycle.transition('cleanup', async () => {
248
- await this.lifecycle.drain();
249
246
  this.clearSessionState();
250
247
  this.lifecycle.markDisconnected();
251
248
  });
@@ -285,6 +282,12 @@ class AbstractAbapConnection {
285
282
  if (this.lifecycle.teardownEpoch !== baselineEpoch) {
286
283
  throw (0, SessionLifecycle_js_1.sessionError)(interfaces_1.ADT_SESSION_ERROR.NOT_CONNECTED, 'Establishment abandoned: a teardown was requested for this connection');
287
284
  }
285
+ // Whatever session arrives from here is one we are deliberately
286
+ // establishing, so it must not read as a replacement: the identity policy
287
+ // treats a changed fingerprint as fatal, and it cannot tell our own
288
+ // re-establishment from a session taken out from under us. Forgetting first
289
+ // makes the new fingerprint `established`, which is what it is.
290
+ this.lifecycle.forgetIdentity();
288
291
  try {
289
292
  await this.establishSession();
290
293
  }
@@ -342,14 +345,13 @@ class AbstractAbapConnection {
342
345
  * immediately — a later comparison must see the change, and on a dead session
343
346
  * the cookie is unchanged, so only the state can tell.
344
347
  *
345
- * Does not await: it is called from inside a request, and the queued cleanup
346
- * drains that very request.
348
+ * Does not await: it is called from inside a request, and the cleanup must not
349
+ * wait for the very request that raised it.
347
350
  */
348
351
  raiseSessionLost(reason) {
349
352
  this.logger?.warn(`Session lost: ${reason}`);
350
353
  this.lifecycle.beginTeardown({ origin: 'internal', sessionLost: true });
351
354
  void this.lifecycle.transition('cleanup', async () => {
352
- await this.lifecycle.drain();
353
355
  this.clearSessionState();
354
356
  this.lifecycle.markDisconnected();
355
357
  });
@@ -381,27 +383,44 @@ class AbstractAbapConnection {
381
383
  * later check reads `unchanged`. That is one call site forgetting, and it
382
384
  * happened — on the error path and on every retry response.
383
385
  */
384
- observeResponse(headers) {
386
+ observeResponse(headers, generation) {
387
+ // Fenced by SESSION GENERATION, not by the teardown epoch. Only a
388
+ // caller-initiated teardown moves the epoch — a recovery deliberately does
389
+ // not — so after a session loss and a successful recovery, a request from
390
+ // the dead session carries the same epoch as the new one and would sail
391
+ // straight through. The generation moves whenever the current session does.
392
+ //
393
+ // `undefined` means "not issued against a session": the CSRF fetch during
394
+ // connect() has no lease and must apply, since it is establishing the very
395
+ // session this would compare against.
396
+ if (generation !== undefined &&
397
+ generation !== this.lifecycle.sessionGeneration) {
398
+ this.logger?.debug('Ignoring a response from a previous session: its effects are fenced');
399
+ return;
400
+ }
385
401
  this.applyIdentityPolicy(this.updateCookiesFromResponse(headers));
386
402
  }
387
403
  /**
388
404
  * Acts on what a response said about the session identity.
389
405
  *
390
- * A replacement is fatal only while a lock is held — and "a lock is held"
391
- * means an open window, not `sessionMode`: a mode flag cannot represent
392
- * windows, another handler can flip it back, and a batch never sets it.
393
- * With no lock held the same replacement is not a loss: nothing was being
394
- * held, so the new identity simply becomes the current one.
406
+ * A replacement is always fatal, and that is a narrowing: an earlier version
407
+ * tolerated it "when no lock is held", deciding from the connection's own
408
+ * lock windows. Those are gone, and rightly — this layer does not know that a
409
+ * lock exists, what object it covers or what would release it. Locks are
410
+ * tracked a layer up, per object, by the code that took them.
411
+ *
412
+ * So the rule is written from what this layer CAN know: the ABAP session we
413
+ * were speaking to is not the one we are speaking to now. Anything the caller
414
+ * held against the old one is dead, and continuing quietly would hand them a
415
+ * session they never opened — the failure this whole design exists to prevent.
416
+ * Being wrong in this direction costs a reconnect; being wrong the other way
417
+ * costs a lock nobody can find.
395
418
  */
396
419
  applyIdentityPolicy(classification) {
397
420
  if (classification !== 'replaced')
398
421
  return;
399
- if (this.lifecycle.openWindows.length === 0) {
400
- this.logger?.debug('Session identity changed with no lock held; continuing on the new one');
401
- return;
402
- }
403
- this.raiseSessionLost('the session cookie changed while a lock was held');
404
- throw (0, SessionLifecycle_js_1.sessionError)(interfaces_1.ADT_SESSION_ERROR.SESSION_REPLACED, 'The SAP session was replaced while a lock was held; the lock handle is dead');
422
+ this.raiseSessionLost('the session cookie changed under us');
423
+ throw (0, SessionLifecycle_js_1.sessionError)(interfaces_1.ADT_SESSION_ERROR.SESSION_REPLACED, 'The SAP session was replaced; anything held against the previous one is dead');
405
424
  }
406
425
  /**
407
426
  * Whether the server is telling us the session it was given no longer exists.
@@ -476,13 +495,13 @@ class AbstractAbapConnection {
476
495
  // shut the door.
477
496
  const lease = this.lifecycle.admitRequest();
478
497
  try {
479
- return await this.performRequest(options);
498
+ return await this.performRequest(options, lease);
480
499
  }
481
500
  finally {
482
501
  lease.release();
483
502
  }
484
503
  }
485
- async performRequest(options) {
504
+ async performRequest(options, lease) {
486
505
  const { url: endpoint, method, timeout, data, params, headers: customHeaders, } = options;
487
506
  const normalizedMethod = method.toUpperCase();
488
507
  // Build full URL: baseUrl + endpoint
@@ -578,7 +597,7 @@ class AbstractAbapConnection {
578
597
  });
579
598
  try {
580
599
  const response = await this.getAxiosInstance()(requestConfig);
581
- this.observeResponse(response.headers);
600
+ this.observeResponse(response.headers, lease.generation);
582
601
  this.logger?.debug(`Request succeeded with status ${response.status}`, {
583
602
  type: 'REQUEST_SUCCESS',
584
603
  status: response.status,
@@ -588,6 +607,20 @@ class AbstractAbapConnection {
588
607
  return response;
589
608
  }
590
609
  catch (error) {
610
+ // FENCE FIRST, before anything reads or writes shared state.
611
+ //
612
+ // Fencing observeResponse() alone was not enough, and the gap was wide:
613
+ // everything below acts on this connection, not on the request. A late
614
+ // 400 "session not found" would call raiseSessionLost() and tear down the
615
+ // healthy session established since; a late 401/403 would call
616
+ // invalidateSession(), write a fresh CSRF token, and RETRY — replaying a
617
+ // mutation from a dead session inside the live one.
618
+ //
619
+ // A stale request gets its error back and nothing else happens.
620
+ if (!this.lifecycle.isCurrent(lease)) {
621
+ this.logger?.debug('A request from a previous session failed; its recovery is fenced');
622
+ throw error;
623
+ }
591
624
  const errorDetails = {
592
625
  type: 'REQUEST_ERROR',
593
626
  message: error instanceof Error ? error.message : String(error),
@@ -601,7 +634,7 @@ class AbstractAbapConnection {
601
634
  typeof error.response.data === 'string'
602
635
  ? error.response.data.slice(0, 200)
603
636
  : JSON.stringify(error.response.data).slice(0, 200);
604
- this.observeResponse(error.response.headers);
637
+ this.observeResponse(error.response.headers, lease.generation);
605
638
  }
606
639
  // The server telling us the session is gone is invisible to the identity
607
640
  // comparison: the cookie, and therefore the fingerprint, is unchanged.
@@ -653,7 +686,7 @@ class AbstractAbapConnection {
653
686
  delete requestHeaders.cookie;
654
687
  }
655
688
  try {
656
- this.setCsrfToken(await this.fetchCsrfToken(requestUrl, 5, 2000));
689
+ this.setCsrfToken(await this.fetchCsrfToken(requestUrl, 5, 2000, lease.generation));
657
690
  const refreshedToken = this.getCsrfToken();
658
691
  if (refreshedToken) {
659
692
  requestHeaders['x-csrf-token'] = refreshedToken;
@@ -663,7 +696,7 @@ class AbstractAbapConnection {
663
696
  requestHeaders.Cookie = refreshedCookies;
664
697
  }
665
698
  const retryResponse = await this.getAxiosInstance()(requestConfig);
666
- this.observeResponse(retryResponse.headers);
699
+ this.observeResponse(retryResponse.headers, lease.generation);
667
700
  return retryResponse;
668
701
  }
669
702
  catch (retryError) {
@@ -691,19 +724,19 @@ class AbstractAbapConnection {
691
724
  this.logger?.debug(`[DEBUG] BaseAbapConnection - 401 on GET request, retrying with cookies from error response`);
692
725
  requestHeaders.Cookie = this.cookies;
693
726
  const retryResponse = await this.getAxiosInstance()(requestConfig);
694
- this.observeResponse(retryResponse.headers);
727
+ this.observeResponse(retryResponse.headers, lease.generation);
695
728
  return retryResponse;
696
729
  }
697
730
  // If no cookies, try to get them via CSRF token fetch
698
731
  this.logger?.debug(`[DEBUG] BaseAbapConnection - 401 on GET request, attempting to get cookies via CSRF token fetch`);
699
732
  try {
700
733
  // Try to get CSRF token (this will also get cookies)
701
- this.csrfToken = await this.fetchCsrfToken(requestUrl, 3, 1000);
734
+ this.csrfToken = await this.fetchCsrfToken(requestUrl, 3, 1000, lease.generation);
702
735
  if (this.cookies) {
703
736
  requestHeaders.Cookie = this.cookies;
704
737
  this.logger?.debug(`[DEBUG] BaseAbapConnection - Retrying GET request with cookies from CSRF fetch`);
705
738
  const retryResponse = await this.getAxiosInstance()(requestConfig);
706
- this.observeResponse(retryResponse.headers);
739
+ this.observeResponse(retryResponse.headers, lease.generation);
707
740
  return retryResponse;
708
741
  }
709
742
  }
@@ -722,7 +755,9 @@ class AbstractAbapConnection {
722
755
  * Fetch CSRF token from SAP system
723
756
  * Protected method for use by concrete implementations in their connect() method
724
757
  */
725
- async fetchCsrfToken(url, retryCount = csrfConfig_js_1.CSRF_CONFIG.RETRY_COUNT, retryDelay = csrfConfig_js_1.CSRF_CONFIG.RETRY_DELAY) {
758
+ async fetchCsrfToken(url, retryCount = csrfConfig_js_1.CSRF_CONFIG.RETRY_COUNT, retryDelay = csrfConfig_js_1.CSRF_CONFIG.RETRY_DELAY,
759
+ /** Fences the response effects; omitted during connect(), which has no lease. */
760
+ generation) {
726
761
  // Try primary endpoint first, then fallback for older systems
727
762
  const baseUrl = url.includes('/sap/bc/adt/')
728
763
  ? url.split('/sap/bc/adt')[0]
@@ -746,7 +781,7 @@ class AbstractAbapConnection {
746
781
  let lastError;
747
782
  for (const csrfUrl of endpoints) {
748
783
  try {
749
- return await this.fetchCsrfTokenFromEndpoint(csrfUrl, retryCount, retryDelay);
784
+ return await this.fetchCsrfTokenFromEndpoint(csrfUrl, retryCount, retryDelay, generation);
750
785
  }
751
786
  catch (error) {
752
787
  // Third layer with a catch on this path, and the last one that could
@@ -766,7 +801,7 @@ class AbstractAbapConnection {
766
801
  /**
767
802
  * Fetch CSRF token from a specific endpoint with retries
768
803
  */
769
- async fetchCsrfTokenFromEndpoint(csrfUrl, retryCount, retryDelay) {
804
+ async fetchCsrfTokenFromEndpoint(csrfUrl, retryCount, retryDelay, generation) {
770
805
  this.logger?.debug(`Fetching CSRF token from: ${csrfUrl}`);
771
806
  for (let attempt = 0; attempt <= retryCount; attempt++) {
772
807
  try {
@@ -804,7 +839,7 @@ class AbstractAbapConnection {
804
839
  headers,
805
840
  timeout: (0, timeouts_js_1.getTimeout)('csrf'),
806
841
  });
807
- this.observeResponse(response.headers);
842
+ this.observeResponse(response.headers, generation);
808
843
  const token = response.headers['x-csrf-token'];
809
844
  if (!token) {
810
845
  this.logger?.error('No CSRF token in response headers', {
@@ -818,7 +853,7 @@ class AbstractAbapConnection {
818
853
  throw new Error(csrfConfig_js_1.CSRF_ERROR_MESSAGES.NOT_IN_HEADERS);
819
854
  }
820
855
  if (response.headers['set-cookie']) {
821
- this.observeResponse(response.headers);
856
+ this.observeResponse(response.headers, generation);
822
857
  if (this.cookies) {
823
858
  this.logger?.debug(`[DEBUG] BaseAbapConnection - Cookies received from CSRF response (first 100 chars): ${this.cookies.substring(0, 100)}...`);
824
859
  this.logger?.debug('Cookies extracted from response', {
@@ -842,7 +877,7 @@ class AbstractAbapConnection {
842
877
  // Always try to extract cookies from error response, even on 401
843
878
  // This ensures cookies are available for subsequent requests
844
879
  if (error.response?.headers) {
845
- this.observeResponse(error.response.headers);
880
+ this.observeResponse(error.response.headers, generation);
846
881
  if (this.cookies) {
847
882
  this.logger?.debug('Cookies extracted from error response', {
848
883
  status: error.response.status,
@@ -861,14 +896,14 @@ class AbstractAbapConnection {
861
896
  this.logger?.debug('CSRF: SAP returned 405 (Method Not Allowed) — not critical, token found in header');
862
897
  const token = error.response.headers['x-csrf-token'];
863
898
  if (token) {
864
- this.observeResponse(error.response.headers);
899
+ this.observeResponse(error.response.headers, generation);
865
900
  return token;
866
901
  }
867
902
  }
868
903
  if (error.response?.headers['x-csrf-token']) {
869
904
  this.logger?.debug(`Got CSRF token despite error (status: ${error.response?.status})`);
870
905
  const token = error.response.headers['x-csrf-token'];
871
- this.observeResponse(error.response.headers);
906
+ this.observeResponse(error.response.headers, generation);
872
907
  return token;
873
908
  }
874
909
  if (error.response) {
@@ -1037,6 +1072,13 @@ class AbstractAbapConnection {
1037
1072
  this.setCsrfToken(null);
1038
1073
  this.cookies = null;
1039
1074
  this.cookieStore.clear();
1075
+ // And the tracked identity, because WE discarded the session. Without this
1076
+ // the cookie that arrives next reads as a foreign replacement — and since a
1077
+ // replacement is now always fatal, our own deliberate re-authentication
1078
+ // would tear the connection down. The distinction that matters is not
1079
+ // "was a lock held" but "did we cause this": what we discarded on purpose
1080
+ // is not a session taken from under us.
1081
+ this.lifecycle.forgetIdentity();
1040
1082
  }
1041
1083
  shouldRetryCsrf(error) {
1042
1084
  if (!(error instanceof axios_1.AxiosError)) {
@@ -25,7 +25,7 @@ class BaseAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnect
25
25
  this.logger?.debug(`[DEBUG] BaseAbapConnection - Connecting to SAP system: ${discoveryUrl}`);
26
26
  try {
27
27
  // Try to get CSRF token (this will also get cookies)
28
- const token = await this.fetchCsrfToken(discoveryUrl, 3, 1000);
28
+ const token = await this.fetchCsrfToken(discoveryUrl);
29
29
  this.setCsrfToken(token);
30
30
  this.logger?.debug('Successfully connected to SAP system', {
31
31
  hasCsrfToken: !!this.getCsrfToken(),
@@ -47,7 +47,7 @@ class CertificateAbapConnection extends AbstractAbapConnection_js_1.AbstractAbap
47
47
  const baseUrl = await this.getBaseUrl();
48
48
  const discoveryUrl = `${baseUrl}/sap/bc/adt/discovery`;
49
49
  try {
50
- const token = await this.fetchCsrfToken(discoveryUrl, 3, 1000);
50
+ const token = await this.fetchCsrfToken(discoveryUrl);
51
51
  this.setCsrfToken(token);
52
52
  }
53
53
  catch (error) {
@@ -1,4 +1,4 @@
1
- import type { IAdtResponse, ITokenRefresher } from '@mcp-abap-adt/interfaces';
1
+ import { type IAdtResponse, type ITokenRefresher } from '@mcp-abap-adt/interfaces';
2
2
  import type { SapConfig } from '../config/sapConfig.js';
3
3
  import type { ILogger } from '../logger.js';
4
4
  import type { AbapRequestOptions } from './AbapConnection.js';
@@ -7,19 +7,92 @@ import { AbstractAbapConnection } from './AbstractAbapConnection.js';
7
7
  * JWT Authentication connection for SAP BTP Cloud systems
8
8
  *
9
9
  * Supports automatic token refresh via ITokenRefresher injection:
10
- * - If tokenRefresher is provided, 401/403 errors trigger automatic token refresh
11
- * - If tokenRefresher is not provided, 401/403 errors throw an error (legacy behavior)
10
+ * - a **401** triggers a token refresh when a tokenRefresher is available;
11
+ * - without one, or when the refresh does not help, the server's own error is
12
+ * thrown unchanged — status and body intact;
13
+ * - a **403** is never a token problem. It propagates as it arrived, because a
14
+ * new token is the same caller and cannot change a permissions answer.
12
15
  */
13
16
  export declare class JwtAbapConnection extends AbstractAbapConnection {
14
17
  private tokenRefresher?;
15
18
  private currentToken;
19
+ /** Bumped by any token refresh, token-only ones included. */
20
+ private tokenGeneration;
21
+ /** Single-flight over the token fetch alone. Touches no session state. */
22
+ private tokenRefreshInFlight?;
23
+ /**
24
+ * The `tokenGeneration` a completed session recovery was built for. Only
25
+ * `performRenewal` moves it, and only after `recoverSession` resolves.
26
+ *
27
+ * Separate from `tokenGeneration` because a token-only refresh — which
28
+ * `fetchCsrfToken` does, from inside an establishment — bumps that counter
29
+ * without rebuilding anything. Reading it as "the session is settled" made
30
+ * the outer handler retry a request whose session was never rebuilt.
31
+ */
32
+ private recoveredGeneration;
33
+ /**
34
+ * Single-flight over the whole credential renewal, session included.
35
+ *
36
+ * Sharing only the token fetch leaves the expensive half racing: `recover`
37
+ * and `cleanup` never join (`SessionLifecycle.transition`), so two concurrent
38
+ * renewals queue as cleanup A → recover A → cleanup B → recover B, and A's
39
+ * retry meets a session B has just torn down.
40
+ */
41
+ private renewalInFlight?;
42
+ /**
43
+ * Per connection, deliberately NOT static. `baseline` is compared against
44
+ * `this.tokenGeneration`, which is instance state — a store shared between
45
+ * instances would let one connection's operation hand its baseline to
46
+ * another's, and the comparison would be between unrelated counters.
47
+ */
48
+ private readonly recoveryScope;
16
49
  constructor(config: SapConfig, logger?: ILogger | null, sessionId?: string, tokenRefresher?: ITokenRefresher);
17
50
  protected buildAuthorizationHeader(): string;
18
51
  /**
19
- * Refresh the JWT token using the injected tokenRefresher
20
- * @returns true if token was refreshed, false if no refresher available
52
+ * For the public entry point: this call is its own operation, always.
53
+ *
54
+ * A re-entrant `makeAdtRequest` — from a logger or a refresher callback the
55
+ * connection itself invokes while a scope is live — is a new caller-visible
56
+ * operation. Inheriting there would hand it a baseline from somebody else's
57
+ * refresh, which reads as "already refreshed for me" and skips a refresh it
58
+ * needs.
59
+ */
60
+ private inNewRecoveryScope;
61
+ /** For the inner levels: join the operation in progress, or start one. */
62
+ private inRecoveryScope;
63
+ /**
64
+ * The baseline this operation is reasoning from.
65
+ *
66
+ * The `active` check here and the one in `inRecoveryScope` OVERLAP: each
67
+ * compensates for the other, and the stale-context test only fails when both
68
+ * are removed. Kept as two because they answer different questions — "may I
69
+ * join this scope" and "may I trust this baseline" — and a reader who finds
70
+ * one redundant would be deleting half a guarantee.
71
+ */
72
+ private currentBaseline;
73
+ /**
74
+ * Fetch a new token, unless somebody already did for this operation.
75
+ *
76
+ * Single-flighted so two concurrent handlers — including two nested
77
+ * `fetchCsrfToken` calls, which is a level `renewalInFlight` cannot reach —
78
+ * share one network call instead of racing and leaving `currentToken` as
79
+ * whichever settled last.
80
+ *
81
+ * @returns true when the caller may retry.
82
+ */
83
+ private refreshTokenOnce;
84
+ private performTokenRefresh;
85
+ /**
86
+ * Renew the credential and the session it belongs to, once, shared.
87
+ */
88
+ private renewCredential;
89
+ private performRenewal;
90
+ /**
91
+ * May this operation retry?
92
+ *
93
+ * The order of the checks is the design, not style.
21
94
  */
22
- private tryRefreshToken;
95
+ private ensureRecovered;
23
96
  /**
24
97
  * Establishes the session for this auth type. Called by
25
98
  * AbstractAbapConnection.connect(), which owns the lifecycle around it.
@@ -29,10 +102,14 @@ export declare class JwtAbapConnection extends AbstractAbapConnection {
29
102
  * Override makeAdtRequest to handle JWT auth errors with automatic token refresh
30
103
  */
31
104
  makeAdtRequest<T = any, D = any>(options: AbapRequestOptions): Promise<IAdtResponse<T, D>>;
105
+ private attemptRequest;
32
106
  /**
33
107
  * Override fetchCsrfToken to handle JWT auth errors with automatic token refresh
34
108
  */
35
- protected fetchCsrfToken(url: string, retryCount?: number, retryDelay?: number): Promise<string>;
109
+ protected fetchCsrfToken(url: string, retryCount?: number, retryDelay?: number,
110
+ /** Fences the response effects; omitted during connect(), which has no lease. */
111
+ generation?: number): Promise<string>;
112
+ private attemptCsrfToken;
36
113
  private static validateConfig;
37
114
  }
38
115
  //# sourceMappingURL=JwtAbapConnection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"JwtAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/JwtAbapConnection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE;;;;;;GAMG;AACH,qBAAa,iBAAkB,SAAQ,sBAAsB;IAC3D,OAAO,CAAC,cAAc,CAAC,CAAkB;IACzC,OAAO,CAAC,YAAY,CAAS;gBAG3B,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,eAAe;IAWlC,SAAS,CAAC,wBAAwB,IAAI,MAAM;IAW5C;;;OAGG;YACW,eAAe;IA0B7B;;;OAGG;cACa,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IA2DjD;;OAEG;IACG,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACnC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAiE9B;;OAEG;cACa,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,UAAU,SAAI,EACd,UAAU,SAAO,GAChB,OAAO,CAAC,MAAM,CAAC;IA2ClB,OAAO,CAAC,MAAM,CAAC,cAAc;CAa9B"}
1
+ {"version":3,"file":"JwtAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/JwtAbapConnection.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAuBrE;;;;;;;;;GASG;AACH,qBAAa,iBAAkB,SAAQ,sBAAsB;IAC3D,OAAO,CAAC,cAAc,CAAC,CAAkB;IACzC,OAAO,CAAC,YAAY,CAAS;IAE7B,6DAA6D;IAC7D,OAAO,CAAC,eAAe,CAAK;IAE5B,0EAA0E;IAC1E,OAAO,CAAC,oBAAoB,CAAC,CAAmB;IAEhD;;;;;;;;OAQG;IACH,OAAO,CAAC,mBAAmB,CAAK;IAEhC;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe,CAAC,CAAmB;IAE3C;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2C;gBAGvE,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,eAAe;IAWlC,SAAS,CAAC,wBAAwB,IAAI,MAAM;IAW5C;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAc1B,0EAA0E;IAC1E,OAAO,CAAC,eAAe;IASvB;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe;IAQvB;;;;;;;;;OASG;IACH,OAAO,CAAC,gBAAgB;YAsBV,mBAAmB;IAoBjC;;OAEG;IACH,OAAO,CAAC,eAAe;YAiBT,cAAc;IAwB5B;;;;OAIG;YACW,eAAe;IAyB7B;;;OAGG;cACa,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BjD;;OAEG;IACG,cAAc,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG,EACnC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAKhB,cAAc;IA8D5B;;OAEG;cACa,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,UAAU,GAAE,MAAgC,EAC5C,UAAU,GAAE,MAAgC;IAC5C,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC;YASJ,gBAAgB;IAkD9B,OAAO,CAAC,MAAM,CAAC,cAAc;CAa9B"}