@mcp-abap-adt/auth-broker 4.0.0 → 5.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 +273 -1
- package/README.md +1028 -908
- package/dist/AuthBroker.d.ts +353 -99
- package/dist/AuthBroker.d.ts.map +1 -1
- package/dist/AuthBroker.js +806 -258
- package/dist/DestinationConfigError.d.ts +35 -6
- package/dist/DestinationConfigError.d.ts.map +1 -1
- package/dist/DestinationConfigError.js +51 -9
- package/dist/SessionWriter.d.ts +70 -33
- package/dist/SessionWriter.d.ts.map +1 -1
- package/dist/SessionWriter.js +118 -94
- package/dist/binding.d.ts +137 -64
- package/dist/binding.d.ts.map +1 -1
- package/dist/binding.js +330 -131
- package/dist/bindingOf.d.ts +33 -14
- package/dist/bindingOf.d.ts.map +1 -1
- package/dist/bindingOf.js +50 -20
- package/dist/buildIdentity.d.ts +137 -0
- package/dist/buildIdentity.d.ts.map +1 -0
- package/dist/buildIdentity.js +393 -0
- package/dist/clientAuthentication.d.ts +124 -0
- package/dist/clientAuthentication.d.ts.map +1 -0
- package/dist/clientAuthentication.js +218 -0
- package/dist/contractShape.d.ts +24 -0
- package/dist/contractShape.d.ts.map +1 -0
- package/dist/contractShape.js +24 -0
- package/dist/destinations.d.ts +174 -53
- package/dist/destinations.d.ts.map +1 -1
- package/dist/destinations.js +460 -150
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/quietLogger.d.ts +24 -0
- package/dist/quietLogger.d.ts.map +1 -0
- package/dist/quietLogger.js +63 -0
- package/package.json +9 -7
|
@@ -7,21 +7,50 @@
|
|
|
7
7
|
*
|
|
8
8
|
* **It carries names, never values.** `missingFields` holds store field names
|
|
9
9
|
* (`authType`, `username`, `sncQop`) or broker option names
|
|
10
|
-
* (`serviceKeyStore`, `provider`); the message is fixed wording plus
|
|
11
|
-
* destination's name and those names. A stored value — a password, a
|
|
12
|
-
* even an `authType` that is not one of the four — never reaches it.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* (`serviceKeyStore`, `provider`, `renewal`); the message is fixed wording plus
|
|
11
|
+
* the destination's name and those names. A stored value — a password, a
|
|
12
|
+
* token, even an `authType` that is not one of the four — never reaches it.
|
|
13
|
+
*
|
|
14
|
+
* **When a provider or a strategy caused the refusal, its error is carried**
|
|
15
|
+
* as auth-errors read it (`readFailure`): `error` — kind, facts, the words
|
|
16
|
+
* auth-errors rendered and the diagnostics it admitted, nothing more. There is
|
|
17
|
+
* no `cause`: no message of any thrown value reaches the broker's error. The
|
|
18
|
+
* message then ends with `: <error.reason>`, the rendered words, never copied.
|
|
15
19
|
*/
|
|
20
|
+
import type { IAuthProviderError } from '@mcp-abap-adt/interfaces-auth';
|
|
16
21
|
export declare class DestinationConfigError extends Error {
|
|
17
22
|
readonly code: "DESTINATION_CONFIG";
|
|
18
23
|
readonly destination: string;
|
|
19
24
|
readonly missingFields: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Present when the refusal was caused by a provider's or a strategy's
|
|
27
|
+
* failure: that error as auth-errors read it (`readFailure`), its
|
|
28
|
+
* diagnostics kept when it came from this copy of auth-errors. An own
|
|
29
|
+
* property only when present.
|
|
30
|
+
*/
|
|
31
|
+
readonly error?: IAuthProviderError | undefined;
|
|
20
32
|
/**
|
|
21
33
|
* @param destination The destination's name, as the caller passed it.
|
|
22
34
|
* @param missingFields Field or option names only.
|
|
23
35
|
* @param reason Fixed wording: what is wrong, never a stored value.
|
|
36
|
+
* @param error The provider's or strategy's error, read by `readFailure`.
|
|
24
37
|
*/
|
|
25
|
-
constructor(destination: string, missingFields: string[], reason: string);
|
|
38
|
+
constructor(destination: string, missingFields: string[], reason: string, error?: IAuthProviderError);
|
|
26
39
|
}
|
|
40
|
+
/** What `isDestinationConfigError` vouches for: the fields, by structure. */
|
|
41
|
+
export interface DestinationConfigErrorLike {
|
|
42
|
+
readonly name: 'DestinationConfigError';
|
|
43
|
+
readonly code: 'DESTINATION_CONFIG';
|
|
44
|
+
readonly destination: string;
|
|
45
|
+
readonly missingFields: readonly string[];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Whether a value is a `DestinationConfigError` — or its JSON copy, or one
|
|
49
|
+
* of another installed copy of this package — by structure only: own data
|
|
50
|
+
* properties `name === 'DestinationConfigError'`, `code === 'DESTINATION_CONFIG'`,
|
|
51
|
+
* a string `destination` and an array of strings `missingFields`. No
|
|
52
|
+
* `instanceof`, no getter invoked; total — a Proxy whose traps throw answers
|
|
53
|
+
* false.
|
|
54
|
+
*/
|
|
55
|
+
export declare function isDestinationConfigError(value: unknown): value is DestinationConfigErrorLike;
|
|
27
56
|
//# sourceMappingURL=DestinationConfigError.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DestinationConfigError.d.ts","sourceRoot":"","sources":["../src/DestinationConfigError.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"DestinationConfigError.d.ts","sourceRoot":"","sources":["../src/DestinationConfigError.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAExE,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,IAAI,EAAG,oBAAoB,CAAU;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IACjC;;;;;OAKG;IACH,SAAiB,KAAK,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAExD;;;;;OAKG;gBAED,WAAW,EAAE,MAAM,EACnB,aAAa,EAAE,MAAM,EAAE,EACvB,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,kBAAkB;CAY7B;AAED,6EAA6E;AAC7E,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,wBAAwB,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAUD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,0BAA0B,CAcrC"}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DestinationConfigError = void 0;
|
|
4
2
|
/**
|
|
5
3
|
* A destination that lacks what its type needs.
|
|
6
4
|
*
|
|
@@ -10,12 +8,19 @@ exports.DestinationConfigError = void 0;
|
|
|
10
8
|
*
|
|
11
9
|
* **It carries names, never values.** `missingFields` holds store field names
|
|
12
10
|
* (`authType`, `username`, `sncQop`) or broker option names
|
|
13
|
-
* (`serviceKeyStore`, `provider`); the message is fixed wording plus
|
|
14
|
-
* destination's name and those names. A stored value — a password, a
|
|
15
|
-
* even an `authType` that is not one of the four — never reaches it.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
11
|
+
* (`serviceKeyStore`, `provider`, `renewal`); the message is fixed wording plus
|
|
12
|
+
* the destination's name and those names. A stored value — a password, a
|
|
13
|
+
* token, even an `authType` that is not one of the four — never reaches it.
|
|
14
|
+
*
|
|
15
|
+
* **When a provider or a strategy caused the refusal, its error is carried**
|
|
16
|
+
* as auth-errors read it (`readFailure`): `error` — kind, facts, the words
|
|
17
|
+
* auth-errors rendered and the diagnostics it admitted, nothing more. There is
|
|
18
|
+
* no `cause`: no message of any thrown value reaches the broker's error. The
|
|
19
|
+
* message then ends with `: <error.reason>`, the rendered words, never copied.
|
|
18
20
|
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.DestinationConfigError = void 0;
|
|
23
|
+
exports.isDestinationConfigError = isDestinationConfigError;
|
|
19
24
|
class DestinationConfigError extends Error {
|
|
20
25
|
code = 'DESTINATION_CONFIG';
|
|
21
26
|
destination;
|
|
@@ -24,13 +29,50 @@ class DestinationConfigError extends Error {
|
|
|
24
29
|
* @param destination The destination's name, as the caller passed it.
|
|
25
30
|
* @param missingFields Field or option names only.
|
|
26
31
|
* @param reason Fixed wording: what is wrong, never a stored value.
|
|
32
|
+
* @param error The provider's or strategy's error, read by `readFailure`.
|
|
27
33
|
*/
|
|
28
|
-
constructor(destination, missingFields, reason) {
|
|
29
|
-
|
|
34
|
+
constructor(destination, missingFields, reason, error) {
|
|
35
|
+
const words = error === undefined ? reason : `${reason}: ${error.reason}`;
|
|
36
|
+
super(`Destination "${destination}": ${words} (${missingFields.join(', ')})`);
|
|
30
37
|
this.name = 'DestinationConfigError';
|
|
31
38
|
this.destination = destination;
|
|
32
39
|
this.missingFields = [...missingFields];
|
|
40
|
+
if (error !== undefined)
|
|
41
|
+
this.error = error;
|
|
33
42
|
Object.setPrototypeOf(this, DestinationConfigError.prototype);
|
|
34
43
|
}
|
|
35
44
|
}
|
|
36
45
|
exports.DestinationConfigError = DestinationConfigError;
|
|
46
|
+
/** An own data property's value; `undefined` for an accessor or an absent key. */
|
|
47
|
+
function ownData(value, key) {
|
|
48
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
49
|
+
return descriptor !== undefined && 'value' in descriptor
|
|
50
|
+
? descriptor.value
|
|
51
|
+
: undefined;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Whether a value is a `DestinationConfigError` — or its JSON copy, or one
|
|
55
|
+
* of another installed copy of this package — by structure only: own data
|
|
56
|
+
* properties `name === 'DestinationConfigError'`, `code === 'DESTINATION_CONFIG'`,
|
|
57
|
+
* a string `destination` and an array of strings `missingFields`. No
|
|
58
|
+
* `instanceof`, no getter invoked; total — a Proxy whose traps throw answers
|
|
59
|
+
* false.
|
|
60
|
+
*/
|
|
61
|
+
function isDestinationConfigError(value) {
|
|
62
|
+
try {
|
|
63
|
+
if (typeof value !== 'object' || value === null)
|
|
64
|
+
return false;
|
|
65
|
+
if (ownData(value, 'name') !== 'DestinationConfigError')
|
|
66
|
+
return false;
|
|
67
|
+
if (ownData(value, 'code') !== 'DESTINATION_CONFIG')
|
|
68
|
+
return false;
|
|
69
|
+
if (typeof ownData(value, 'destination') !== 'string')
|
|
70
|
+
return false;
|
|
71
|
+
const fields = ownData(value, 'missingFields');
|
|
72
|
+
return (Array.isArray(fields) &&
|
|
73
|
+
fields.every((field) => typeof field === 'string'));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
package/dist/SessionWriter.d.ts
CHANGED
|
@@ -1,27 +1,53 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The broker's writes of a destination's session secret
|
|
3
|
-
*
|
|
2
|
+
* The broker's writes of a destination's session secret: one plain queue per
|
|
3
|
+
* destination.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
5
|
+
* - **One at a time, in order.** The writes of one destination run one after
|
|
6
|
+
* another, in the order they were queued, each writing what it was given;
|
|
7
|
+
* an older write never runs after, and so never overwrites, a newer one.
|
|
8
|
+
* Destinations never wait on each other.
|
|
9
|
+
* - **Each write's own outcome.** A submission resolves with what its own
|
|
10
|
+
* write came to — landed, or the store's error — never with another
|
|
11
|
+
* submission's: no write is folded into another, so no caller is told
|
|
12
|
+
* "landed" for a write that never ran.
|
|
13
|
+
* - **A failed write stays pending.** The destination then has a pending
|
|
14
|
+
* write: the latest one that failed — every write is built from its
|
|
15
|
+
* build's logical state, so the latest is the one that must land. It is
|
|
16
|
+
* retried by the destination's next write, which carries the same state
|
|
17
|
+
* and, once it lands, clears it; or by `retry()` / `flush()`, which write
|
|
18
|
+
* the pending one again. **There is no retry timer:** nothing is retried on
|
|
19
|
+
* its own, and nothing is scheduled.
|
|
20
|
+
* - **The store's contract:** `saveSession` settles — resolves or rejects. A
|
|
21
|
+
* store that never settles holds its destination's queue; avoiding that is
|
|
22
|
+
* the consumer's. The writer builds no machinery against it.
|
|
23
|
+
* - **Failures are logged once each, with `logFields`** of auth-errors'
|
|
24
|
+
* classification (`persisting-tokens`) — never a message: the store holds
|
|
25
|
+
* tokens, and its errors are foreign text. Each failed attempt is one
|
|
26
|
+
* `warn` line, written here only: the row's persistence is given no logger.
|
|
27
|
+
* A retry that fails again is a new attempt, and its own line.
|
|
28
|
+
* - **`flush()`** retries every pending write once, after everything queued
|
|
29
|
+
* before it, and rejects naming the destinations still pending.
|
|
21
30
|
*/
|
|
31
|
+
import type { IAuthProviderError } from '@mcp-abap-adt/interfaces-auth';
|
|
22
32
|
import type { ILogger } from '@mcp-abap-adt/interfaces-utils';
|
|
23
|
-
/**
|
|
24
|
-
export
|
|
33
|
+
/** What one write came to: it landed, or the store's error. */
|
|
34
|
+
export type WriteOutcome = {
|
|
35
|
+
readonly landed: true;
|
|
36
|
+
} | {
|
|
37
|
+
readonly landed: false;
|
|
38
|
+
readonly error: unknown;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* One destination whose session write still fails, in `flush()`'s
|
|
42
|
+
* `AggregateError`: the destination and the store's error as auth-errors
|
|
43
|
+
* classifies it (`persisting-tokens`) — never the store's message.
|
|
44
|
+
*/
|
|
45
|
+
export declare class SessionWriteFailure extends Error {
|
|
46
|
+
readonly destination: string;
|
|
47
|
+
/** `classify(storeError, 'persisting-tokens')`. */
|
|
48
|
+
readonly error: IAuthProviderError;
|
|
49
|
+
constructor(destination: string, storeError: unknown);
|
|
50
|
+
}
|
|
25
51
|
/** `T`: what one write takes — the result, with what the broker writes beside it. */
|
|
26
52
|
export declare class SessionWriter<T> {
|
|
27
53
|
private readonly write;
|
|
@@ -30,25 +56,36 @@ export declare class SessionWriter<T> {
|
|
|
30
56
|
/**
|
|
31
57
|
* @param write Writes one result for a destination; throws when the store
|
|
32
58
|
* does not take it.
|
|
59
|
+
* @param logger The broker's quiet logger (`quietLogger`): a line never
|
|
60
|
+
* throws and never rejects, so what a write came to is the store's answer
|
|
61
|
+
* alone, and `submit` / `retry` never reject.
|
|
33
62
|
*/
|
|
34
63
|
constructor(write: (destination: string, result: T) => Promise<void>, logger: ILogger);
|
|
35
64
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
65
|
+
* Queue `result` for the destination, after every write queued before it.
|
|
66
|
+
* Never rejects: resolves once this write has settled, with what it came
|
|
67
|
+
* to. Landed: the destination's pending write, if any — an older one — is
|
|
68
|
+
* replaced. Failed: this write is the destination's pending one.
|
|
69
|
+
*/
|
|
70
|
+
submit(destination: string, result: T): Promise<WriteOutcome>;
|
|
71
|
+
/**
|
|
72
|
+
* Wait for every write of the destination queued before this call, then
|
|
73
|
+
* write its pending write once more, if one is left. Never rejects:
|
|
74
|
+
* resolves landed when, by its turn, nothing is pending — every write
|
|
75
|
+
* queued before it landed, or the latest of them did, replacing a failed
|
|
76
|
+
* older one — else with the attempt's own outcome. It writes the pending
|
|
77
|
+
* write as its turn finds it: the latest that failed.
|
|
38
78
|
*/
|
|
39
|
-
|
|
79
|
+
retry(destination: string): Promise<WriteOutcome>;
|
|
40
80
|
/**
|
|
41
|
-
* One more attempt for every pending
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
81
|
+
* One more attempt for every pending write, each after everything queued
|
|
82
|
+
* for its destination before it; resolves when all landed, rejects naming
|
|
83
|
+
* the destinations still pending — one `SessionWriteFailure` per
|
|
84
|
+
* destination, never the store's message. What still fails stays pending.
|
|
45
85
|
*/
|
|
46
86
|
flush(): Promise<void>;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
private
|
|
50
|
-
private writePending;
|
|
51
|
-
private schedule;
|
|
52
|
-
private cancelTimer;
|
|
87
|
+
/** Runs `job` after the destination's last queued write has settled. */
|
|
88
|
+
private enqueue;
|
|
89
|
+
private logFailure;
|
|
53
90
|
}
|
|
54
91
|
//# sourceMappingURL=SessionWriter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionWriter.d.ts","sourceRoot":"","sources":["../src/SessionWriter.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"SessionWriter.d.ts","sourceRoot":"","sources":["../src/SessionWriter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACxE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAe9D,+DAA+D;AAC/D,MAAM,MAAM,YAAY,GACpB;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAIxD;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;gBAEvB,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO;CAQrD;AAED,qFAAqF;AACrF,qBAAa,aAAa,CAAC,CAAC;IAWxB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAXzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA+B;IAEtD;;;;;;OAMG;gBAEgB,KAAK,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,EACxD,MAAM,EAAE,OAAO;IAGlC;;;;;OAKG;IACH,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC;IAgB7D;;;;;;;OAOG;IACH,KAAK,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAiBjD;;;;;OAKG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB5B,wEAAwE;IACxE,OAAO,CAAC,OAAO;IAmBf,OAAO,CAAC,UAAU;CAMnB"}
|
package/dist/SessionWriter.js
CHANGED
|
@@ -1,37 +1,57 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* The broker's writes of a destination's session secret
|
|
4
|
-
*
|
|
3
|
+
* The broker's writes of a destination's session secret: one plain queue per
|
|
4
|
+
* destination.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
6
|
+
* - **One at a time, in order.** The writes of one destination run one after
|
|
7
|
+
* another, in the order they were queued, each writing what it was given;
|
|
8
|
+
* an older write never runs after, and so never overwrites, a newer one.
|
|
9
|
+
* Destinations never wait on each other.
|
|
10
|
+
* - **Each write's own outcome.** A submission resolves with what its own
|
|
11
|
+
* write came to — landed, or the store's error — never with another
|
|
12
|
+
* submission's: no write is folded into another, so no caller is told
|
|
13
|
+
* "landed" for a write that never ran.
|
|
14
|
+
* - **A failed write stays pending.** The destination then has a pending
|
|
15
|
+
* write: the latest one that failed — every write is built from its
|
|
16
|
+
* build's logical state, so the latest is the one that must land. It is
|
|
17
|
+
* retried by the destination's next write, which carries the same state
|
|
18
|
+
* and, once it lands, clears it; or by `retry()` / `flush()`, which write
|
|
19
|
+
* the pending one again. **There is no retry timer:** nothing is retried on
|
|
20
|
+
* its own, and nothing is scheduled.
|
|
21
|
+
* - **The store's contract:** `saveSession` settles — resolves or rejects. A
|
|
22
|
+
* store that never settles holds its destination's queue; avoiding that is
|
|
23
|
+
* the consumer's. The writer builds no machinery against it.
|
|
24
|
+
* - **Failures are logged once each, with `logFields`** of auth-errors'
|
|
25
|
+
* classification (`persisting-tokens`) — never a message: the store holds
|
|
26
|
+
* tokens, and its errors are foreign text. Each failed attempt is one
|
|
27
|
+
* `warn` line, written here only: the row's persistence is given no logger.
|
|
28
|
+
* A retry that fails again is a new attempt, and its own line.
|
|
29
|
+
* - **`flush()`** retries every pending write once, after everything queued
|
|
30
|
+
* before it, and rejects naming the destinations still pending.
|
|
22
31
|
*/
|
|
23
32
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.SessionWriter = void 0;
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
exports.SessionWriter = exports.SessionWriteFailure = void 0;
|
|
34
|
+
const auth_errors_1 = require("@mcp-abap-adt/auth-errors");
|
|
35
|
+
const LANDED = Object.freeze({ landed: true });
|
|
36
|
+
/**
|
|
37
|
+
* One destination whose session write still fails, in `flush()`'s
|
|
38
|
+
* `AggregateError`: the destination and the store's error as auth-errors
|
|
39
|
+
* classifies it (`persisting-tokens`) — never the store's message.
|
|
40
|
+
*/
|
|
41
|
+
class SessionWriteFailure extends Error {
|
|
42
|
+
destination;
|
|
43
|
+
/** `classify(storeError, 'persisting-tokens')`. */
|
|
44
|
+
error;
|
|
45
|
+
constructor(destination, storeError) {
|
|
46
|
+
const error = (0, auth_errors_1.classify)(storeError, 'persisting-tokens');
|
|
47
|
+
super(`"${destination}": ${error.reason}`);
|
|
48
|
+
this.name = 'SessionWriteFailure';
|
|
49
|
+
this.destination = destination;
|
|
50
|
+
this.error = error;
|
|
51
|
+
Object.setPrototypeOf(this, SessionWriteFailure.prototype);
|
|
32
52
|
}
|
|
33
|
-
return typeof error;
|
|
34
53
|
}
|
|
54
|
+
exports.SessionWriteFailure = SessionWriteFailure;
|
|
35
55
|
/** `T`: what one write takes — the result, with what the broker writes beside it. */
|
|
36
56
|
class SessionWriter {
|
|
37
57
|
write;
|
|
@@ -40,96 +60,100 @@ class SessionWriter {
|
|
|
40
60
|
/**
|
|
41
61
|
* @param write Writes one result for a destination; throws when the store
|
|
42
62
|
* does not take it.
|
|
63
|
+
* @param logger The broker's quiet logger (`quietLogger`): a line never
|
|
64
|
+
* throws and never rejects, so what a write came to is the store's answer
|
|
65
|
+
* alone, and `submit` / `retry` never reject.
|
|
43
66
|
*/
|
|
44
67
|
constructor(write, logger) {
|
|
45
68
|
this.write = write;
|
|
46
69
|
this.logger = logger;
|
|
47
70
|
}
|
|
48
71
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
72
|
+
* Queue `result` for the destination, after every write queued before it.
|
|
73
|
+
* Never rejects: resolves once this write has settled, with what it came
|
|
74
|
+
* to. Landed: the destination's pending write, if any — an older one — is
|
|
75
|
+
* replaced. Failed: this write is the destination's pending one.
|
|
51
76
|
*/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
77
|
+
submit(destination, result) {
|
|
78
|
+
return this.enqueue(destination, async (queue) => {
|
|
79
|
+
try {
|
|
80
|
+
await this.write(destination, result);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
queue.pending = { write: result, error };
|
|
84
|
+
this.logFailure(destination, error);
|
|
85
|
+
return Object.freeze({ landed: false, error });
|
|
86
|
+
}
|
|
87
|
+
// Every write queued before this one has settled: a pending write is
|
|
88
|
+
// older, and this one carries the state it was built from.
|
|
89
|
+
queue.pending = undefined;
|
|
90
|
+
return LANDED;
|
|
91
|
+
});
|
|
57
92
|
}
|
|
58
93
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
94
|
+
* Wait for every write of the destination queued before this call, then
|
|
95
|
+
* write its pending write once more, if one is left. Never rejects:
|
|
96
|
+
* resolves landed when, by its turn, nothing is pending — every write
|
|
97
|
+
* queued before it landed, or the latest of them did, replacing a failed
|
|
98
|
+
* older one — else with the attempt's own outcome. It writes the pending
|
|
99
|
+
* write as its turn finds it: the latest that failed.
|
|
100
|
+
*/
|
|
101
|
+
retry(destination) {
|
|
102
|
+
return this.enqueue(destination, async (queue) => {
|
|
103
|
+
const pending = queue.pending;
|
|
104
|
+
if (pending === undefined)
|
|
105
|
+
return LANDED;
|
|
106
|
+
try {
|
|
107
|
+
await this.write(destination, pending.write);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
// Nothing else ran meanwhile: the queue runs one write at a time.
|
|
111
|
+
pending.error = error;
|
|
112
|
+
this.logFailure(destination, error);
|
|
113
|
+
return Object.freeze({ landed: false, error });
|
|
114
|
+
}
|
|
115
|
+
queue.pending = undefined;
|
|
116
|
+
return LANDED;
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* One more attempt for every pending write, each after everything queued
|
|
121
|
+
* for its destination before it; resolves when all landed, rejects naming
|
|
122
|
+
* the destinations still pending — one `SessionWriteFailure` per
|
|
123
|
+
* destination, never the store's message. What still fails stays pending.
|
|
63
124
|
*/
|
|
64
125
|
async flush() {
|
|
65
|
-
const
|
|
66
|
-
await Promise.all(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
126
|
+
const destinations = [...this.queues.keys()];
|
|
127
|
+
await Promise.all(destinations.map((d) => this.retry(d)));
|
|
128
|
+
const failed = [];
|
|
129
|
+
for (const destination of destinations) {
|
|
130
|
+
const pending = this.queues.get(destination)?.pending;
|
|
131
|
+
if (pending !== undefined)
|
|
132
|
+
failed.push([destination, pending]);
|
|
133
|
+
}
|
|
73
134
|
if (failed.length > 0) {
|
|
74
135
|
// The stores' own errors are not carried: their messages, causes and
|
|
75
136
|
// properties are foreign text that may quote what was being written.
|
|
76
|
-
throw new AggregateError(failed.map(([destination,
|
|
137
|
+
throw new AggregateError(failed.map(([destination, pending]) => new SessionWriteFailure(destination, pending.error)), `Session writes still failing for ${failed
|
|
77
138
|
.map(([destination]) => `"${destination}"`)
|
|
78
|
-
.join(', ')};
|
|
139
|
+
.join(', ')}; each stays pending until its destination's next write or flush()`);
|
|
79
140
|
}
|
|
80
141
|
}
|
|
81
|
-
|
|
142
|
+
/** Runs `job` after the destination's last queued write has settled. */
|
|
143
|
+
enqueue(destination, job) {
|
|
82
144
|
let queue = this.queues.get(destination);
|
|
83
145
|
if (!queue) {
|
|
84
|
-
queue = { tail: Promise.resolve()
|
|
146
|
+
queue = { tail: Promise.resolve() };
|
|
85
147
|
this.queues.set(destination, queue);
|
|
86
148
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const run = queue.tail.then(() => this.writePending(destination, queue));
|
|
92
|
-
queue.tail = run;
|
|
149
|
+
const own = queue;
|
|
150
|
+
// `job` never rejects; the tail is chained whatever the previous came to.
|
|
151
|
+
const run = own.tail.then(() => job(own), () => job(own));
|
|
152
|
+
own.tail = run;
|
|
93
153
|
return run;
|
|
94
154
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (result === undefined)
|
|
98
|
-
return;
|
|
99
|
-
try {
|
|
100
|
-
await this.write(destination, result);
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
queue.failures += 1;
|
|
104
|
-
queue.lastError = error;
|
|
105
|
-
const delay = Math.min(FIRST_DELAY_MS * 2 ** (queue.failures - 1), MAX_DELAY_MS);
|
|
106
|
-
this.logger.warn(`[AuthBroker] Session write for ${destination} failed; the token stands, the write is retried in ${delay} ms`, { error: classLabel(error), attempt: queue.failures });
|
|
107
|
-
this.schedule(destination, queue, delay);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
queue.failures = 0;
|
|
111
|
-
queue.lastError = undefined;
|
|
112
|
-
// A newer result that arrived meanwhile stays pending: its own attempt is
|
|
113
|
-
// queued after this one.
|
|
114
|
-
if (queue.pending === result) {
|
|
115
|
-
queue.pending = undefined;
|
|
116
|
-
this.cancelTimer(queue);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
schedule(destination, queue, delay) {
|
|
120
|
-
this.cancelTimer(queue);
|
|
121
|
-
const timer = setTimeout(() => {
|
|
122
|
-
queue.timer = undefined;
|
|
123
|
-
void this.attempt(destination, queue);
|
|
124
|
-
}, delay);
|
|
125
|
-
timer.unref();
|
|
126
|
-
queue.timer = timer;
|
|
127
|
-
}
|
|
128
|
-
cancelTimer(queue) {
|
|
129
|
-
if (queue.timer) {
|
|
130
|
-
clearTimeout(queue.timer);
|
|
131
|
-
queue.timer = undefined;
|
|
132
|
-
}
|
|
155
|
+
logFailure(destination, error) {
|
|
156
|
+
this.logger.warn(`[AuthBroker] Session write for ${destination} failed; it stays pending until the destination's next write or flush()`, (0, auth_errors_1.logFields)((0, auth_errors_1.classify)(error, 'persisting-tokens')));
|
|
133
157
|
}
|
|
134
158
|
}
|
|
135
159
|
exports.SessionWriter = SessionWriter;
|