@mcp-abap-adt/connection 1.10.2 → 2.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.
- package/CHANGELOG.md +758 -0
- package/README.md +42 -11
- package/dist/__tests__/helpers/session.d.ts +15 -0
- package/dist/__tests__/helpers/session.d.ts.map +1 -0
- package/dist/__tests__/helpers/session.js +19 -0
- package/dist/auth/ntlm.d.ts +15 -0
- package/dist/auth/ntlm.d.ts.map +1 -1
- package/dist/auth/ntlm.js +38 -0
- package/dist/connection/AbstractAbapConnection.d.ts +163 -11
- package/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
- package/dist/connection/AbstractAbapConnection.js +351 -14
- package/dist/connection/BaseAbapConnection.d.ts +5 -1
- package/dist/connection/BaseAbapConnection.d.ts.map +1 -1
- package/dist/connection/BaseAbapConnection.js +11 -1
- package/dist/connection/CertificateAbapConnection.d.ts +5 -1
- package/dist/connection/CertificateAbapConnection.d.ts.map +1 -1
- package/dist/connection/CertificateAbapConnection.js +11 -1
- package/dist/connection/JwtAbapConnection.d.ts +3 -2
- package/dist/connection/JwtAbapConnection.d.ts.map +1 -1
- package/dist/connection/JwtAbapConnection.js +19 -8
- package/dist/connection/KerberosAbapConnection.d.ts +5 -1
- package/dist/connection/KerberosAbapConnection.d.ts.map +1 -1
- package/dist/connection/KerberosAbapConnection.js +54 -3
- package/dist/connection/SamlAbapConnection.d.ts +5 -1
- package/dist/connection/SamlAbapConnection.d.ts.map +1 -1
- package/dist/connection/SamlAbapConnection.js +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/session/SessionLifecycle.d.ts +2 -9
- package/dist/session/SessionLifecycle.d.ts.map +1 -1
- package/dist/session/SessionLifecycle.js +8 -8
- package/docs/INDEX.md +106 -0
- package/docs/INSTALLATION.md +304 -0
- package/docs/JWT_AUTH_TOOLS.md +142 -0
- package/docs/MIGRATION-2.0.md +125 -0
- package/docs/SCOPE.md +44 -0
- package/docs/STATEFUL_SESSION_GUIDE.md +121 -0
- package/docs/USAGE.md +749 -0
- package/examples/README.md +112 -0
- package/examples/basic-connection.js +55 -0
- package/examples/jwt-with-token-refresh.js +87 -0
- package/examples/saml-connection.js +52 -0
- package/examples/websocket-transport.js +87 -0
- package/package.json +11 -4
|
@@ -52,9 +52,10 @@ class JwtAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnecti
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
55
|
+
* Establishes the session for this auth type. Called by
|
|
56
|
+
* AbstractAbapConnection.connect(), which owns the lifecycle around it.
|
|
56
57
|
*/
|
|
57
|
-
async
|
|
58
|
+
async establishSession() {
|
|
58
59
|
const baseUrl = await this.getBaseUrl();
|
|
59
60
|
const discoveryUrl = `${baseUrl}/sap/bc/adt/discovery`;
|
|
60
61
|
this.logger?.debug(`[DEBUG] JwtAbapConnection - Connecting to SAP system: ${discoveryUrl}`);
|
|
@@ -85,9 +86,11 @@ class JwtAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnecti
|
|
|
85
86
|
}
|
|
86
87
|
// Try to refresh token if tokenRefresher is available
|
|
87
88
|
if (await this.tryRefreshToken()) {
|
|
88
|
-
// Retry connect
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
// Retry the ESTABLISHMENT, not connect(): connect() runs this as a
|
|
90
|
+
// joinable transition, so a nested call joins the transition already
|
|
91
|
+
// in flight — this one — and waits for itself forever.
|
|
92
|
+
this.logger?.debug(`[DEBUG] JwtAbapConnection - Retrying establishment after token refresh...`);
|
|
93
|
+
return this.establishSession();
|
|
91
94
|
}
|
|
92
95
|
throw new Error('JWT token has expired. Please re-authenticate.');
|
|
93
96
|
}
|
|
@@ -99,6 +102,9 @@ class JwtAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnecti
|
|
|
99
102
|
* Override makeAdtRequest to handle JWT auth errors with automatic token refresh
|
|
100
103
|
*/
|
|
101
104
|
async makeAdtRequest(options) {
|
|
105
|
+
// Captured before the attempt: a recovery asks "has the caller asked to
|
|
106
|
+
// stop since this request began", not since some later bookkeeping step.
|
|
107
|
+
const baselineEpoch = this.teardownEpoch;
|
|
102
108
|
this.logger?.debug(`[DEBUG] JwtAbapConnection.makeAdtRequest - Starting request: ${options.method} ${options.url}`);
|
|
103
109
|
try {
|
|
104
110
|
const response = await super.makeAdtRequest(options);
|
|
@@ -124,9 +130,14 @@ class JwtAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnecti
|
|
|
124
130
|
}
|
|
125
131
|
// Try to refresh token if tokenRefresher is available
|
|
126
132
|
if (await this.tryRefreshToken()) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
this.logger?.debug(`[DEBUG] JwtAbapConnection.makeAdtRequest - Recovering session after token refresh...`);
|
|
134
|
+
// The renewed credential cannot keep the old ABAP session, so this is
|
|
135
|
+
// a session-lost teardown — internal, or it would cancel the very
|
|
136
|
+
// recovery it is setting up. reset() would be the caller-origin one.
|
|
137
|
+
this.discardSession();
|
|
138
|
+
// Re-establish before retrying: the retry goes through admission, and
|
|
139
|
+
// a discarded session admits nothing.
|
|
140
|
+
await this.recoverSession(baselineEpoch);
|
|
130
141
|
return super.makeAdtRequest(options);
|
|
131
142
|
}
|
|
132
143
|
throw new Error('JWT token has expired. Please re-authenticate.');
|
|
@@ -14,7 +14,11 @@ export declare class KerberosAbapConnection extends AbstractAbapConnection {
|
|
|
14
14
|
* request — the first request carries the Negotiate header; SAP then issues a session
|
|
15
15
|
* cookie which is reused for subsequent requests.
|
|
16
16
|
*/
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Establishes the session for this auth type. Called by
|
|
19
|
+
* AbstractAbapConnection.connect(), which owns the lifecycle around it.
|
|
20
|
+
*/
|
|
21
|
+
protected establishSession(): Promise<void>;
|
|
18
22
|
protected buildAuthorizationHeader(): string;
|
|
19
23
|
}
|
|
20
24
|
//# sourceMappingURL=KerberosAbapConnection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"KerberosAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/KerberosAbapConnection.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"KerberosAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/KerberosAbapConnection.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE,uGAAuG;AACvG,qBAAa,sBAAuB,SAAQ,sBAAsB;IAChE,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,YAAY,CAAM;gBAEd,MAAM,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EAAE,SAAS,CAAC,EAAE,MAAM;IAQ1E,OAAO,CAAC,MAAM,CAAC,cAAc;IAa7B,mDAAmD;cACnC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5C;;;;OAIG;IACH;;;OAGG;cACa,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAqFjD,SAAS,CAAC,wBAAwB,IAAI,MAAM;CAS7C"}
|
|
@@ -34,16 +34,38 @@ class KerberosAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapCon
|
|
|
34
34
|
* request — the first request carries the Negotiate header; SAP then issues a session
|
|
35
35
|
* cookie which is reused for subsequent requests.
|
|
36
36
|
*/
|
|
37
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Establishes the session for this auth type. Called by
|
|
39
|
+
* AbstractAbapConnection.connect(), which owns the lifecycle around it.
|
|
40
|
+
*/
|
|
41
|
+
async establishSession() {
|
|
38
42
|
await this.ensureToken();
|
|
39
43
|
const baseUrl = await this.getBaseUrl();
|
|
40
44
|
const discoveryUrl = `${baseUrl}/sap/bc/adt/discovery`;
|
|
41
45
|
this.logger?.debug(`[DEBUG] KerberosAbapConnection - Connecting to SAP system: ${discoveryUrl}`);
|
|
42
46
|
try {
|
|
43
|
-
|
|
47
|
+
// ONE attempt, deliberately — the shared default is 3 retries a second
|
|
48
|
+
// apart, and every one of them corrupts this exchange:
|
|
49
|
+
//
|
|
50
|
+
// - A GSS token is one-shot. Replaying it is meaningless at best, and
|
|
51
|
+
// replay protection may answer it differently than the original.
|
|
52
|
+
// - Worse, a 401 that sets a cookie silences buildAuthorizationHeader()
|
|
53
|
+
// (a cookie carries auth after the first round trip), so the retry
|
|
54
|
+
// goes out with NO Authorization at all and draws the server's plain
|
|
55
|
+
// opening challenge.
|
|
56
|
+
//
|
|
57
|
+
// Either way the classifier below would read the LAST response and name
|
|
58
|
+
// the wrong cause. The retry cannot help here anyway: nothing about a
|
|
59
|
+
// rejected token changes by waiting a second. Retrying a transient
|
|
60
|
+
// network failure is the caller's business now that connect() is
|
|
61
|
+
// explicit, and a fresh connect() mints a fresh token.
|
|
62
|
+
const token = await this.fetchCsrfToken(discoveryUrl, 0, 0);
|
|
44
63
|
this.setCsrfToken(token);
|
|
45
64
|
}
|
|
46
65
|
catch (error) {
|
|
66
|
+
// The token went out and was not accepted, so it is spent. Keeping it
|
|
67
|
+
// would make every later connect() replay a token already known to fail.
|
|
68
|
+
this.currentToken = '';
|
|
47
69
|
if (error instanceof axios_1.AxiosError && error.response?.headers) {
|
|
48
70
|
const wwwAuth = error.response.headers['www-authenticate'];
|
|
49
71
|
if ((0, ntlm_js_1.isNtlmChallenge)(wwwAuth)) {
|
|
@@ -53,8 +75,37 @@ class KerberosAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapCon
|
|
|
53
75
|
if (this.getCookies()) {
|
|
54
76
|
this.logger?.debug('[DEBUG] KerberosAbapConnection - cookies captured from error response during connect');
|
|
55
77
|
}
|
|
78
|
+
// A 401 carrying a Negotiate token is a CONTINUATION, not a success.
|
|
79
|
+
//
|
|
80
|
+
// Per RFC 4559 the server is handing back gssapi-data for the client to
|
|
81
|
+
// feed into its GSS context and retry. This client cannot do that:
|
|
82
|
+
// generateSpnegoToken() calls step('') once and discards the context,
|
|
83
|
+
// so there is nothing to continue with. The token it holds is the
|
|
84
|
+
// initial one, and sending it again produces the same 401.
|
|
85
|
+
//
|
|
86
|
+
// So this fails, and says why — resolving here would claim a readiness
|
|
87
|
+
// that does not exist and move the failure to the first real request,
|
|
88
|
+
// where it reads as an unrelated auth error.
|
|
89
|
+
if ((0, ntlm_js_1.isNegotiateContinuation)(wwwAuth)) {
|
|
90
|
+
throw new Error('KerberosAbapConnection: the server continued the SPNEGO exchange ' +
|
|
91
|
+
'(401 with a Negotiate token), which requires feeding that token ' +
|
|
92
|
+
'back into the GSS context and retrying. This client performs a ' +
|
|
93
|
+
'single-leg exchange only — the context is discarded after the ' +
|
|
94
|
+
'first step — so multi-leg SPNEGO is not supported. See ' +
|
|
95
|
+
'https://www.rfc-editor.org/rfc/rfc4559');
|
|
96
|
+
}
|
|
97
|
+
// A BARE Negotiate is a different problem with a different fix: the
|
|
98
|
+
// initial Authorization has already gone out, so an empty challenge is
|
|
99
|
+
// the server declining it. No token came back, so there is nothing to
|
|
100
|
+
// step even for a client that supports multi-leg.
|
|
101
|
+
if ((0, ntlm_js_1.isBareNegotiateChallenge)(wwwAuth)) {
|
|
102
|
+
throw new Error('KerberosAbapConnection: the server rejected the SPNEGO token ' +
|
|
103
|
+
'(401 with a bare Negotiate challenge, no token returned). The ' +
|
|
104
|
+
'credentials were not accepted — check the SPN, the ticket ' +
|
|
105
|
+
'(klist / kinit), and that the user is permitted Kerberos logon.');
|
|
106
|
+
}
|
|
56
107
|
}
|
|
57
|
-
|
|
108
|
+
throw error;
|
|
58
109
|
}
|
|
59
110
|
}
|
|
60
111
|
buildAuthorizationHeader() {
|
|
@@ -13,7 +13,11 @@ export declare class SamlAbapConnection extends AbstractAbapConnection {
|
|
|
13
13
|
* Connect to SAP system using existing session cookies
|
|
14
14
|
* Fetches CSRF token to establish session context
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Establishes the session for this auth type. Called by
|
|
18
|
+
* AbstractAbapConnection.connect(), which owns the lifecycle around it.
|
|
19
|
+
*/
|
|
20
|
+
protected establishSession(): Promise<void>;
|
|
17
21
|
protected buildAuthorizationHeader(): string;
|
|
18
22
|
getAuthHeaders(): Promise<Record<string, string>>;
|
|
19
23
|
private static validateConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SamlAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/SamlAbapConnection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,sBAAsB;IAC5D,OAAO,CAAC,cAAc,CAAS;gBAG7B,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE;IAUzC;;;OAGG;
|
|
1
|
+
{"version":3,"file":"SamlAbapConnection.d.ts","sourceRoot":"","sources":["../../src/connection/SamlAbapConnection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,sBAAsB;IAC5D,OAAO,CAAC,cAAc,CAAS;gBAG7B,MAAM,EAAE,SAAS,EACjB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,EACvB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE;IAUzC;;;OAGG;IACH;;;OAGG;cACa,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAwCjD,SAAS,CAAC,wBAAwB,IAAI,MAAM;IAItC,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAQvD,OAAO,CAAC,MAAM,CAAC,cAAc;CAW9B"}
|
|
@@ -20,7 +20,11 @@ class SamlAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnect
|
|
|
20
20
|
* Connect to SAP system using existing session cookies
|
|
21
21
|
* Fetches CSRF token to establish session context
|
|
22
22
|
*/
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Establishes the session for this auth type. Called by
|
|
25
|
+
* AbstractAbapConnection.connect(), which owns the lifecycle around it.
|
|
26
|
+
*/
|
|
27
|
+
async establishSession() {
|
|
24
28
|
const baseUrl = await this.getBaseUrl();
|
|
25
29
|
const discoveryUrl = `${baseUrl}/sap/bc/adt/discovery`;
|
|
26
30
|
this.logger?.debug(`[DEBUG] SamlAbapConnection - Connecting to SAP system: ${discoveryUrl}`);
|
|
@@ -41,6 +45,12 @@ class SamlAbapConnection extends AbstractAbapConnection_js_1.AbstractAbapConnect
|
|
|
41
45
|
this.logger?.debug(`[DEBUG] SamlAbapConnection - Cookies extracted from error response during connect (first 100 chars): ${this.getCookies()?.substring(0, 100)}...`);
|
|
42
46
|
}
|
|
43
47
|
}
|
|
48
|
+
// Rethrow: a resolved connect() must mean a usable session exists. This
|
|
49
|
+
// used to swallow and resolve anyway, deferring establishment to the
|
|
50
|
+
// first request — coherent only while that lazy path existed. Without it,
|
|
51
|
+
// swallowing would leave a connection that reports success, holds
|
|
52
|
+
// nothing, and refuses every request.
|
|
53
|
+
throw error;
|
|
44
54
|
}
|
|
45
55
|
}
|
|
46
56
|
buildAuthorizationHeader() {
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAC;AACxF,YAAY,EACV,WAAW,EACX,SAAS,EACT,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,YAAY,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAC;AACxF,YAAY,EACV,WAAW,EACX,SAAS,EACT,iBAAiB,GAClB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,YAAY,EACV,cAAc,EACd,kBAAkB,GACnB,MAAM,gCAAgC,CAAC;AAOxC,OAAO,EACL,kBAAkB,EAClB,kBAAkB,IAAI,oBAAoB,GAC3C,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AAEtF,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAEzE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EACL,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,IAAI,mBAAmB,GACzC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,KAAK,aAAa,GACnB,MAAM,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,10 @@ Object.defineProperty(exports, "FileCertificateMaterialLoader", { enumerable: tr
|
|
|
7
7
|
// Config utilities
|
|
8
8
|
var sapConfig_js_1 = require("./config/sapConfig.js");
|
|
9
9
|
Object.defineProperty(exports, "sapConfigSignature", { enumerable: true, get: function () { return sapConfig_js_1.sapConfigSignature; } });
|
|
10
|
+
// The session lifecycle vocabulary — ISessionLifecycleAware, ILockWindowAware,
|
|
11
|
+
// ITeardownReport, WindowToken, ADT_SESSION_ERROR — is deliberately NOT exported
|
|
12
|
+
// here. It lives in @mcp-abap-adt/interfaces, and a consumer imports it from
|
|
13
|
+
// there: re-exporting a contract type gives it two names and lets the two drift.
|
|
10
14
|
// Connection classes - only final implementations
|
|
11
15
|
// Deprecated aliases for backward compatibility
|
|
12
16
|
var BaseAbapConnection_js_1 = require("./connection/BaseAbapConnection.js");
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
* Design: docs/superpowers/specs/2026-07-27-session-lifecycle-design.md in
|
|
11
11
|
* @mcp-abap-adt/adt-clients.
|
|
12
12
|
*/
|
|
13
|
-
|
|
14
|
-
export type WindowToken = symbol;
|
|
13
|
+
import { type AdtSessionErrorCode, type WindowToken } from '@mcp-abap-adt/interfaces';
|
|
15
14
|
export type TransitionKind = 'connect' | 'disconnect' | 'recover' | 'cleanup';
|
|
16
15
|
export interface RequestLease {
|
|
17
16
|
/** Teardown epoch at admission — the baseline a recovery compares against. */
|
|
@@ -29,12 +28,6 @@ export interface BeginTeardownOptions {
|
|
|
29
28
|
/** Decides admission: a lost session cannot let an open window finish. */
|
|
30
29
|
sessionLost: boolean;
|
|
31
30
|
}
|
|
32
|
-
export declare const ADT_SESSION_ERROR: {
|
|
33
|
-
readonly NOT_CONNECTED: "ADT_NOT_CONNECTED";
|
|
34
|
-
readonly SESSION_REPLACED: "ADT_SESSION_REPLACED";
|
|
35
|
-
readonly RELEASE_PENDING: "ADT_RELEASE_PENDING";
|
|
36
|
-
};
|
|
37
|
-
export type AdtSessionErrorCode = (typeof ADT_SESSION_ERROR)[keyof typeof ADT_SESSION_ERROR];
|
|
38
31
|
export declare function sessionError(code: AdtSessionErrorCode, message?: string): Error & {
|
|
39
32
|
code: AdtSessionErrorCode;
|
|
40
33
|
};
|
|
@@ -131,7 +124,7 @@ export declare class SessionLifecycle {
|
|
|
131
124
|
* internal cleanup owes its result to nobody while a caller's teardown owes a
|
|
132
125
|
* report.
|
|
133
126
|
*/
|
|
134
|
-
transition(kind: TransitionKind, run: () => Promise<
|
|
127
|
+
transition<T>(kind: TransitionKind, run: () => Promise<T>): Promise<T>;
|
|
135
128
|
private wake;
|
|
136
129
|
private changed;
|
|
137
130
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionLifecycle.d.ts","sourceRoot":"","sources":["../../src/session/SessionLifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH
|
|
1
|
+
{"version":3,"file":"SessionLifecycle.d.ts","sourceRoot":"","sources":["../../src/session/SessionLifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,WAAW,EACjB,MAAM,0BAA0B,CAAC;AAElC,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,SAAS,CAAC;AAE9E,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,0FAA0F;IAC1F,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC9B,0EAA0E;IAC1E,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,mBAAmB,EACzB,OAAO,CAAC,EAAE,MAAM,GACf,KAAK,GAAG;IAAE,IAAI,EAAE,mBAAmB,CAAA;CAAE,CAMvC;AAED,MAAM,WAAW,uBAAuB;IACtC,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAQD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAAgD;IAC7D,OAAO,CAAC,WAAW,CAA6B;IAChD,OAAO,CAAC,KAAK,CAAK;IAElB,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,UAAU,CAAuB;IACzC,mEAAmE;IACnE,OAAO,CAAC,mBAAmB,CAAS;IAEpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuC;IAC/D,OAAO,CAAC,QAAQ,CAAK;IAErB,OAAO,CAAC,IAAI,CAAuC;IACnD,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,WAAW,CAAiC;IAEpD,OAAO,CAAC,OAAO,CAAyB;IAExC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,OAAO,GAAE,uBAA4B;IAOjD;;;OAGG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,0EAA0E;IAC1E,IAAI,QAAQ,IAAI,MAAM,GAAG,IAAI,CAM5B;IAED,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,0DAA0D;IAC1D,IAAI,WAAW,IAAI,SAAS,MAAM,EAAE,CAEnC;IAED;;;;;;;;;;OAUG;IACH,aAAa,CAAC,WAAW,GAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAa,GAAG,IAAI;IAWzE,gBAAgB,IAAI,IAAI;IAMxB;;;;;;OAMG;IACH,OAAO,CACL,WAAW,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,GACvC,WAAW,GAAG,aAAa,GAAG,UAAU;IAiB3C,uEAAuE;IACvE,OAAO,KAAK,mBAAmB,GAO9B;IAED,OAAO,KAAK,MAAM,GAIjB;IAED,YAAY,IAAI,IAAI;IAMpB,4EAA4E;IAC5E,YAAY,IAAI,YAAY;IAkB5B;;;OAGG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW;IASvC,+EAA+E;IAC/E,SAAS,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAOnC,aAAa,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,oBAAoB,GAAG,IAAI;IAkBlE,IAAI,iBAAiB,IAAI,OAAO,CAE/B;IAED;;;;;;;;OAQG;IACG,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC;IA2BnC,OAAO,KAAK,WAAW,GAItB;IAED,OAAO,CAAC,eAAe;IAUvB;;;;;;;;;;OAUG;IACH,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IA8BtE,OAAO,CAAC,IAAI;IAMZ,OAAO,CAAC,OAAO;CAehB"}
|
|
@@ -12,13 +12,9 @@
|
|
|
12
12
|
* @mcp-abap-adt/adt-clients.
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.SessionLifecycle =
|
|
15
|
+
exports.SessionLifecycle = void 0;
|
|
16
16
|
exports.sessionError = sessionError;
|
|
17
|
-
|
|
18
|
-
NOT_CONNECTED: 'ADT_NOT_CONNECTED',
|
|
19
|
-
SESSION_REPLACED: 'ADT_SESSION_REPLACED',
|
|
20
|
-
RELEASE_PENDING: 'ADT_RELEASE_PENDING',
|
|
21
|
-
};
|
|
17
|
+
const interfaces_1 = require("@mcp-abap-adt/interfaces");
|
|
22
18
|
function sessionError(code, message) {
|
|
23
19
|
const error = new Error(message ?? code);
|
|
24
20
|
error.code = code;
|
|
@@ -134,7 +130,7 @@ class SessionLifecycle {
|
|
|
134
130
|
}
|
|
135
131
|
assertUsable() {
|
|
136
132
|
if (!this.admits) {
|
|
137
|
-
throw sessionError(
|
|
133
|
+
throw sessionError(interfaces_1.ADT_SESSION_ERROR.NOT_CONNECTED);
|
|
138
134
|
}
|
|
139
135
|
}
|
|
140
136
|
/** Asserts usability and counts the request in, in one synchronous step. */
|
|
@@ -161,7 +157,7 @@ class SessionLifecycle {
|
|
|
161
157
|
*/
|
|
162
158
|
beginWindow(label) {
|
|
163
159
|
if (this.state !== 'connected' || this.teardownPending) {
|
|
164
|
-
throw sessionError(
|
|
160
|
+
throw sessionError(interfaces_1.ADT_SESSION_ERROR.NOT_CONNECTED);
|
|
165
161
|
}
|
|
166
162
|
const token = Symbol(label);
|
|
167
163
|
this.windows.set(token, { label, abandoned: false });
|
|
@@ -258,6 +254,10 @@ class SessionLifecycle {
|
|
|
258
254
|
transition(kind, run) {
|
|
259
255
|
const joinable = kind === 'connect' || kind === 'disconnect';
|
|
260
256
|
if (joinable && this.tailKind === kind && this.tailPromise) {
|
|
257
|
+
// Joining shares the ANSWER, not just the execution: a joiner that got a
|
|
258
|
+
// resolved promise carrying nothing would have to invent a result, and
|
|
259
|
+
// for a teardown that means reporting no abandoned locks when there were
|
|
260
|
+
// some. Callers of one kind ask the same question, so they get one reply.
|
|
261
261
|
return this.tailPromise;
|
|
262
262
|
}
|
|
263
263
|
const queued = this.tail.then(run, run);
|
package/docs/INDEX.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Documentation Index
|
|
2
|
+
|
|
3
|
+
**Package:** `@mcp-abap-adt/connection`
|
|
4
|
+
**Version:** see [CHANGELOG.md](../CHANGELOG.md) — kept there rather than
|
|
5
|
+
duplicated here, where it went stale by eight minor versions.
|
|
6
|
+
|
|
7
|
+
## Package Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
mcp-abap-connection/
|
|
11
|
+
├── README.md # Main package documentation
|
|
12
|
+
├── CHANGELOG.md # Version history and changes
|
|
13
|
+
├── docs/ # Detailed documentation
|
|
14
|
+
│ ├── INDEX.md # This file - documentation overview
|
|
15
|
+
│ ├── INSTALLATION.md # Setup and installation guide
|
|
16
|
+
│ ├── USAGE.md # API documentation and examples
|
|
17
|
+
│ ├── MIGRATION-2.0.md # Moving to the explicit session lifecycle
|
|
18
|
+
│ ├── SCOPE.md # What this package does and does not own
|
|
19
|
+
│ ├── STATEFUL_SESSION_GUIDE.md # Stateful requests and lock windows
|
|
20
|
+
│ └── JWT_AUTH_TOOLS.md # CLI tool for authentication
|
|
21
|
+
├── examples/ # Working code examples
|
|
22
|
+
│ ├── README.md # Examples overview
|
|
23
|
+
│ └── basic-connection.js # Simple connection example
|
|
24
|
+
├── bin/ # CLI tools
|
|
25
|
+
│ └── sap-abap-auth.js # JWT authentication CLI
|
|
26
|
+
└── src/ # Source code
|
|
27
|
+
├── connection/ # Connection classes
|
|
28
|
+
├── config/ # Configuration utilities
|
|
29
|
+
├── utils/ # Helper functions
|
|
30
|
+
└── __tests__/ # Unit tests
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Links
|
|
34
|
+
|
|
35
|
+
### Getting Started
|
|
36
|
+
- 📦 [Installation Guide](./INSTALLATION.md) - How to install and set up the package
|
|
37
|
+
- 📚 [Usage Guide](./USAGE.md) - Basic usage and comprehensive API documentation
|
|
38
|
+
- 📖 [Main README](../README.md) - Package overview and quick start
|
|
39
|
+
- 🧭 [Scope and Boundaries](./SCOPE.md) - What this package does (and does not), sibling packages, and why there is no RFC to cloud
|
|
40
|
+
|
|
41
|
+
### Core Features
|
|
42
|
+
- 🔑 [JWT Auth Tools](./JWT_AUTH_TOOLS.md) - CLI tool for browser-based authentication
|
|
43
|
+
|
|
44
|
+
### Version Information
|
|
45
|
+
- 📋 [CHANGELOG](../CHANGELOG.md) - Complete version history, including what the latest release changed
|
|
46
|
+
|
|
47
|
+
### Examples
|
|
48
|
+
- 📁 [Examples Overview](../examples/README.md) - All available examples
|
|
49
|
+
- 🔌 [Basic Connection](../examples/basic-connection.js) - Simple connection setup
|
|
50
|
+
|
|
51
|
+
## Documentation by Topic
|
|
52
|
+
|
|
53
|
+
### Authentication
|
|
54
|
+
- **Basic Auth**: [USAGE.md - Basic Authentication](./USAGE.md#basic-authentication-on-premise)
|
|
55
|
+
- **JWT/OAuth2**: [USAGE.md - JWT Authentication](./USAGE.md#jwt-authentication-cloudbtp)
|
|
56
|
+
- **Token Refresh**: Handled by `@mcp-abap-adt/auth-broker` package (removed in 0.2.0)
|
|
57
|
+
- **CLI Tool**: [JWT_AUTH_TOOLS.md](./JWT_AUTH_TOOLS.md)
|
|
58
|
+
|
|
59
|
+
### Session Management
|
|
60
|
+
- **Overview**: [USAGE.md - Session Management](./USAGE.md#session-management)
|
|
61
|
+
- **Stateful Mode**: Use `setSessionType('stateful')` for session headers
|
|
62
|
+
- **Session State Persistence**: Handled by `@mcp-abap-adt/auth-broker` package
|
|
63
|
+
- **API Methods**:
|
|
64
|
+
- `getSessionId()` - Get current session ID (auto-generated UUID)
|
|
65
|
+
- `setSessionType()` - Switch between stateful/stateless modes
|
|
66
|
+
|
|
67
|
+
### API Reference
|
|
68
|
+
- **Connection Interface**: [USAGE.md - API Reference](./USAGE.md#api-reference)
|
|
69
|
+
- **Configuration Types**: [USAGE.md - Configuration Types](./USAGE.md#configuration-types)
|
|
70
|
+
- **Factory Function**: `createAbapConnection()`
|
|
71
|
+
- **Connection Classes**: `BaseAbapConnection`, `JwtAbapConnection`
|
|
72
|
+
|
|
73
|
+
## Version Highlights
|
|
74
|
+
|
|
75
|
+
See [CHANGELOG.md](../CHANGELOG.md). This section used to restate it and drifted
|
|
76
|
+
eight minor versions behind — a second copy of a changelog is a changelog that
|
|
77
|
+
is wrong.
|
|
78
|
+
|
|
79
|
+
## Documentation Standards
|
|
80
|
+
|
|
81
|
+
### File Organization
|
|
82
|
+
- **README.md** - Package overview, quick start, basic API
|
|
83
|
+
- **CHANGELOG.md** - All changes, following [Keep a Changelog](https://keepachangelog.com/)
|
|
84
|
+
- **docs/** - Detailed documentation, tutorials, guides
|
|
85
|
+
- **examples/** - Working code examples with README
|
|
86
|
+
|
|
87
|
+
### Naming Conventions
|
|
88
|
+
- `UPPERCASE.md` - Main documentation files (README, CHANGELOG)
|
|
89
|
+
- `PascalCase.md` - Detailed guides in docs/ folder
|
|
90
|
+
- `kebab-case.js` - Example files
|
|
91
|
+
|
|
92
|
+
### Content Guidelines
|
|
93
|
+
- Keep README concise, link to detailed docs
|
|
94
|
+
- Include working code examples
|
|
95
|
+
- Document environment variables and configuration
|
|
96
|
+
- Provide troubleshooting sections
|
|
97
|
+
- Show both success and error handling
|
|
98
|
+
|
|
99
|
+
## Contributing Documentation
|
|
100
|
+
|
|
101
|
+
When adding new features:
|
|
102
|
+
1. Update CHANGELOG.md with changes
|
|
103
|
+
2. Add usage examples to USAGE.md
|
|
104
|
+
3. Create working examples in examples/
|
|
105
|
+
4. Update README.md if API changes
|
|
106
|
+
5. Add troubleshooting to relevant guide
|