@mcp-abap-adt/connection 5.0.0 → 6.0.1
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 +243 -1
- package/README.md +204 -48
- package/bin/sap-abap-auth.js +101 -13
- package/dist/auth/providers.d.ts +38 -9
- package/dist/auth/providers.d.ts.map +1 -1
- package/dist/auth/providers.js +46 -3
- package/dist/connection/AbstractAbapConnection.d.ts +83 -136
- package/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
- package/dist/connection/AbstractAbapConnection.js +241 -763
- package/dist/connection/AdtCloudConnector.d.ts +12 -7
- package/dist/connection/AdtCloudConnector.d.ts.map +1 -1
- package/dist/connection/AdtCloudConnector.js +2 -6
- package/dist/connection/AdtOnPremConnector.d.ts +20 -7
- package/dist/connection/AdtOnPremConnector.d.ts.map +1 -1
- package/dist/connection/AdtOnPremConnector.js +2 -6
- package/dist/connection/CloudHttpTransport.d.ts +37 -0
- package/dist/connection/CloudHttpTransport.d.ts.map +1 -0
- package/dist/{session/CloudSecuritySessionStrategy.js → connection/CloudHttpTransport.js} +59 -49
- package/dist/connection/CredentialAbapConnection.d.ts +8 -41
- package/dist/connection/CredentialAbapConnection.d.ts.map +1 -1
- package/dist/connection/CredentialAbapConnection.js +44 -111
- package/dist/connection/HttpTransport.d.ts +178 -0
- package/dist/connection/HttpTransport.d.ts.map +1 -0
- package/dist/connection/HttpTransport.js +402 -0
- package/dist/connection/IAdtTransport.d.ts +232 -0
- package/dist/connection/IAdtTransport.d.ts.map +1 -0
- package/dist/connection/IAdtTransport.js +28 -0
- package/dist/connection/LegacyOnPremHttpTransport.d.ts +40 -0
- package/dist/connection/LegacyOnPremHttpTransport.d.ts.map +1 -0
- package/dist/connection/LegacyOnPremHttpTransport.js +57 -0
- package/dist/connection/OnPremHttpTransport.d.ts +45 -0
- package/dist/connection/OnPremHttpTransport.d.ts.map +1 -0
- package/dist/connection/OnPremHttpTransport.js +91 -0
- package/dist/connection/RfcTransport.d.ts +89 -0
- package/dist/connection/RfcTransport.d.ts.map +1 -0
- package/dist/connection/RfcTransport.js +270 -0
- package/dist/connection/rfcConversation.d.ts +44 -0
- package/dist/connection/rfcConversation.d.ts.map +1 -0
- package/dist/connection/rfcConversation.js +71 -0
- package/dist/index.d.ts +7 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -18
- package/dist/utils/timeouts.d.ts +6 -19
- package/dist/utils/timeouts.d.ts.map +1 -1
- package/dist/utils/timeouts.js +6 -22
- package/docs/INDEX.md +5 -2
- package/docs/INSTALLATION.md +28 -10
- package/docs/JWT_AUTH_TOOLS.md +20 -4
- package/docs/MIGRATION-6.0.md +359 -0
- package/docs/SCOPE.md +1 -1
- package/docs/STATEFUL_SESSION_GUIDE.md +86 -17
- package/docs/USAGE.md +260 -119
- package/examples/basic-connection.js +15 -3
- package/examples/jwt-with-token-refresh.js +15 -7
- package/examples/saml-connection.js +15 -2
- package/package.json +12 -11
- package/dist/__tests__/helpers/session.d.ts +0 -15
- package/dist/__tests__/helpers/session.d.ts.map +0 -1
- package/dist/__tests__/helpers/session.js +0 -19
- package/dist/auth/IAuthProvider.d.ts +0 -84
- package/dist/auth/IAuthProvider.d.ts.map +0 -1
- package/dist/auth/IAuthProvider.js +0 -21
- package/dist/connection/BaseAbapConnection.d.ts +0 -29
- package/dist/connection/BaseAbapConnection.d.ts.map +0 -1
- package/dist/connection/BaseAbapConnection.js +0 -81
- package/dist/connection/CertificateAbapConnection.d.ts +0 -35
- package/dist/connection/CertificateAbapConnection.d.ts.map +0 -1
- package/dist/connection/CertificateAbapConnection.js +0 -91
- package/dist/connection/JwtAbapConnection.d.ts +0 -131
- package/dist/connection/JwtAbapConnection.d.ts.map +0 -1
- package/dist/connection/JwtAbapConnection.js +0 -376
- package/dist/connection/KerberosAbapConnection.d.ts +0 -32
- package/dist/connection/KerberosAbapConnection.d.ts.map +0 -1
- package/dist/connection/KerberosAbapConnection.js +0 -128
- package/dist/connection/RfcAbapConnection.d.ts +0 -44
- package/dist/connection/RfcAbapConnection.d.ts.map +0 -1
- package/dist/connection/RfcAbapConnection.js +0 -324
- package/dist/connection/SamlAbapConnection.d.ts +0 -31
- package/dist/connection/SamlAbapConnection.d.ts.map +0 -1
- package/dist/connection/SamlAbapConnection.js +0 -81
- package/dist/connection/connectionFactory.d.ts +0 -25
- package/dist/connection/connectionFactory.d.ts.map +0 -1
- package/dist/connection/connectionFactory.js +0 -84
- package/dist/session/CloudSecuritySessionStrategy.d.ts +0 -32
- package/dist/session/CloudSecuritySessionStrategy.d.ts.map +0 -1
- package/dist/session/IcfSessionStrategy.d.ts +0 -27
- package/dist/session/IcfSessionStrategy.d.ts.map +0 -1
- package/dist/session/IcfSessionStrategy.js +0 -62
- package/dist/session/SessionStrategy.d.ts +0 -86
- package/dist/session/SessionStrategy.d.ts.map +0 -1
- package/dist/session/SessionStrategy.js +0 -33
- package/docs/superpowers/specs/2026-08-21-platform-connectors.md +0 -108
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* An ADT request carried over RFC.
|
|
4
|
+
*
|
|
5
|
+
* `SADT_REST_RFC_ENDPOINT` is the standard FM Eclipse ADT uses for on-prem ADT
|
|
6
|
+
* through JCo, and what it carries is a request line, header fields and a body
|
|
7
|
+
* — an HTTP request in all but the wire. So this translates, and translating is
|
|
8
|
+
* the whole of its job.
|
|
9
|
+
*
|
|
10
|
+
* It exists because an RFC conversation is inherently stateful: one ABAP
|
|
11
|
+
* session for the connection's lifetime, which is the way through HTTP 423
|
|
12
|
+
* "invalid lock handle" on legacy systems (BASIS < 7.50) where stateful HTTP
|
|
13
|
+
* sessions are not usable.
|
|
14
|
+
*
|
|
15
|
+
* **What is NOT here.** Cookies, the CSRF token, `x-sap-adt-sessiontype` and
|
|
16
|
+
* the session lifecycle belong to the connection above this seam and are
|
|
17
|
+
* already in `request.headers` by the time `send()` is called. A transport that
|
|
18
|
+
* also captured cookies would be doing that work twice, and the two copies
|
|
19
|
+
* would disagree the first time one of them was cleared.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.RfcTransport = void 0;
|
|
23
|
+
const node_crypto_1 = require("node:crypto");
|
|
24
|
+
/**
|
|
25
|
+
* ADT exception types, mapped to what they would have been over HTTP. Standard
|
|
26
|
+
* types, returned in `<exc:exception>` XML.
|
|
27
|
+
*/
|
|
28
|
+
const EXCEPTION_STATUS = {
|
|
29
|
+
ExceptionResourceNotFound: { code: 404, text: 'Not Found' },
|
|
30
|
+
ExceptionResourceNoAuthorization: { code: 403, text: 'Forbidden' },
|
|
31
|
+
ExceptionResourceAlreadyExists: { code: 409, text: 'Conflict' },
|
|
32
|
+
ExceptionResourceLocked: { code: 423, text: 'Locked' },
|
|
33
|
+
ExceptionBadRequest: { code: 400, text: 'Bad Request' },
|
|
34
|
+
ExceptionNotSupported: { code: 501, text: 'Not Implemented' },
|
|
35
|
+
ExceptionConflict: { code: 409, text: 'Conflict' },
|
|
36
|
+
};
|
|
37
|
+
function statusFromExceptionXml(body) {
|
|
38
|
+
const type = /<exc:type[^>]*>([^<]+)<\/exc:type>/.exec(body)?.[1]?.trim();
|
|
39
|
+
return ((type ? EXCEPTION_STATUS[type] : undefined) ?? {
|
|
40
|
+
code: 500,
|
|
41
|
+
text: 'Internal Server Error',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function message(e) {
|
|
45
|
+
if (e instanceof Error)
|
|
46
|
+
return e.message;
|
|
47
|
+
if (e && typeof e === 'object')
|
|
48
|
+
return JSON.stringify(e);
|
|
49
|
+
return String(e);
|
|
50
|
+
}
|
|
51
|
+
/** Axios's own default, which the classification above this seam is written against. */
|
|
52
|
+
const admits2xx = (status) => status >= 200 && status < 300;
|
|
53
|
+
class RfcTransport {
|
|
54
|
+
connect;
|
|
55
|
+
logger;
|
|
56
|
+
kind = 'rfc';
|
|
57
|
+
/** Which system this wire is for. Read by the compiler, never at runtime. */
|
|
58
|
+
system = 'onprem';
|
|
59
|
+
conversation = null;
|
|
60
|
+
/** Names the conversation, and so the ABAP session it carries. */
|
|
61
|
+
conversationId = '';
|
|
62
|
+
/**
|
|
63
|
+
* Nothing to fold in. `SADT_REST_RFC_ENDPOINT` answers with two header
|
|
64
|
+
* fields — `~server_protocol` and `content-type` — on every call, including
|
|
65
|
+
* one that asks for a token with `x-csrf-token: fetch`, and on a stateful
|
|
66
|
+
* POST. Measured on E19. There is no ICM in this path, so there is no ICF
|
|
67
|
+
* session to cookie and no application server to be redispatched between.
|
|
68
|
+
*/
|
|
69
|
+
ingest() { }
|
|
70
|
+
/** None, and never any: see `ingest()`. */
|
|
71
|
+
cookies() {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Nothing to establish. `open()` made the ABAP session, and there is no token
|
|
76
|
+
* to earn: this endpoint answers `x-csrf-token: fetch` with the same two
|
|
77
|
+
* header fields it answers everything else with. A wire with no
|
|
78
|
+
* cross-site request to forge against needs no token to prove one was not.
|
|
79
|
+
*/
|
|
80
|
+
async establish(_context) { }
|
|
81
|
+
/** None: see `establish()`. */
|
|
82
|
+
csrfToken() {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Ignored. Nothing on this wire reads a token, and keeping one would be
|
|
87
|
+
* state that never leaves the object.
|
|
88
|
+
*/
|
|
89
|
+
adoptCsrfToken() { }
|
|
90
|
+
/**
|
|
91
|
+
* The conversation, which IS the session — so it is fingerprinted by its own
|
|
92
|
+
* existence rather than by an address the server hands out.
|
|
93
|
+
*/
|
|
94
|
+
/** The conversation IS the session, so it exists exactly while that does. */
|
|
95
|
+
sessionEstablished() {
|
|
96
|
+
return this.conversation?.alive ?? false;
|
|
97
|
+
}
|
|
98
|
+
sessionFingerprint() {
|
|
99
|
+
const fingerprint = new Map();
|
|
100
|
+
if (this.conversation?.alive) {
|
|
101
|
+
fingerprint.set('rfc-conversation', this.conversationId);
|
|
102
|
+
}
|
|
103
|
+
return fingerprint;
|
|
104
|
+
}
|
|
105
|
+
/** None: there is no dispatcher in front of this wire to stay bound to. */
|
|
106
|
+
affinityHeaders() {
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Nothing to forget separately. The session ends when the conversation does,
|
|
111
|
+
* and that is `close()` — a wire whose state could be dropped while the
|
|
112
|
+
* conversation stayed open would be claiming a session it had disowned.
|
|
113
|
+
*/
|
|
114
|
+
forgetSession() { }
|
|
115
|
+
/**
|
|
116
|
+
* The client is built by a factory rather than constructed here: the SDK is
|
|
117
|
+
* an optional dependency loaded by `require` at open time, and a transport
|
|
118
|
+
* that reached for it in its constructor could not be built at all on a
|
|
119
|
+
* machine without it.
|
|
120
|
+
*/
|
|
121
|
+
constructor(connect, logger = null) {
|
|
122
|
+
this.connect = connect;
|
|
123
|
+
this.logger = logger;
|
|
124
|
+
}
|
|
125
|
+
async open() {
|
|
126
|
+
if (this.conversation?.alive)
|
|
127
|
+
return;
|
|
128
|
+
const conversation = this.connect();
|
|
129
|
+
try {
|
|
130
|
+
await conversation.open();
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
throw new Error(`Failed to open RFC connection: ${message(e)}`);
|
|
134
|
+
}
|
|
135
|
+
this.conversation = conversation;
|
|
136
|
+
// Minted here, where the session begins. A conversation opened after an
|
|
137
|
+
// earlier one closed is a DIFFERENT ABAP session, and the fingerprint has
|
|
138
|
+
// to say so — a constant would report `unchanged` across a reconnect and
|
|
139
|
+
// hide exactly the replacement the identity policy exists to catch.
|
|
140
|
+
this.conversationId = (0, node_crypto_1.randomUUID)();
|
|
141
|
+
this.logger?.debug('RFC conversation opened (stateful by nature)');
|
|
142
|
+
}
|
|
143
|
+
/** Never throws, and a repeat call finds nothing owed. */
|
|
144
|
+
async close() {
|
|
145
|
+
const conversation = this.conversation;
|
|
146
|
+
if (!conversation)
|
|
147
|
+
return;
|
|
148
|
+
this.conversation = null;
|
|
149
|
+
this.conversationId = '';
|
|
150
|
+
try {
|
|
151
|
+
await conversation.close();
|
|
152
|
+
this.logger?.debug('RFC conversation closed');
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
this.logger?.debug(`RFC close error: ${message(e)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async send(request) {
|
|
159
|
+
if (!this.conversation?.alive) {
|
|
160
|
+
throw new Error('RFC transport is not open. Call connect() first.');
|
|
161
|
+
}
|
|
162
|
+
const method = request.method.toUpperCase();
|
|
163
|
+
// HTTP clients serialise `params`; RFC has no such step, so they go into
|
|
164
|
+
// the URI here or they do not travel at all.
|
|
165
|
+
let uri = request.url;
|
|
166
|
+
const query = Object.entries(request.params ?? {}).filter(([, v]) => v !== undefined && v !== null);
|
|
167
|
+
if (query.length > 0) {
|
|
168
|
+
const serialised = new URLSearchParams(query.map(([k, v]) => [k, String(v)])).toString();
|
|
169
|
+
uri += (uri.includes('?') ? '&' : '?') + serialised;
|
|
170
|
+
}
|
|
171
|
+
const headerFields = Object.entries(request.headers ?? {}).map(([NAME, VALUE]) => ({ NAME, VALUE: String(VALUE) }));
|
|
172
|
+
// ADT refuses a request with no Accept — `400 ExceptionResourceBadRequest:
|
|
173
|
+
// Accept header missing`, measured on E19. Over HTTP axios supplies the
|
|
174
|
+
// default and nobody notices; this endpoint forwards only what it is
|
|
175
|
+
// handed, so without this the same call dies on this wire alone.
|
|
176
|
+
if (!headerFields.some((h) => h.NAME.toLowerCase() === 'accept')) {
|
|
177
|
+
headerFields.push({ NAME: 'Accept', VALUE: '*/*' });
|
|
178
|
+
}
|
|
179
|
+
const body = request.data !== undefined && request.data !== null
|
|
180
|
+
? String(request.data)
|
|
181
|
+
: '';
|
|
182
|
+
if (body &&
|
|
183
|
+
!headerFields.some((h) => h.NAME.toLowerCase() === 'content-type')) {
|
|
184
|
+
headerFields.push({
|
|
185
|
+
NAME: 'Content-Type',
|
|
186
|
+
VALUE: 'text/plain; charset=utf-8',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
this.logger?.debug(`RFC → ${method} ${uri}`);
|
|
190
|
+
// `request.timeout` is deliberately not read, and the absence of the word
|
|
191
|
+
// here is what made that look like an oversight (#42).
|
|
192
|
+
//
|
|
193
|
+
// There is nothing to enforce it with: the SDK exposes no cancel, and an
|
|
194
|
+
// abandoned call still holds the conversation — the next one would queue
|
|
195
|
+
// behind a call nobody is waiting for. A deadline that reports failure
|
|
196
|
+
// while the wire stays busy is worse than none, because the error reads as
|
|
197
|
+
// "safe to retry" when it is not.
|
|
198
|
+
//
|
|
199
|
+
// Nor is one needed. What bounds this call is the server: a dialog step
|
|
200
|
+
// ends at `rdisp/max_wprun_time`. And a call that cannot be torn down
|
|
201
|
+
// mid-flight is precisely what the connection emulates over HTTP, where
|
|
202
|
+
// a critical section raises the caller's timeout to a ten-minute ceiling so
|
|
203
|
+
// an abort cannot orphan a lock handle. Here that comes free.
|
|
204
|
+
let raw;
|
|
205
|
+
try {
|
|
206
|
+
raw = await this.conversation.call('SADT_REST_RFC_ENDPOINT', {
|
|
207
|
+
REQUEST: {
|
|
208
|
+
REQUEST_LINE: {
|
|
209
|
+
METHOD: method,
|
|
210
|
+
URI: uri,
|
|
211
|
+
VERSION: 'HTTP/1.1',
|
|
212
|
+
},
|
|
213
|
+
HEADER_FIELDS: headerFields,
|
|
214
|
+
MESSAGE_BODY: body ? Buffer.from(body, 'utf-8') : Buffer.alloc(0),
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
// An RFC-level failure, not an answer: nothing came back to classify.
|
|
220
|
+
const msg = message(e);
|
|
221
|
+
this.logger?.error(`RFC call failed: ${msg}`);
|
|
222
|
+
throw new Error(`RFC call to SADT_REST_RFC_ENDPOINT failed: ${msg}`);
|
|
223
|
+
}
|
|
224
|
+
const answer = raw.RESPONSE ?? raw;
|
|
225
|
+
const rawCode = answer.STATUS_LINE?.STATUS_CODE ?? answer.STATUS_LINE?.CODE;
|
|
226
|
+
let status = typeof rawCode === 'string'
|
|
227
|
+
? Number.parseInt(rawCode, 10)
|
|
228
|
+
: (rawCode ?? 0);
|
|
229
|
+
let statusText = answer.STATUS_LINE?.REASON_PHRASE ?? answer.STATUS_LINE?.REASON ?? '';
|
|
230
|
+
const data = answer.MESSAGE_BODY
|
|
231
|
+
? Buffer.isBuffer(answer.MESSAGE_BODY)
|
|
232
|
+
? answer.MESSAGE_BODY.toString('utf-8')
|
|
233
|
+
: String(answer.MESSAGE_BODY)
|
|
234
|
+
: '';
|
|
235
|
+
const headers = {};
|
|
236
|
+
for (const field of answer.HEADER_FIELDS ?? []) {
|
|
237
|
+
if (field.NAME && field.VALUE !== undefined) {
|
|
238
|
+
headers[String(field.NAME).toLowerCase()] = field.VALUE;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// BASIS < 7.50 answers without populating STATUS_LINE. Left alone, a
|
|
242
|
+
// failure would read as 200 with an error document in the body — which is
|
|
243
|
+
// the green run that inspected nothing, one layer down.
|
|
244
|
+
if (!status && data.includes('<exc:exception')) {
|
|
245
|
+
const detected = statusFromExceptionXml(data);
|
|
246
|
+
status = detected.code;
|
|
247
|
+
statusText = detected.text;
|
|
248
|
+
this.logger?.debug(`RFC: STATUS_LINE empty, read ${status} out of the exception XML`);
|
|
249
|
+
}
|
|
250
|
+
if (!status) {
|
|
251
|
+
status = 200;
|
|
252
|
+
statusText = statusText || 'OK';
|
|
253
|
+
}
|
|
254
|
+
this.logger?.debug(`RFC ← ${status} ${statusText} (${data.length} bytes)`);
|
|
255
|
+
const response = {
|
|
256
|
+
status,
|
|
257
|
+
statusText,
|
|
258
|
+
headers,
|
|
259
|
+
data,
|
|
260
|
+
};
|
|
261
|
+
const admits = request.validateStatus ?? admits2xx;
|
|
262
|
+
if (!admits(status)) {
|
|
263
|
+
const error = new Error(`Request failed with status ${status}: ${method} ${uri}`);
|
|
264
|
+
error.response = response;
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
return response;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
exports.RfcTransport = RfcTransport;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An RFC conversation, from the config a consumer already holds.
|
|
3
|
+
*
|
|
4
|
+
* `RfcTransport` takes a factory rather than connection parameters, and that is
|
|
5
|
+
* deliberate: `@mcp-abap-adt/sap-rfc-lite` needs the SAP NW RFC SDK on the
|
|
6
|
+
* machine, and a transport that reached for it in its constructor could not be
|
|
7
|
+
* built at all where it is absent. But a factory is a poor front door — without
|
|
8
|
+
* this, reaching the RFC wire means every consumer writing the `require` and
|
|
9
|
+
* deriving `ashost` and `sysnr` for themselves, which is the derivation the
|
|
10
|
+
* per-transport connection class used to do for them.
|
|
11
|
+
*
|
|
12
|
+
* So the derivation lives here, once, and the SDK is still only touched when a
|
|
13
|
+
* conversation is actually opened.
|
|
14
|
+
*/
|
|
15
|
+
import type { SapConfig } from '../config/sapConfig.js';
|
|
16
|
+
import type { IRfcConversation } from './RfcTransport.js';
|
|
17
|
+
/** What the native client is constructed with. */
|
|
18
|
+
export interface RfcConnectionParams {
|
|
19
|
+
ashost: string;
|
|
20
|
+
sysnr: string;
|
|
21
|
+
client: string;
|
|
22
|
+
user: string;
|
|
23
|
+
passwd: string;
|
|
24
|
+
lang: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read out of the config, never asked of the server.
|
|
28
|
+
*
|
|
29
|
+
* The system number comes off the HTTP port by the SAP convention that 80XX is
|
|
30
|
+
* the ICM port for system XX — the convention the port itself follows, not a
|
|
31
|
+
* guess about the deployment. `SAP_SYSNR` overrides it for a port that follows
|
|
32
|
+
* no convention, which is the case on a system reached through 50400.
|
|
33
|
+
*/
|
|
34
|
+
export declare function rfcParamsFrom(config: SapConfig): RfcConnectionParams;
|
|
35
|
+
/**
|
|
36
|
+
* The factory `RfcTransport` asks for.
|
|
37
|
+
*
|
|
38
|
+
* The SDK is loaded when a conversation is opened, not when this is called, so
|
|
39
|
+
* a consumer can build the transport on a machine that has no SDK and find out
|
|
40
|
+
* at `connect()` rather than at construction — with a message that says what to
|
|
41
|
+
* install.
|
|
42
|
+
*/
|
|
43
|
+
export declare function rfcConversationFrom(config: SapConfig): () => IRfcConversation;
|
|
44
|
+
//# sourceMappingURL=rfcConversation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rfcConversation.d.ts","sourceRoot":"","sources":["../../src/connection/rfcConversation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,kDAAkD;AAClD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,mBAAmB,CAoBpE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,gBAAgB,CAmB7E"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* An RFC conversation, from the config a consumer already holds.
|
|
4
|
+
*
|
|
5
|
+
* `RfcTransport` takes a factory rather than connection parameters, and that is
|
|
6
|
+
* deliberate: `@mcp-abap-adt/sap-rfc-lite` needs the SAP NW RFC SDK on the
|
|
7
|
+
* machine, and a transport that reached for it in its constructor could not be
|
|
8
|
+
* built at all where it is absent. But a factory is a poor front door — without
|
|
9
|
+
* this, reaching the RFC wire means every consumer writing the `require` and
|
|
10
|
+
* deriving `ashost` and `sysnr` for themselves, which is the derivation the
|
|
11
|
+
* per-transport connection class used to do for them.
|
|
12
|
+
*
|
|
13
|
+
* So the derivation lives here, once, and the SDK is still only touched when a
|
|
14
|
+
* conversation is actually opened.
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.rfcParamsFrom = rfcParamsFrom;
|
|
18
|
+
exports.rfcConversationFrom = rfcConversationFrom;
|
|
19
|
+
/**
|
|
20
|
+
* Read out of the config, never asked of the server.
|
|
21
|
+
*
|
|
22
|
+
* The system number comes off the HTTP port by the SAP convention that 80XX is
|
|
23
|
+
* the ICM port for system XX — the convention the port itself follows, not a
|
|
24
|
+
* guess about the deployment. `SAP_SYSNR` overrides it for a port that follows
|
|
25
|
+
* no convention, which is the case on a system reached through 50400.
|
|
26
|
+
*/
|
|
27
|
+
function rfcParamsFrom(config) {
|
|
28
|
+
if (!config.url) {
|
|
29
|
+
throw new Error('An RFC conversation needs a url to take its host from');
|
|
30
|
+
}
|
|
31
|
+
if (!config.username || !config.password) {
|
|
32
|
+
throw new Error('An RFC conversation needs both a username and a password');
|
|
33
|
+
}
|
|
34
|
+
const parsed = new URL(config.url);
|
|
35
|
+
const port = Number.parseInt(parsed.port || '8000', 10);
|
|
36
|
+
const derived = String(port - 8000).padStart(2, '0');
|
|
37
|
+
return {
|
|
38
|
+
ashost: parsed.hostname,
|
|
39
|
+
sysnr: process.env.SAP_SYSNR?.trim() || derived,
|
|
40
|
+
client: config.client || '000',
|
|
41
|
+
user: config.username,
|
|
42
|
+
passwd: config.password,
|
|
43
|
+
lang: 'EN',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The factory `RfcTransport` asks for.
|
|
48
|
+
*
|
|
49
|
+
* The SDK is loaded when a conversation is opened, not when this is called, so
|
|
50
|
+
* a consumer can build the transport on a machine that has no SDK and find out
|
|
51
|
+
* at `connect()` rather than at construction — with a message that says what to
|
|
52
|
+
* install.
|
|
53
|
+
*/
|
|
54
|
+
function rfcConversationFrom(config) {
|
|
55
|
+
const params = rfcParamsFrom(config);
|
|
56
|
+
return () => {
|
|
57
|
+
let Client;
|
|
58
|
+
try {
|
|
59
|
+
// Dynamic, because the SDK is an optional peer: it needs the SAP NW RFC
|
|
60
|
+
// SDK installed on the machine, and most consumers travel over HTTP.
|
|
61
|
+
// biome-ignore lint/correctness/noNodejsModules: optional by design
|
|
62
|
+
Client = require('@mcp-abap-adt/sap-rfc-lite').Client;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
throw new Error('@mcp-abap-adt/sap-rfc-lite is not available. To take the RFC wire, ' +
|
|
66
|
+
'install the SAP NW RFC SDK and run: npm install @mcp-abap-adt/sap-rfc-lite. ' +
|
|
67
|
+
`Details: ${error instanceof Error ? error.message : String(error)}`);
|
|
68
|
+
}
|
|
69
|
+
return new Client(params);
|
|
70
|
+
};
|
|
71
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
export type { IWebSocketCloseInfo, IWebSocketConnectOptions, IWebSocketMessageEnvelope, IWebSocketMessageHandler, IWebSocketTransport, } from '@mcp-abap-adt/interfaces';
|
|
2
2
|
export { FileCertificateMaterialLoader } from './auth/FileCertificateMaterialLoader.js';
|
|
3
|
-
export type { IAuthProvider } from './auth/IAuthProvider.js';
|
|
4
3
|
export { BasicAuthProvider, CertificateAuthProvider, SamlAuthProvider, TokenAuthProvider, } from './auth/providers.js';
|
|
5
4
|
export type { SapAuthType, SapConfig, SapConnectionType, } from './config/sapConfig.js';
|
|
6
5
|
export { sapConfigSignature } from './config/sapConfig.js';
|
|
7
6
|
export type { AbapConnection, AbapRequestOptions, } from './connection/AbapConnection.js';
|
|
8
7
|
export { AdtCloudConnector } from './connection/AdtCloudConnector.js';
|
|
9
8
|
export { AdtOnPremConnector } from './connection/AdtOnPremConnector.js';
|
|
10
|
-
export {
|
|
11
|
-
export { CertificateAbapConnection } from './connection/CertificateAbapConnection.js';
|
|
12
|
-
export { createAbapConnection } from './connection/connectionFactory.js';
|
|
9
|
+
export { CloudHttpTransport } from './connection/CloudHttpTransport.js';
|
|
13
10
|
export { CSRF_CONFIG, CSRF_ERROR_MESSAGES } from './connection/csrfConfig.js';
|
|
14
11
|
export { GenericWebSocketTransport, type IWebSocketFactory, type IWebSocketLike, } from './connection/GenericWebSocketTransport.js';
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
12
|
+
export { HttpTransport } from './connection/HttpTransport.js';
|
|
13
|
+
export type { IAdtEstablishContext, IAdtSessionContext, IAdtTransport, IAdtTransportRequest, IAdtTransportResponse, ICloudTransport, IOnPremTransport, } from './connection/IAdtTransport.js';
|
|
14
|
+
export { LegacyOnPremHttpTransport } from './connection/LegacyOnPremHttpTransport.js';
|
|
15
|
+
export { OnPremHttpTransport } from './connection/OnPremHttpTransport.js';
|
|
16
|
+
export { type IRfcConversation, RfcTransport, } from './connection/RfcTransport.js';
|
|
17
|
+
export { type RfcConnectionParams, rfcConversationFrom, rfcParamsFrom, } from './connection/rfcConversation.js';
|
|
19
18
|
export type { ILogger } from './logger.js';
|
|
20
19
|
export { getTimeout, getTimeoutConfig, type TimeoutConfig, } from './utils/timeouts.js';
|
|
21
20
|
//# sourceMappingURL=index.d.ts.map
|
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;
|
|
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;AAKxF,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,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;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAQxE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EACL,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,2CAA2C,CAAC;AAEnD,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,YAAY,EACV,oBAAoB,EACpB,kBAAkB,EAClB,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,eAAe,EACf,gBAAgB,GACjB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EACL,KAAK,gBAAgB,EACrB,YAAY,GACb,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EACL,KAAK,mBAAmB,EACxB,mBAAmB,EACnB,aAAa,GACd,MAAM,iCAAiC,CAAC;AACzC,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
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// Types - re-exported from interfaces package with backward compatibility aliases
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.getTimeoutConfig = exports.getTimeout = exports.
|
|
4
|
+
exports.getTimeoutConfig = exports.getTimeout = exports.rfcParamsFrom = exports.rfcConversationFrom = exports.RfcTransport = exports.OnPremHttpTransport = exports.LegacyOnPremHttpTransport = exports.HttpTransport = exports.GenericWebSocketTransport = exports.CSRF_ERROR_MESSAGES = exports.CSRF_CONFIG = exports.CloudHttpTransport = exports.AdtOnPremConnector = exports.AdtCloudConnector = exports.sapConfigSignature = exports.TokenAuthProvider = exports.SamlAuthProvider = exports.CertificateAuthProvider = exports.BasicAuthProvider = exports.FileCertificateMaterialLoader = void 0;
|
|
5
5
|
var FileCertificateMaterialLoader_js_1 = require("./auth/FileCertificateMaterialLoader.js");
|
|
6
6
|
Object.defineProperty(exports, "FileCertificateMaterialLoader", { enumerable: true, get: function () { return FileCertificateMaterialLoader_js_1.FileCertificateMaterialLoader; } });
|
|
7
|
+
// IAuthProvider is deliberately NOT re-exported. It lives in
|
|
8
|
+
// @mcp-abap-adt/interfaces and a consumer imports it from there — the same rule
|
|
9
|
+
// the session-lifecycle vocabulary follows below, and for the same reason: two
|
|
10
|
+
// names for one contract let the two drift.
|
|
7
11
|
var providers_js_1 = require("./auth/providers.js");
|
|
8
12
|
Object.defineProperty(exports, "BasicAuthProvider", { enumerable: true, get: function () { return providers_js_1.BasicAuthProvider; } });
|
|
9
13
|
Object.defineProperty(exports, "CertificateAuthProvider", { enumerable: true, get: function () { return providers_js_1.CertificateAuthProvider; } });
|
|
@@ -16,35 +20,34 @@ var AdtCloudConnector_js_1 = require("./connection/AdtCloudConnector.js");
|
|
|
16
20
|
Object.defineProperty(exports, "AdtCloudConnector", { enumerable: true, get: function () { return AdtCloudConnector_js_1.AdtCloudConnector; } });
|
|
17
21
|
var AdtOnPremConnector_js_1 = require("./connection/AdtOnPremConnector.js");
|
|
18
22
|
Object.defineProperty(exports, "AdtOnPremConnector", { enumerable: true, get: function () { return AdtOnPremConnector_js_1.AdtOnPremConnector; } });
|
|
23
|
+
var CloudHttpTransport_js_1 = require("./connection/CloudHttpTransport.js");
|
|
24
|
+
Object.defineProperty(exports, "CloudHttpTransport", { enumerable: true, get: function () { return CloudHttpTransport_js_1.CloudHttpTransport; } });
|
|
19
25
|
// The session lifecycle vocabulary — ISessionLifecycleAware, ADT_SESSION_ERROR —
|
|
20
26
|
// is deliberately NOT exported here. It lives in @mcp-abap-adt/interfaces, and a
|
|
21
27
|
// consumer imports it from there: re-exporting a contract type gives it two
|
|
22
28
|
// names and lets the two drift.
|
|
23
29
|
// Connection classes - only final implementations
|
|
24
30
|
// Deprecated aliases for backward compatibility
|
|
25
|
-
var BaseAbapConnection_js_1 = require("./connection/BaseAbapConnection.js");
|
|
26
|
-
Object.defineProperty(exports, "BaseAbapConnection", { enumerable: true, get: function () { return BaseAbapConnection_js_1.BaseAbapConnection; } });
|
|
27
|
-
Object.defineProperty(exports, "OnPremAbapConnection", { enumerable: true, get: function () { return BaseAbapConnection_js_1.BaseAbapConnection; } });
|
|
28
|
-
var CertificateAbapConnection_js_1 = require("./connection/CertificateAbapConnection.js");
|
|
29
|
-
Object.defineProperty(exports, "CertificateAbapConnection", { enumerable: true, get: function () { return CertificateAbapConnection_js_1.CertificateAbapConnection; } });
|
|
30
|
-
// Factory
|
|
31
|
-
var connectionFactory_js_1 = require("./connection/connectionFactory.js");
|
|
32
|
-
Object.defineProperty(exports, "createAbapConnection", { enumerable: true, get: function () { return connectionFactory_js_1.createAbapConnection; } });
|
|
33
31
|
// CSRF configuration
|
|
34
32
|
var csrfConfig_js_1 = require("./connection/csrfConfig.js");
|
|
35
33
|
Object.defineProperty(exports, "CSRF_CONFIG", { enumerable: true, get: function () { return csrfConfig_js_1.CSRF_CONFIG; } });
|
|
36
34
|
Object.defineProperty(exports, "CSRF_ERROR_MESSAGES", { enumerable: true, get: function () { return csrfConfig_js_1.CSRF_ERROR_MESSAGES; } });
|
|
37
35
|
var GenericWebSocketTransport_js_1 = require("./connection/GenericWebSocketTransport.js");
|
|
38
36
|
Object.defineProperty(exports, "GenericWebSocketTransport", { enumerable: true, get: function () { return GenericWebSocketTransport_js_1.GenericWebSocketTransport; } });
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
Object.defineProperty(exports, "
|
|
42
|
-
var
|
|
43
|
-
Object.defineProperty(exports, "
|
|
44
|
-
var
|
|
45
|
-
Object.defineProperty(exports, "
|
|
46
|
-
var
|
|
47
|
-
Object.defineProperty(exports, "
|
|
37
|
+
// The transport axis. Both ends are objects, so a caller can name either.
|
|
38
|
+
var HttpTransport_js_1 = require("./connection/HttpTransport.js");
|
|
39
|
+
Object.defineProperty(exports, "HttpTransport", { enumerable: true, get: function () { return HttpTransport_js_1.HttpTransport; } });
|
|
40
|
+
var LegacyOnPremHttpTransport_js_1 = require("./connection/LegacyOnPremHttpTransport.js");
|
|
41
|
+
Object.defineProperty(exports, "LegacyOnPremHttpTransport", { enumerable: true, get: function () { return LegacyOnPremHttpTransport_js_1.LegacyOnPremHttpTransport; } });
|
|
42
|
+
var OnPremHttpTransport_js_1 = require("./connection/OnPremHttpTransport.js");
|
|
43
|
+
Object.defineProperty(exports, "OnPremHttpTransport", { enumerable: true, get: function () { return OnPremHttpTransport_js_1.OnPremHttpTransport; } });
|
|
44
|
+
var RfcTransport_js_1 = require("./connection/RfcTransport.js");
|
|
45
|
+
Object.defineProperty(exports, "RfcTransport", { enumerable: true, get: function () { return RfcTransport_js_1.RfcTransport; } });
|
|
46
|
+
// The front door to the RFC wire: the derivation a consumer would otherwise
|
|
47
|
+
// copy, and the SDK loaded only when a conversation is opened.
|
|
48
|
+
var rfcConversation_js_1 = require("./connection/rfcConversation.js");
|
|
49
|
+
Object.defineProperty(exports, "rfcConversationFrom", { enumerable: true, get: function () { return rfcConversation_js_1.rfcConversationFrom; } });
|
|
50
|
+
Object.defineProperty(exports, "rfcParamsFrom", { enumerable: true, get: function () { return rfcConversation_js_1.rfcParamsFrom; } });
|
|
48
51
|
// Timeouts
|
|
49
52
|
var timeouts_js_1 = require("./utils/timeouts.js");
|
|
50
53
|
Object.defineProperty(exports, "getTimeout", { enumerable: true, get: function () { return timeouts_js_1.getTimeout; } });
|
package/dist/utils/timeouts.d.ts
CHANGED
|
@@ -5,27 +5,14 @@ export declare function getTimeout(type?: 'default' | 'csrf' | 'long' | number):
|
|
|
5
5
|
/**
|
|
6
6
|
* Large ceiling timeout applied while a connection is inside an uninterruptible
|
|
7
7
|
* critical section (lock → modify → unlock). A short per-request timeout must
|
|
8
|
-
* not abort such a request mid-flight
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* not abort such a request mid-flight — not because aborting ends anything on
|
|
9
|
+
* the server, but because it ends what the CLIENT knows: the modification may
|
|
10
|
+
* or may not have been applied, and the handle `unlock` needs is gone, while
|
|
11
|
+
* the lock and the ABAP session holding it sit there until that server-side
|
|
12
|
+
* idle timeout, which this side can neither read nor influence.
|
|
13
|
+
* Configurable via SAP_TIMEOUT_CRITICAL (ms).
|
|
11
14
|
* Default 600000 (10 minutes) — a generous ceiling that still guards against a
|
|
12
15
|
* permanently dead socket hanging forever.
|
|
13
16
|
*/
|
|
14
17
|
export declare function getCriticalSectionTimeout(): number;
|
|
15
|
-
/**
|
|
16
|
-
* How long `disconnect()` may spend telling the server the session is done.
|
|
17
|
-
*
|
|
18
|
-
* **Zero by default: after a disconnect nothing waits on the answer.** Waiting
|
|
19
|
-
* is for steps whose successor depends on the server having caught up — lock,
|
|
20
|
-
* update, unlock, activate, each needing the one before it to have landed.
|
|
21
|
-
* A teardown has no successor: the session is marked unneeded, the server
|
|
22
|
-
* reclaims it whenever it reclaims it, and a caller blocked on that round trip
|
|
23
|
-
* has bought nothing while holding up the serializing tail, where every later
|
|
24
|
-
* connect and disconnect queues behind it.
|
|
25
|
-
*
|
|
26
|
-
* The knob exists for a caller that wants a bounded wait anyway — a test
|
|
27
|
-
* asserting the logoff landed, a script that would rather see the failure —
|
|
28
|
-
* and is theirs to set per call via `ISessionLifecycleAware.disconnect`.
|
|
29
|
-
*/
|
|
30
|
-
export declare function getReleaseDeadline(): number;
|
|
31
18
|
//# sourceMappingURL=timeouts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeouts.d.ts","sourceRoot":"","sources":["../../src/utils/timeouts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG/D,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC;AAE3C,wBAAgB,gBAAgB,IAAI,cAAc,CAajD;AAED,wBAAgB,UAAU,CACxB,IAAI,GAAE,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAkB,GACrD,MAAM,CAOR;AAED
|
|
1
|
+
{"version":3,"file":"timeouts.d.ts","sourceRoot":"","sources":["../../src/utils/timeouts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG/D,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC;AAE3C,wBAAgB,gBAAgB,IAAI,cAAc,CAajD;AAED,wBAAgB,UAAU,CACxB,IAAI,GAAE,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAkB,GACrD,MAAM,CAOR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAElD"}
|
package/dist/utils/timeouts.js
CHANGED
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.getTimeoutConfig = getTimeoutConfig;
|
|
4
4
|
exports.getTimeout = getTimeout;
|
|
5
5
|
exports.getCriticalSectionTimeout = getCriticalSectionTimeout;
|
|
6
|
-
exports.getReleaseDeadline = getReleaseDeadline;
|
|
7
6
|
function getTimeoutConfig() {
|
|
8
7
|
const defaultTimeout = parseInt(process.env.SAP_TIMEOUT_DEFAULT || '45000', 10);
|
|
9
8
|
const csrfTimeout = parseInt(process.env.SAP_TIMEOUT_CSRF || '15000', 10);
|
|
@@ -24,30 +23,15 @@ function getTimeout(type = 'default') {
|
|
|
24
23
|
/**
|
|
25
24
|
* Large ceiling timeout applied while a connection is inside an uninterruptible
|
|
26
25
|
* critical section (lock → modify → unlock). A short per-request timeout must
|
|
27
|
-
* not abort such a request mid-flight
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* not abort such a request mid-flight — not because aborting ends anything on
|
|
27
|
+
* the server, but because it ends what the CLIENT knows: the modification may
|
|
28
|
+
* or may not have been applied, and the handle `unlock` needs is gone, while
|
|
29
|
+
* the lock and the ABAP session holding it sit there until that server-side
|
|
30
|
+
* idle timeout, which this side can neither read nor influence.
|
|
31
|
+
* Configurable via SAP_TIMEOUT_CRITICAL (ms).
|
|
30
32
|
* Default 600000 (10 minutes) — a generous ceiling that still guards against a
|
|
31
33
|
* permanently dead socket hanging forever.
|
|
32
34
|
*/
|
|
33
35
|
function getCriticalSectionTimeout() {
|
|
34
36
|
return parseInt(process.env.SAP_TIMEOUT_CRITICAL || '600000', 10);
|
|
35
37
|
}
|
|
36
|
-
/**
|
|
37
|
-
* How long `disconnect()` may spend telling the server the session is done.
|
|
38
|
-
*
|
|
39
|
-
* **Zero by default: after a disconnect nothing waits on the answer.** Waiting
|
|
40
|
-
* is for steps whose successor depends on the server having caught up — lock,
|
|
41
|
-
* update, unlock, activate, each needing the one before it to have landed.
|
|
42
|
-
* A teardown has no successor: the session is marked unneeded, the server
|
|
43
|
-
* reclaims it whenever it reclaims it, and a caller blocked on that round trip
|
|
44
|
-
* has bought nothing while holding up the serializing tail, where every later
|
|
45
|
-
* connect and disconnect queues behind it.
|
|
46
|
-
*
|
|
47
|
-
* The knob exists for a caller that wants a bounded wait anyway — a test
|
|
48
|
-
* asserting the logoff landed, a script that would rather see the failure —
|
|
49
|
-
* and is theirs to set per call via `ISessionLifecycleAware.disconnect`.
|
|
50
|
-
*/
|
|
51
|
-
function getReleaseDeadline() {
|
|
52
|
-
return parseInt(process.env.SAP_RELEASE_DEADLINE_MS || '0', 10);
|
|
53
|
-
}
|
package/docs/INDEX.md
CHANGED
|
@@ -15,6 +15,7 @@ mcp-abap-connection/
|
|
|
15
15
|
│ ├── INSTALLATION.md # Setup and installation guide
|
|
16
16
|
│ ├── USAGE.md # API documentation and examples
|
|
17
17
|
│ ├── MIGRATION-2.0.md # Moving to the explicit session lifecycle
|
|
18
|
+
│ ├── MIGRATION-6.0.md # the factory and the per-credential classes go; RFC is a transport
|
|
18
19
|
│ ├── MIGRATION-4.0.md # JWT error classification: 401 refreshes, 403 propagates
|
|
19
20
|
│ ├── SCOPE.md # What this package does and does not own
|
|
20
21
|
│ ├── STATEFUL_SESSION_GUIDE.md # Stateful requests and lock windows
|
|
@@ -43,6 +44,7 @@ mcp-abap-connection/
|
|
|
43
44
|
- 🔑 [JWT Auth Tools](./JWT_AUTH_TOOLS.md) - CLI tool for browser-based authentication
|
|
44
45
|
|
|
45
46
|
### Upgrading
|
|
47
|
+
- 🚚 [Migrating to 6.0.0](./MIGRATION-6.0.md) - the factory and the per-credential classes are removed; the RFC wire is a transport you hand to the on-prem connector
|
|
46
48
|
- 🧱 [Migrating to 4.0.0](./MIGRATION-4.0.md) - JWT error classification: a 401 refreshes, a 403 propagates with the server's message
|
|
47
49
|
- 🧱 [Migrating to 2.0.0](./MIGRATION-2.0.md) - The explicit session lifecycle: `connect()` is required
|
|
48
50
|
|
|
@@ -72,8 +74,9 @@ mcp-abap-connection/
|
|
|
72
74
|
### API Reference
|
|
73
75
|
- **Connection Interface**: [USAGE.md - API Reference](./USAGE.md#api-reference)
|
|
74
76
|
- **Configuration Types**: [USAGE.md - Configuration Types](./USAGE.md#configuration-types)
|
|
75
|
-
- **
|
|
76
|
-
- **
|
|
77
|
+
- **Connectors**: `AdtOnPremConnector`, `AdtCloudConnector`
|
|
78
|
+
- **Credentials**: `BasicAuthProvider`, `TokenAuthProvider`, `SamlAuthProvider`, `CertificateAuthProvider`
|
|
79
|
+
- **Transports**: `HttpTransport`, `RfcTransport` (+ `rfcConversationFrom()`)
|
|
77
80
|
|
|
78
81
|
## Version Highlights
|
|
79
82
|
|