@mcp-abap-adt/connection 1.10.2 → 3.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 +827 -0
- package/README.md +44 -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 +180 -12
- package/dist/connection/AbstractAbapConnection.d.ts.map +1 -1
- package/dist/connection/AbstractAbapConnection.js +398 -19
- 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 +50 -70
- package/dist/session/SessionLifecycle.d.ts.map +1 -1
- package/dist/session/SessionLifecycle.js +59 -158
- 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 +114 -0
- package/docs/SCOPE.md +44 -0
- package/docs/STATEFUL_SESSION_GUIDE.md +122 -0
- package/docs/USAGE.md +745 -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
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Owns the session lifecycle: what state the session is in, who may use it, and
|
|
4
|
-
* when it
|
|
4
|
+
* when it stops being current.
|
|
5
5
|
*
|
|
6
6
|
* Deliberately knows nothing about SAP, HTTP, RFC, cookies or ADT. Identities
|
|
7
|
-
* are opaque maps of name → value that someone else computed
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* are opaque maps of name → value that someone else computed. That is what makes
|
|
8
|
+
* this unit testable without a server, and it is where the hard part lives:
|
|
9
|
+
* ordering, admission, and telling one session from the next.
|
|
10
10
|
*
|
|
11
|
-
* Design: docs/superpowers/specs/2026-07-
|
|
11
|
+
* Design: docs/superpowers/specs/2026-07-31-teardown-policy-design.md in
|
|
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;
|
|
@@ -28,28 +24,14 @@ class SessionLifecycle {
|
|
|
28
24
|
state = 'disconnected';
|
|
29
25
|
fingerprint = new Map();
|
|
30
26
|
epoch = 0;
|
|
27
|
+
generation = 0;
|
|
31
28
|
teardownPending = false;
|
|
32
|
-
teardownLostSession = false;
|
|
33
|
-
teardownAt = null;
|
|
34
|
-
/** Set at expiry: admission is shut regardless of open windows. */
|
|
35
|
-
admissionForcedShut = false;
|
|
36
|
-
windows = new Map();
|
|
37
29
|
inFlight = 0;
|
|
38
30
|
tail = Promise.resolve();
|
|
39
31
|
tailKind = null;
|
|
40
32
|
tailPromise = null;
|
|
41
|
-
waiters = [];
|
|
42
|
-
ceilingMs;
|
|
43
|
-
now;
|
|
44
|
-
constructor(options = {}) {
|
|
45
|
-
this.ceilingMs = options.ceilingMs ?? 600_000;
|
|
46
|
-
this.now = options.now ?? (() => Date.now());
|
|
47
|
-
}
|
|
48
33
|
// ---------------------------------------------------------------- state ---
|
|
49
|
-
/**
|
|
50
|
-
* Whether a caller may start work. False throughout a teardown, including
|
|
51
|
-
* while a grandfathered window is still finishing: finishing is not starting.
|
|
52
|
-
*/
|
|
34
|
+
/** Whether a caller may start work. False throughout a pending teardown. */
|
|
53
35
|
get connected() {
|
|
54
36
|
return this.state === 'connected' && !this.teardownPending;
|
|
55
37
|
}
|
|
@@ -65,42 +47,55 @@ class SessionLifecycle {
|
|
|
65
47
|
get teardownEpoch() {
|
|
66
48
|
return this.epoch;
|
|
67
49
|
}
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Which session is current, counted from zero.
|
|
52
|
+
*
|
|
53
|
+
* Two counters answer two different questions, and one cannot do both:
|
|
54
|
+
*
|
|
55
|
+
* - `epoch` — did the CALLER ask to stop? It moves only on a caller-initiated
|
|
56
|
+
* teardown, because a recovery must not cancel itself.
|
|
57
|
+
* - `generation` — is this still the session you were issued against? It moves
|
|
58
|
+
* on every change of which session is current, however caused.
|
|
59
|
+
*
|
|
60
|
+
* A fence built on `epoch` misses every internal teardown: after a session
|
|
61
|
+
* loss and a successful recovery, requests from the dead session carry the
|
|
62
|
+
* same epoch as the new one and sail straight through.
|
|
63
|
+
*/
|
|
64
|
+
get sessionGeneration() {
|
|
65
|
+
return this.generation;
|
|
71
66
|
}
|
|
72
67
|
/**
|
|
73
68
|
* Publishes a freshly established session, which also ENDS any teardown that
|
|
74
69
|
* was pending: the flags describe the session being torn down, and this is a
|
|
75
70
|
* different one. Without this, `connect()` after `disconnect()` — which the
|
|
76
71
|
* design allows explicitly — leaves the lifecycle permanently unusable.
|
|
77
|
-
*
|
|
78
|
-
* Windows are dropped for the same reason: they belonged to the old session,
|
|
79
|
-
* nothing here can close them, and a teardown that gave up on them has
|
|
80
|
-
* already carried their labels out in its report. Keeping them would make
|
|
81
|
-
* every future drain re-report locks from a session that no longer exists.
|
|
82
72
|
*/
|
|
83
73
|
markConnected(fingerprint = new Map()) {
|
|
84
74
|
this.state = 'connected';
|
|
85
75
|
this.fingerprint = new Map(fingerprint);
|
|
86
76
|
this.teardownPending = false;
|
|
87
|
-
this.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
77
|
+
this.generation += 1;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Drops the tracked identity without touching state or generation.
|
|
81
|
+
*
|
|
82
|
+
* For a session the caller discarded ON PURPOSE — a credential renewal, a
|
|
83
|
+
* cache invalidation. The next fingerprint then reads as `established` rather
|
|
84
|
+
* than `replaced`, which is the truth: nothing was taken from us.
|
|
85
|
+
*/
|
|
86
|
+
forgetIdentity() {
|
|
87
|
+
this.fingerprint.clear();
|
|
92
88
|
}
|
|
93
89
|
markDisconnected() {
|
|
94
90
|
this.state = 'disconnected';
|
|
95
91
|
this.fingerprint.clear();
|
|
96
|
-
this.wake();
|
|
97
92
|
}
|
|
98
93
|
/**
|
|
99
94
|
* Classifies a freshly observed fingerprint.
|
|
100
95
|
*
|
|
101
96
|
* Additive: a name appearing where none was tracked is `established`, never
|
|
102
97
|
* `replaced` — otherwise the identifier a LOCK response adds would read as a
|
|
103
|
-
* new session and
|
|
98
|
+
* new session and condemn the operation it just covered.
|
|
104
99
|
*/
|
|
105
100
|
observe(fingerprint) {
|
|
106
101
|
let established = false;
|
|
@@ -118,23 +113,9 @@ class SessionLifecycle {
|
|
|
118
113
|
return established ? 'established' : 'unchanged';
|
|
119
114
|
}
|
|
120
115
|
// ------------------------------------------------------------ admission ---
|
|
121
|
-
/** True while an already-open window is allowed to finish its work. */
|
|
122
|
-
get finishingWindowOpen() {
|
|
123
|
-
return (this.teardownPending &&
|
|
124
|
-
!this.teardownLostSession &&
|
|
125
|
-
!this.admissionForcedShut &&
|
|
126
|
-
this.windows.size > 0);
|
|
127
|
-
}
|
|
128
|
-
get admits() {
|
|
129
|
-
if (this.state !== 'connected')
|
|
130
|
-
return false;
|
|
131
|
-
if (!this.teardownPending)
|
|
132
|
-
return true;
|
|
133
|
-
return this.finishingWindowOpen;
|
|
134
|
-
}
|
|
135
116
|
assertUsable() {
|
|
136
|
-
if (
|
|
137
|
-
throw sessionError(
|
|
117
|
+
if (this.state !== 'connected' || this.teardownPending) {
|
|
118
|
+
throw sessionError(interfaces_1.ADT_SESSION_ERROR.NOT_CONNECTED);
|
|
138
119
|
}
|
|
139
120
|
}
|
|
140
121
|
/** Asserts usability and counts the request in, in one synchronous step. */
|
|
@@ -142,107 +123,52 @@ class SessionLifecycle {
|
|
|
142
123
|
this.assertUsable();
|
|
143
124
|
this.inFlight += 1;
|
|
144
125
|
const epoch = this.epoch;
|
|
126
|
+
const generation = this.generation;
|
|
145
127
|
let released = false;
|
|
146
128
|
return {
|
|
147
129
|
epoch,
|
|
130
|
+
generation,
|
|
148
131
|
release: () => {
|
|
149
132
|
if (released)
|
|
150
133
|
return;
|
|
151
134
|
released = true;
|
|
152
135
|
this.inFlight -= 1;
|
|
153
|
-
this.wake();
|
|
154
136
|
},
|
|
155
137
|
};
|
|
156
138
|
}
|
|
157
|
-
|
|
139
|
+
/** How many admitted requests have not settled. Diagnostics only — nothing waits on it. */
|
|
140
|
+
get requestsInFlight() {
|
|
141
|
+
return this.inFlight;
|
|
142
|
+
}
|
|
158
143
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
144
|
+
* Whether a lease may still touch shared state.
|
|
145
|
+
*
|
|
146
|
+
* A request outliving its session is ordinary now that a teardown does not
|
|
147
|
+
* wait: its response must not write cookies over a newer session's, and must
|
|
148
|
+
* not be read as a replacement — which would raise a session-lost teardown
|
|
149
|
+
* against a session that is perfectly healthy.
|
|
161
150
|
*/
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
throw sessionError(exports.ADT_SESSION_ERROR.NOT_CONNECTED);
|
|
165
|
-
}
|
|
166
|
-
const token = Symbol(label);
|
|
167
|
-
this.windows.set(token, { label, abandoned: false });
|
|
168
|
-
return token;
|
|
169
|
-
}
|
|
170
|
-
/** A token matching no open window is ignored: double close, foreign token. */
|
|
171
|
-
endWindow(token) {
|
|
172
|
-
if (!this.windows.delete(token))
|
|
173
|
-
return;
|
|
174
|
-
this.wake();
|
|
151
|
+
isCurrent(lease) {
|
|
152
|
+
return lease.generation === this.generation;
|
|
175
153
|
}
|
|
176
154
|
// ------------------------------------------------------------- teardown ---
|
|
177
155
|
beginTeardown({ origin, sessionLost }) {
|
|
178
|
-
if (!this.teardownPending) {
|
|
179
|
-
this.teardownAt = this.now();
|
|
180
|
-
}
|
|
181
156
|
this.teardownPending = true;
|
|
182
157
|
if (origin === 'caller') {
|
|
183
158
|
this.epoch += 1;
|
|
184
159
|
}
|
|
160
|
+
// The moment a session stops being current is the moment leases against it
|
|
161
|
+
// go stale — whether or not a replacement exists yet. Counting only at
|
|
162
|
+
// markConnected() leaves the gap between a teardown and the next connect,
|
|
163
|
+
// where a late response would write into state just cleared.
|
|
164
|
+
this.generation += 1;
|
|
185
165
|
if (sessionLost) {
|
|
186
|
-
this.teardownLostSession = true;
|
|
187
|
-
// Nothing can finish over a session that is gone: give up on every window
|
|
188
|
-
// now, and drop the identity so a later comparison sees the change.
|
|
189
|
-
for (const entry of this.windows.values())
|
|
190
|
-
entry.abandoned = true;
|
|
191
166
|
this.fingerprint.clear();
|
|
192
167
|
}
|
|
193
|
-
this.wake();
|
|
194
168
|
}
|
|
195
169
|
get teardownRequested() {
|
|
196
170
|
return this.teardownPending;
|
|
197
171
|
}
|
|
198
|
-
/**
|
|
199
|
-
* Resolves when nothing is in flight and no window is still worth waiting
|
|
200
|
-
* for. Bounded by the ceiling measured from the teardown request — absolute,
|
|
201
|
-
* never extended by request activity.
|
|
202
|
-
*
|
|
203
|
-
* On expiry, before resolving and without yielding in between: shuts
|
|
204
|
-
* admission, gives up on the remaining windows, then waits once more for the
|
|
205
|
-
* already-admitted requests to settle.
|
|
206
|
-
*/
|
|
207
|
-
async drain() {
|
|
208
|
-
// `??`, not `||`: a teardown requested at timestamp 0 is a real teardown,
|
|
209
|
-
// and treating it as absent would restart the ceiling from drain() — making
|
|
210
|
-
// the deadline relative to the wrong event, which is the sliding behaviour
|
|
211
|
-
// this design rejects.
|
|
212
|
-
const deadline = (this.teardownAt ?? this.now()) + this.ceilingMs;
|
|
213
|
-
while (this.inFlight > 0 || this.liveWindows > 0) {
|
|
214
|
-
const remaining = deadline - this.now();
|
|
215
|
-
if (remaining <= 0)
|
|
216
|
-
break;
|
|
217
|
-
await this.changed(remaining);
|
|
218
|
-
}
|
|
219
|
-
if (this.inFlight === 0 && this.liveWindows === 0) {
|
|
220
|
-
return { abandonedWindows: this.abandonedLabels() };
|
|
221
|
-
}
|
|
222
|
-
// Expiry. Synchronous, before any await: no request may enter after this.
|
|
223
|
-
this.admissionForcedShut = true;
|
|
224
|
-
for (const entry of this.windows.values())
|
|
225
|
-
entry.abandoned = true;
|
|
226
|
-
while (this.inFlight > 0) {
|
|
227
|
-
await this.changed();
|
|
228
|
-
}
|
|
229
|
-
return { abandonedWindows: this.abandonedLabels() };
|
|
230
|
-
}
|
|
231
|
-
get liveWindows() {
|
|
232
|
-
let live = 0;
|
|
233
|
-
for (const entry of this.windows.values())
|
|
234
|
-
if (!entry.abandoned)
|
|
235
|
-
live += 1;
|
|
236
|
-
return live;
|
|
237
|
-
}
|
|
238
|
-
abandonedLabels() {
|
|
239
|
-
const labels = new Set();
|
|
240
|
-
for (const entry of this.windows.values()) {
|
|
241
|
-
if (entry.abandoned)
|
|
242
|
-
labels.add(entry.label);
|
|
243
|
-
}
|
|
244
|
-
return [...labels];
|
|
245
|
-
}
|
|
246
172
|
// ---------------------------------------------------------- transitions ---
|
|
247
173
|
/**
|
|
248
174
|
* Runs a transition on the serializing tail.
|
|
@@ -252,8 +178,7 @@ class SessionLifecycle {
|
|
|
252
178
|
* when nothing is queued behind it, so a join can never overtake a queued
|
|
253
179
|
* transition of another kind. `recover` and `cleanup` never join and are
|
|
254
180
|
* never joined: a recovery carries its own request's baseline, and an
|
|
255
|
-
* internal cleanup owes its result to nobody
|
|
256
|
-
* report.
|
|
181
|
+
* internal cleanup owes its result to nobody.
|
|
257
182
|
*/
|
|
258
183
|
transition(kind, run) {
|
|
259
184
|
const joinable = kind === 'connect' || kind === 'disconnect';
|
|
@@ -273,29 +198,5 @@ class SessionLifecycle {
|
|
|
273
198
|
queued.then(settle, settle);
|
|
274
199
|
return queued;
|
|
275
200
|
}
|
|
276
|
-
// ----------------------------------------------------------- internals ---
|
|
277
|
-
wake() {
|
|
278
|
-
const waiters = this.waiters;
|
|
279
|
-
this.waiters = [];
|
|
280
|
-
for (const resolve of waiters)
|
|
281
|
-
resolve();
|
|
282
|
-
}
|
|
283
|
-
changed(timeoutMs) {
|
|
284
|
-
return new Promise((resolve) => {
|
|
285
|
-
let done = false;
|
|
286
|
-
const finish = () => {
|
|
287
|
-
if (done)
|
|
288
|
-
return;
|
|
289
|
-
done = true;
|
|
290
|
-
resolve();
|
|
291
|
-
};
|
|
292
|
-
this.waiters.push(finish);
|
|
293
|
-
if (timeoutMs !== undefined) {
|
|
294
|
-
const timer = setTimeout(finish, timeoutMs);
|
|
295
|
-
if (typeof timer.unref === 'function')
|
|
296
|
-
timer.unref();
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
201
|
}
|
|
301
202
|
exports.SessionLifecycle = SessionLifecycle;
|
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
|