euclid-ndk 0.2.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/LICENSE +201 -0
- package/README.md +603 -0
- package/dist/auth/index.d.ts +18 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +17 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/rfc9421.d.ts +112 -0
- package/dist/auth/rfc9421.d.ts.map +1 -0
- package/dist/auth/rfc9421.js +452 -0
- package/dist/auth/rfc9421.js.map +1 -0
- package/dist/auth/scheme.d.ts +46 -0
- package/dist/auth/scheme.d.ts.map +1 -0
- package/dist/auth/scheme.js +44 -0
- package/dist/auth/scheme.js.map +1 -0
- package/dist/auth/signable.d.ts +37 -0
- package/dist/auth/signable.d.ts.map +1 -0
- package/dist/auth/signable.js +70 -0
- package/dist/auth/signable.js.map +1 -0
- package/dist/auth/sigv4.d.ts +72 -0
- package/dist/auth/sigv4.d.ts.map +1 -0
- package/dist/auth/sigv4.js +227 -0
- package/dist/auth/sigv4.js.map +1 -0
- package/dist/credentials.d.ts +56 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +135 -0
- package/dist/credentials.js.map +1 -0
- package/dist/dto/com.d.ts +101 -0
- package/dist/dto/com.d.ts.map +1 -0
- package/dist/dto/com.js +132 -0
- package/dist/dto/com.js.map +1 -0
- package/dist/dto/eag.d.ts +104 -0
- package/dist/dto/eag.d.ts.map +1 -0
- package/dist/dto/eag.js +70 -0
- package/dist/dto/eag.js.map +1 -0
- package/dist/dto/eam.d.ts +105 -0
- package/dist/dto/eam.d.ts.map +1 -0
- package/dist/dto/eam.js +98 -0
- package/dist/dto/eam.js.map +1 -0
- package/dist/dto/eap.d.ts +88 -0
- package/dist/dto/eap.d.ts.map +1 -0
- package/dist/dto/eap.js +54 -0
- package/dist/dto/eap.js.map +1 -0
- package/dist/dto/ekm.d.ts +109 -0
- package/dist/dto/ekm.d.ts.map +1 -0
- package/dist/dto/ekm.js +78 -0
- package/dist/dto/ekm.js.map +1 -0
- package/dist/dto/ekv.d.ts +73 -0
- package/dist/dto/ekv.d.ts.map +1 -0
- package/dist/dto/ekv.js +45 -0
- package/dist/dto/ekv.js.map +1 -0
- package/dist/dto/ens.d.ts +84 -0
- package/dist/dto/ens.d.ts.map +1 -0
- package/dist/dto/ens.js +72 -0
- package/dist/dto/ens.js.map +1 -0
- package/dist/dto/eqs.d.ts +160 -0
- package/dist/dto/eqs.d.ts.map +1 -0
- package/dist/dto/eqs.js +120 -0
- package/dist/dto/eqs.js.map +1 -0
- package/dist/dto/esm.d.ts +192 -0
- package/dist/dto/esm.d.ts.map +1 -0
- package/dist/dto/esm.js +147 -0
- package/dist/dto/esm.js.map +1 -0
- package/dist/dto/ess.d.ts +49 -0
- package/dist/dto/ess.d.ts.map +1 -0
- package/dist/dto/ess.js +30 -0
- package/dist/dto/ess.js.map +1 -0
- package/dist/dto/json.d.ts +24 -0
- package/dist/dto/json.d.ts.map +1 -0
- package/dist/dto/json.js +47 -0
- package/dist/dto/json.js.map +1 -0
- package/dist/errors.d.ts +40 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +73 -0
- package/dist/errors.js.map +1 -0
- package/dist/http/client.d.ts +96 -0
- package/dist/http/client.d.ts.map +1 -0
- package/dist/http/client.js +258 -0
- package/dist/http/client.js.map +1 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +110 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/base.d.ts +121 -0
- package/dist/modules/base.d.ts.map +1 -0
- package/dist/modules/base.js +142 -0
- package/dist/modules/base.js.map +1 -0
- package/dist/modules/eag.d.ts +166 -0
- package/dist/modules/eag.d.ts.map +1 -0
- package/dist/modules/eag.js +189 -0
- package/dist/modules/eag.js.map +1 -0
- package/dist/modules/eam.d.ts +314 -0
- package/dist/modules/eam.d.ts.map +1 -0
- package/dist/modules/eam.js +641 -0
- package/dist/modules/eam.js.map +1 -0
- package/dist/modules/eap.d.ts +204 -0
- package/dist/modules/eap.d.ts.map +1 -0
- package/dist/modules/eap.js +218 -0
- package/dist/modules/eap.js.map +1 -0
- package/dist/modules/ekm.d.ts +182 -0
- package/dist/modules/ekm.d.ts.map +1 -0
- package/dist/modules/ekm.js +228 -0
- package/dist/modules/ekm.js.map +1 -0
- package/dist/modules/ekv.d.ts +189 -0
- package/dist/modules/ekv.d.ts.map +1 -0
- package/dist/modules/ekv.js +212 -0
- package/dist/modules/ekv.js.map +1 -0
- package/dist/modules/ens.d.ts +122 -0
- package/dist/modules/ens.d.ts.map +1 -0
- package/dist/modules/ens.js +167 -0
- package/dist/modules/ens.js.map +1 -0
- package/dist/modules/eqs.d.ts +283 -0
- package/dist/modules/eqs.d.ts.map +1 -0
- package/dist/modules/eqs.js +355 -0
- package/dist/modules/eqs.js.map +1 -0
- package/dist/modules/esm.d.ts +358 -0
- package/dist/modules/esm.d.ts.map +1 -0
- package/dist/modules/esm.js +660 -0
- package/dist/modules/esm.js.map +1 -0
- package/dist/modules/ess.d.ts +96 -0
- package/dist/modules/ess.d.ts.map +1 -0
- package/dist/modules/ess.js +110 -0
- package/dist/modules/ess.js.map +1 -0
- package/dist/url.d.ts +31 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +47 -0
- package/dist/url.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every module client has in common.
|
|
3
|
+
*
|
|
4
|
+
* euclid speaks one request shape, so most of a module client is the same client over and over: post
|
|
5
|
+
* JSON to `/`, name the module in `x-euclid-target` and the operation in `x-euclid-action`,
|
|
6
|
+
* authenticate the way the session that created it authenticates, and turn a non-2xx into a
|
|
7
|
+
* {@link EuclidServiceError}. That part lives here; what is left in {@link import("./esm.js")} - and
|
|
8
|
+
* in the module clients that follow it - is the operations themselves.
|
|
9
|
+
*
|
|
10
|
+
* Subclassing this is also how an application reaches a module this SDK has not wrapped: give the
|
|
11
|
+
* subclass a target and call {@link ModuleClient.call}.
|
|
12
|
+
*/
|
|
13
|
+
import type { EuclidHttpClient, Response } from "../http/client.js";
|
|
14
|
+
import type { EuclidSession } from "./eam.js";
|
|
15
|
+
/**
|
|
16
|
+
* What an action whose body is bytes will take.
|
|
17
|
+
*
|
|
18
|
+
* A string is sent as its UTF-8 bytes, which is then exactly what the server stored or encrypted;
|
|
19
|
+
* anything that matters about the encoding is the caller's to decide before calling.
|
|
20
|
+
*/
|
|
21
|
+
export type Bytes = Buffer | Uint8Array | string;
|
|
22
|
+
/** How a listing is paged and ordered. Every field has a server-side default. */
|
|
23
|
+
export interface PageOptions {
|
|
24
|
+
pageSize?: number;
|
|
25
|
+
pageIndex?: number;
|
|
26
|
+
sortColumn?: string;
|
|
27
|
+
sortDirection?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* How a listing is paged, ordered and narrowed.
|
|
31
|
+
*
|
|
32
|
+
* Most listings take a prefix; the ones that page messages do not, and take {@link PageOptions}
|
|
33
|
+
* instead - the server has nothing to match a message against.
|
|
34
|
+
*/
|
|
35
|
+
export interface ListOptions extends PageOptions {
|
|
36
|
+
prefix?: string;
|
|
37
|
+
}
|
|
38
|
+
/** What a {@link ModuleClient} needs beyond the session: which module it is, and how it behaves. */
|
|
39
|
+
export interface ModuleClientOptions {
|
|
40
|
+
/** The module this client talks to - what travels in `x-euclid-target`. */
|
|
41
|
+
target: string;
|
|
42
|
+
/**
|
|
43
|
+
* The actions of this module that carry raw bytes rather than JSON, and so present the session's
|
|
44
|
+
* bearer token rather than a signature - see {@link ModuleClient.authHeaders}.
|
|
45
|
+
*/
|
|
46
|
+
byteActions?: Iterable<string>;
|
|
47
|
+
/** Headers every request of this client carries on top of the session's own. */
|
|
48
|
+
headers?: Record<string, string>;
|
|
49
|
+
/**
|
|
50
|
+
* A connection to share rather than open. Only for a second view of the same module, where two
|
|
51
|
+
* clients that differ by a header have no reason to differ by a socket.
|
|
52
|
+
*/
|
|
53
|
+
client?: EuclidHttpClient;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* One euclid module, on the credentials of the session that created it.
|
|
57
|
+
*
|
|
58
|
+
* Holds the session rather than a copy of what it knew at the time, so the client follows it: a
|
|
59
|
+
* {@link EuclidSession.changeNamespace} between two calls scopes the second one, and a token the
|
|
60
|
+
* session refreshes is the token the next request carries.
|
|
61
|
+
*/
|
|
62
|
+
export declare abstract class ModuleClient {
|
|
63
|
+
#private;
|
|
64
|
+
/** The module this client talks to - what travels in `x-euclid-target`. */
|
|
65
|
+
readonly target: string;
|
|
66
|
+
/** The session this client authenticates as. */
|
|
67
|
+
protected readonly session: EuclidSession;
|
|
68
|
+
/**
|
|
69
|
+
* The connection this client sends on.
|
|
70
|
+
*
|
|
71
|
+
* Visible to a subclass because a second view of the same module shares it - see
|
|
72
|
+
* {@link import("./eqs.js").EuclidEqs.asInternal}, where two clients that differ by a header have no
|
|
73
|
+
* reason to differ by a socket.
|
|
74
|
+
*/
|
|
75
|
+
protected readonly client: EuclidHttpClient;
|
|
76
|
+
protected constructor(session: EuclidSession, options: ModuleClientOptions);
|
|
77
|
+
/** Releases the connection this client was holding. Closing the session calls it. */
|
|
78
|
+
close(): void;
|
|
79
|
+
/**
|
|
80
|
+
* Sends any action of this module, for one this SDK does not wrap yet.
|
|
81
|
+
*
|
|
82
|
+
* Public on purpose: a server that gains an action should be reachable without waiting for a
|
|
83
|
+
* release here.
|
|
84
|
+
*/
|
|
85
|
+
call(action: string, payload?: Record<string, unknown>, timeoutMs?: number): Promise<Record<string, unknown>>;
|
|
86
|
+
/** Who is asking, what they are scoped to, and whatever this client adds to that. */
|
|
87
|
+
routingHeaders(): Record<string, string>;
|
|
88
|
+
/** One action, sent. The raw response, for a caller that reads a status this does not. */
|
|
89
|
+
protected post(action: string, payload?: Record<string, unknown>, headers?: Record<string, string>, timeoutMs?: number): Promise<Response>;
|
|
90
|
+
/**
|
|
91
|
+
* One of the actions whose body is bytes rather than JSON, described by its headers instead.
|
|
92
|
+
*
|
|
93
|
+
* The content type is the only routing header that changes, and neither signing scheme covers it -
|
|
94
|
+
* both sign a fixed list of headers, which is what lets this differ without the signature having to
|
|
95
|
+
* know.
|
|
96
|
+
*/
|
|
97
|
+
protected postBytes(action: string, data: Bytes, headers?: Record<string, string>, timeoutMs?: number): Promise<Response>;
|
|
98
|
+
/**
|
|
99
|
+
* How this request authenticates - and what it is rebuilt with when the server says the credentials
|
|
100
|
+
* had expired.
|
|
101
|
+
*
|
|
102
|
+
* A byte-carrying action presents the session's bearer token rather than a signature, which is what
|
|
103
|
+
* euclid-cli and euclid-jdk do for the same actions, so all of them write an object the same way. A
|
|
104
|
+
* session that {@link EuclidSession.alwaysSigns} signs them anyway: it asked not to be handed a token
|
|
105
|
+
* silently, and a signature over raw bytes is exact here in a way it is not in every language.
|
|
106
|
+
*/
|
|
107
|
+
protected authHeaders(action: string, body: Buffer): Record<string, string>;
|
|
108
|
+
/** The response body as an object, or the server's own reason for refusing. */
|
|
109
|
+
protected result(action: string, response: Response): Record<string, unknown>;
|
|
110
|
+
/** An action whose answer is one string - an ERN, mostly. */
|
|
111
|
+
protected textOf(action: string, payload: Record<string, unknown>, field: string): Promise<string>;
|
|
112
|
+
/** An action whose answer is one number - a size, a count. */
|
|
113
|
+
protected numberOf(action: string, payload: Record<string, unknown>, field: string): Promise<number>;
|
|
114
|
+
}
|
|
115
|
+
/** A listing's paging and ordering, with the server's defaults filled in where the caller said nothing. */
|
|
116
|
+
export declare function pagePayload(options: PageOptions, defaultSortColumn: string): Record<string, unknown>;
|
|
117
|
+
/** The same, for the listings that also narrow by prefix. */
|
|
118
|
+
export declare function listPayload(options: ListOptions, defaultSortColumn: string): Record<string, unknown>;
|
|
119
|
+
/** One request body's bytes, whichever of the three spellings a caller reached for. */
|
|
120
|
+
export declare function bytesOf(data: Bytes): Buffer;
|
|
121
|
+
//# sourceMappingURL=base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/modules/base.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What every module client has in common.
|
|
3
|
+
*
|
|
4
|
+
* euclid speaks one request shape, so most of a module client is the same client over and over: post
|
|
5
|
+
* JSON to `/`, name the module in `x-euclid-target` and the operation in `x-euclid-action`,
|
|
6
|
+
* authenticate the way the session that created it authenticates, and turn a non-2xx into a
|
|
7
|
+
* {@link EuclidServiceError}. That part lives here; what is left in {@link import("./esm.js")} - and
|
|
8
|
+
* in the module clients that follow it - is the operations themselves.
|
|
9
|
+
*
|
|
10
|
+
* Subclassing this is also how an application reaches a module this SDK has not wrapped: give the
|
|
11
|
+
* subclass a target and call {@link ModuleClient.call}.
|
|
12
|
+
*/
|
|
13
|
+
import { EuclidServiceError } from "../errors.js";
|
|
14
|
+
/**
|
|
15
|
+
* One euclid module, on the credentials of the session that created it.
|
|
16
|
+
*
|
|
17
|
+
* Holds the session rather than a copy of what it knew at the time, so the client follows it: a
|
|
18
|
+
* {@link EuclidSession.changeNamespace} between two calls scopes the second one, and a token the
|
|
19
|
+
* session refreshes is the token the next request carries.
|
|
20
|
+
*/
|
|
21
|
+
export class ModuleClient {
|
|
22
|
+
/** The module this client talks to - what travels in `x-euclid-target`. */
|
|
23
|
+
target;
|
|
24
|
+
/** The session this client authenticates as. */
|
|
25
|
+
session;
|
|
26
|
+
/**
|
|
27
|
+
* The connection this client sends on.
|
|
28
|
+
*
|
|
29
|
+
* Visible to a subclass because a second view of the same module shares it - see
|
|
30
|
+
* {@link import("./eqs.js").EuclidEqs.asInternal}, where two clients that differ by a header have no
|
|
31
|
+
* reason to differ by a socket.
|
|
32
|
+
*/
|
|
33
|
+
client;
|
|
34
|
+
#byteActions;
|
|
35
|
+
#headers;
|
|
36
|
+
constructor(session, options) {
|
|
37
|
+
if (!options.target)
|
|
38
|
+
throw new Error("a module client must name its target, e.g. target: 'esm'");
|
|
39
|
+
this.target = options.target;
|
|
40
|
+
this.session = session;
|
|
41
|
+
this.#byteActions = new Set(options.byteActions ?? []);
|
|
42
|
+
this.#headers = { ...options.headers };
|
|
43
|
+
// Its own connection, and its own header factory: a request of this module whose credentials
|
|
44
|
+
// expired in flight has to be rebuilt as a request of *this* module rather than of EAM.
|
|
45
|
+
this.client = options.client ?? session.newClient((action, body) => this.authHeaders(action, body));
|
|
46
|
+
}
|
|
47
|
+
/** Releases the connection this client was holding. Closing the session calls it. */
|
|
48
|
+
close() {
|
|
49
|
+
this.client.close();
|
|
50
|
+
}
|
|
51
|
+
// -- transport -------------------------------------------------------------------------------
|
|
52
|
+
/**
|
|
53
|
+
* Sends any action of this module, for one this SDK does not wrap yet.
|
|
54
|
+
*
|
|
55
|
+
* Public on purpose: a server that gains an action should be reachable without waiting for a
|
|
56
|
+
* release here.
|
|
57
|
+
*/
|
|
58
|
+
async call(action, payload = {}, timeoutMs) {
|
|
59
|
+
return this.result(action, await this.post(action, payload, {}, timeoutMs));
|
|
60
|
+
}
|
|
61
|
+
/** Who is asking, what they are scoped to, and whatever this client adds to that. */
|
|
62
|
+
routingHeaders() {
|
|
63
|
+
return { ...this.session.routingHeaders(), ...this.#headers };
|
|
64
|
+
}
|
|
65
|
+
/** One action, sent. The raw response, for a caller that reads a status this does not. */
|
|
66
|
+
async post(action, payload = {}, headers = {}, timeoutMs) {
|
|
67
|
+
const body = Buffer.from(JSON.stringify(payload), "utf8");
|
|
68
|
+
const sent = { ...this.routingHeaders(), ...headers, ...this.authHeaders(action, body) };
|
|
69
|
+
return this.client.post(`${this.session.baseUrl}/`, body, this.target, action, sent, timeoutMs);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One of the actions whose body is bytes rather than JSON, described by its headers instead.
|
|
73
|
+
*
|
|
74
|
+
* The content type is the only routing header that changes, and neither signing scheme covers it -
|
|
75
|
+
* both sign a fixed list of headers, which is what lets this differ without the signature having to
|
|
76
|
+
* know.
|
|
77
|
+
*/
|
|
78
|
+
async postBytes(action, data, headers = {}, timeoutMs) {
|
|
79
|
+
const bytes = bytesOf(data);
|
|
80
|
+
const sent = {
|
|
81
|
+
...this.routingHeaders(),
|
|
82
|
+
"content-type": "application/octet-stream",
|
|
83
|
+
...headers,
|
|
84
|
+
...this.authHeaders(action, bytes),
|
|
85
|
+
};
|
|
86
|
+
return this.client.post(`${this.session.baseUrl}/`, bytes, this.target, action, sent, timeoutMs);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* How this request authenticates - and what it is rebuilt with when the server says the credentials
|
|
90
|
+
* had expired.
|
|
91
|
+
*
|
|
92
|
+
* A byte-carrying action presents the session's bearer token rather than a signature, which is what
|
|
93
|
+
* euclid-cli and euclid-jdk do for the same actions, so all of them write an object the same way. A
|
|
94
|
+
* session that {@link EuclidSession.alwaysSigns} signs them anyway: it asked not to be handed a token
|
|
95
|
+
* silently, and a signature over raw bytes is exact here in a way it is not in every language.
|
|
96
|
+
*/
|
|
97
|
+
authHeaders(action, body) {
|
|
98
|
+
if (this.#byteActions.has(action) && !this.session.alwaysSigns)
|
|
99
|
+
return this.session.bearerHeaders();
|
|
100
|
+
return this.session.authHeaders(this.target, action, body);
|
|
101
|
+
}
|
|
102
|
+
/** The response body as an object, or the server's own reason for refusing. */
|
|
103
|
+
result(action, response) {
|
|
104
|
+
if (!response.ok)
|
|
105
|
+
throw new EuclidServiceError(this.target, action, response.status, response.text);
|
|
106
|
+
const parsed = response.json();
|
|
107
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
|
|
108
|
+
? parsed
|
|
109
|
+
: { result: parsed };
|
|
110
|
+
}
|
|
111
|
+
// -- reading answers -------------------------------------------------------------------------
|
|
112
|
+
/** An action whose answer is one string - an ERN, mostly. */
|
|
113
|
+
async textOf(action, payload, field) {
|
|
114
|
+
const value = (await this.call(action, payload))[field];
|
|
115
|
+
return typeof value === "string" ? value : "";
|
|
116
|
+
}
|
|
117
|
+
/** An action whose answer is one number - a size, a count. */
|
|
118
|
+
async numberOf(action, payload, field) {
|
|
119
|
+
const value = (await this.call(action, payload))[field];
|
|
120
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** A listing's paging and ordering, with the server's defaults filled in where the caller said nothing. */
|
|
124
|
+
export function pagePayload(options, defaultSortColumn) {
|
|
125
|
+
return {
|
|
126
|
+
pageSize: options.pageSize ?? 10,
|
|
127
|
+
pageIndex: options.pageIndex ?? 0,
|
|
128
|
+
sortColumn: options.sortColumn ?? defaultSortColumn,
|
|
129
|
+
sortDirection: options.sortDirection ?? "asc",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** The same, for the listings that also narrow by prefix. */
|
|
133
|
+
export function listPayload(options, defaultSortColumn) {
|
|
134
|
+
return { prefix: options.prefix ?? "", ...pagePayload(options, defaultSortColumn) };
|
|
135
|
+
}
|
|
136
|
+
/** One request body's bytes, whichever of the three spellings a caller reached for. */
|
|
137
|
+
export function bytesOf(data) {
|
|
138
|
+
if (typeof data === "string")
|
|
139
|
+
return Buffer.from(data, "utf8");
|
|
140
|
+
return Buffer.isBuffer(data) ? data : Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/modules/base.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EAG - euclid's API gateway: the paths it publishes, and the ports it publishes them on.
|
|
3
|
+
*
|
|
4
|
+
* One object, {@link EuclidEag}, built from a session that has already logged in:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const eag = session.eag();
|
|
8
|
+
*
|
|
9
|
+
* await eag.createRoute("orders", "/api/orders", { applicationId: "order-service" });
|
|
10
|
+
* await eag.createModuleRoute("login", "/euclid/login", "eam", "login");
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* A route publishes a path prefix and says where everything beneath it goes: to an application euclid runs,
|
|
14
|
+
* or to one action of a euclid module. It is one or the other, never both and never neither - those are
|
|
15
|
+
* reached in entirely different ways, and a route that named both would leave which one wins up to the
|
|
16
|
+
* proxy.
|
|
17
|
+
*
|
|
18
|
+
* Module routes are the way in for something outside euclid that needs euclid itself - a browser that has to
|
|
19
|
+
* log in before it can call anything. Without one, a front end would talk to the API gateway for the
|
|
20
|
+
* application and to euclid's own gateway for its credentials: two ports, two origins, and CORS between
|
|
21
|
+
* them.
|
|
22
|
+
*
|
|
23
|
+
* Every action here is administrator-only, server-side. {@link EuclidSession.isAdmin} says whether the
|
|
24
|
+
* logged-in user is one, though the server enforces it regardless.
|
|
25
|
+
*/
|
|
26
|
+
import { type ListListenersResult, type Route } from "../dto/eag.js";
|
|
27
|
+
import { ModuleClient } from "./base.js";
|
|
28
|
+
import type { EuclidSession } from "./eam.js";
|
|
29
|
+
export declare const TARGET = "eag";
|
|
30
|
+
/** Proxied as it arrives: whatever the application requires, it enforces itself. */
|
|
31
|
+
export declare const ROUTE_AUTH_NONE = "NONE";
|
|
32
|
+
/**
|
|
33
|
+
* A euclid credential is required and verified before anything is forwarded - whatever euclid's own gateway
|
|
34
|
+
* accepts, which is a bearer token, an RFC 9421 signature or SigV4.
|
|
35
|
+
*/
|
|
36
|
+
export declare const ROUTE_AUTH_EUCLID = "EUCLID";
|
|
37
|
+
/**
|
|
38
|
+
* HTTP Basic against a euclid user's password, for the callers a euclid credential does not suit: a browser,
|
|
39
|
+
* which prompts when it is answered with `WWW-Authenticate`, and a script with nothing but curl.
|
|
40
|
+
*/
|
|
41
|
+
export declare const ROUTE_AUTH_BASIC = "BASIC";
|
|
42
|
+
/** What a listener speaks, as {@link import("../dto/eag.js").Listener} reports it. */
|
|
43
|
+
export declare const PROTOCOL_HTTP = "http";
|
|
44
|
+
export declare const PROTOCOL_HTTPS = "https";
|
|
45
|
+
/** Where a route sends what it carries, and on what terms. */
|
|
46
|
+
export interface CreateRouteOptions {
|
|
47
|
+
/** The application requests are sent to, which has to exist already. */
|
|
48
|
+
applicationId?: string;
|
|
49
|
+
/**
|
|
50
|
+
* The euclid module to reach instead, e.g. `"eam"` - see {@link EuclidEag.createModuleRoute}, which is
|
|
51
|
+
* this with the pair spelled out.
|
|
52
|
+
*/
|
|
53
|
+
moduleTarget?: string;
|
|
54
|
+
/** The one action that module answers for on this route. */
|
|
55
|
+
moduleAction?: string;
|
|
56
|
+
/** The HTTP methods this route answers for; none means every method. */
|
|
57
|
+
methods?: Iterable<string>;
|
|
58
|
+
/** {@link ROUTE_AUTH_NONE}, {@link ROUTE_AUTH_EUCLID} or {@link ROUTE_AUTH_BASIC}. */
|
|
59
|
+
authentication?: string;
|
|
60
|
+
/** Whether the gateway serves it from the start. */
|
|
61
|
+
active?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* The namespace requests carried by this route act in. Left empty, the session's own - which is almost
|
|
64
|
+
* always what is meant, and nameable because it is not always: a route published for one namespace should
|
|
65
|
+
* not act in another just because an administrator of the first happened to configure it.
|
|
66
|
+
*/
|
|
67
|
+
namespace?: string;
|
|
68
|
+
/** Likewise, defaulting to the session's. */
|
|
69
|
+
region?: string;
|
|
70
|
+
}
|
|
71
|
+
/** The same, minus the pair {@link EuclidEag.createModuleRoute} supplies itself. */
|
|
72
|
+
export type CreateModuleRouteOptions = Omit<CreateRouteOptions, "applicationId" | "moduleTarget" | "moduleAction">;
|
|
73
|
+
/**
|
|
74
|
+
* What an update changes - and only what it names.
|
|
75
|
+
*
|
|
76
|
+
* The distinction the server draws is between a field being sent and not being sent, rather than between its
|
|
77
|
+
* values, so leaving `path` out leaves the stored path alone. Moving a route to an application clears its
|
|
78
|
+
* module target and the other way round, since leaving both set would make which one wins depend on the
|
|
79
|
+
* proxy.
|
|
80
|
+
*/
|
|
81
|
+
export interface UpdateRouteChanges {
|
|
82
|
+
path?: string;
|
|
83
|
+
applicationId?: string;
|
|
84
|
+
moduleTarget?: string;
|
|
85
|
+
moduleAction?: string;
|
|
86
|
+
methods?: Iterable<string>;
|
|
87
|
+
authentication?: string;
|
|
88
|
+
active?: boolean;
|
|
89
|
+
namespace?: string;
|
|
90
|
+
region?: string;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* EAG's operations, on the credentials of the session that created it.
|
|
94
|
+
*
|
|
95
|
+
* Built by {@link EuclidSession.eag} rather than directly, so that it shares that session's identity,
|
|
96
|
+
* namespace and connection settings - and follows them as they change.
|
|
97
|
+
*/
|
|
98
|
+
export declare class EuclidEag extends ModuleClient {
|
|
99
|
+
constructor(session: EuclidSession);
|
|
100
|
+
/**
|
|
101
|
+
* Publishes a path, and sends everything beneath it to an application or to a module.
|
|
102
|
+
*
|
|
103
|
+
* Refused with HTTP 409 if the route ID is taken, or if another route already answers for this path and
|
|
104
|
+
* one of these methods. An application that does not exist is refused with 404 rather than becoming a
|
|
105
|
+
* route that answers 503 for every request - which looks like an application that is down rather than one
|
|
106
|
+
* that was never deployed.
|
|
107
|
+
*
|
|
108
|
+
* @param routeId the name to manage this route under, unique across the installation.
|
|
109
|
+
* @param path the path prefix to publish, which has to start with `/`.
|
|
110
|
+
* @param options request options
|
|
111
|
+
* @throws {Error} if neither an application nor a module was named, or both were. The server refuses that
|
|
112
|
+
* too; this just says so before the round trip.
|
|
113
|
+
*/
|
|
114
|
+
createRoute(routeId: string, path: string, options?: CreateRouteOptions): Promise<Route>;
|
|
115
|
+
/**
|
|
116
|
+
* Publishes a path that reaches one action of a euclid module rather than an application.
|
|
117
|
+
*
|
|
118
|
+
* The same call as {@link createRoute} with the module pair required rather than optional, because that
|
|
119
|
+
* pair is what makes a module route: a target with no action gives the gateway nothing to dispatch on, and
|
|
120
|
+
* would answer 400 for every request the route ever carries.
|
|
121
|
+
*/
|
|
122
|
+
createModuleRoute(routeId: string, path: string, moduleTarget: string, moduleAction: string, options?: CreateModuleRouteOptions): Promise<Route>;
|
|
123
|
+
/**
|
|
124
|
+
* Changes a route that already exists. Only what `changes` names changes - see
|
|
125
|
+
* {@link UpdateRouteChanges}.
|
|
126
|
+
*/
|
|
127
|
+
updateRoute(routeId: string, changes?: UpdateRouteChanges): Promise<Route>;
|
|
128
|
+
/**
|
|
129
|
+
* Takes a route out of service, or puts it back, without changing anything else about it.
|
|
130
|
+
*
|
|
131
|
+
* This is how something stops being exposed in a hurry: the route stays exactly as it was and comes back
|
|
132
|
+
* the same when it is reactivated, which deleting and recreating it would not guarantee.
|
|
133
|
+
*/
|
|
134
|
+
setRouteActive(routeId: string, active: boolean): Promise<Route>;
|
|
135
|
+
/**
|
|
136
|
+
* The routes whose path starts with a prefix - what somebody asking "what is published under `/api`"
|
|
137
|
+
* wants. An empty prefix lists them all.
|
|
138
|
+
*
|
|
139
|
+
* The prefix filters on the path rather than on the route ID, and is matched literally rather than as a
|
|
140
|
+
* pattern, so `/api/v1.0` does not also match `/api/v1X0`.
|
|
141
|
+
*/
|
|
142
|
+
listRoutes(pathPrefix?: string): Promise<Route[]>;
|
|
143
|
+
/** One route, by its ID. */
|
|
144
|
+
getRoute(routeId: string): Promise<Route>;
|
|
145
|
+
/**
|
|
146
|
+
* Deletes a route, which stops the gateway serving its path.
|
|
147
|
+
*
|
|
148
|
+
* Deleting is not how something is taken out of service temporarily - see {@link setRouteActive}, which
|
|
149
|
+
* leaves the route as it was so it returns exactly the same.
|
|
150
|
+
*/
|
|
151
|
+
deleteRoute(routeId: string): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* The ports the gateway was configured to answer on, and whether it is answering.
|
|
154
|
+
*
|
|
155
|
+
* A listener whose port was taken, or whose certificate could not be loaded, is still listed - it is the
|
|
156
|
+
* one somebody is looking for - and `serving` is what says whether anything is bound. For an HTTPS
|
|
157
|
+
* listener the certificate comes with it, including whether euclid minted it itself.
|
|
158
|
+
*/
|
|
159
|
+
listListeners(): Promise<ListListenersResult>;
|
|
160
|
+
/**
|
|
161
|
+
* EAG's own metrics, as the server collects them. Answered unparsed - the shape belongs to the monitoring
|
|
162
|
+
* module rather than to EAG.
|
|
163
|
+
*/
|
|
164
|
+
metrics(): Promise<Record<string, unknown>>;
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=eag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eag.d.ts","sourceRoot":"","sources":["../../src/modules/eag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,KAAK,EACX,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,eAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,oFAAoF;AACpF,eAAO,MAAM,eAAe,SAAS,CAAC;AACtC;;;GAGG;AACH,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAC1C;;;GAGG;AACH,eAAO,MAAM,gBAAgB,UAAU,CAAC;AAExC,sFAAsF;AACtF,eAAO,MAAM,aAAa,SAAS,CAAC;AACpC,eAAO,MAAM,cAAc,UAAU,CAAC;AAEtC,8DAA8D;AAC9D,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3B,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,oFAAoF;AACpF,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,kBAAkB,EAAE,eAAe,GAAG,cAAc,GAAG,cAAc,CAAC,CAAC;AAEnH;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAeD;;;;;GAKG;AACH,qBAAa,SAAU,SAAQ,YAAY;gBAC7B,OAAO,EAAE,aAAa;IAMlC;;;;;;;;;;;;;OAaG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,KAAK,CAAC;IA6BlG;;;;;;OAMG;IACG,iBAAiB,CACrB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,KAAK,CAAC;IAIjB;;;OAGG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,KAAK,CAAC;IAUpF;;;;;OAKG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC;IAItE;;;;;;OAMG;IACG,UAAU,CAAC,UAAU,SAAK,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;IAMnD,4BAA4B;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;IAI/C;;;;;OAKG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMjD;;;;;;OAMG;IACG,aAAa,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAMnD;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAGlD"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EAG - euclid's API gateway: the paths it publishes, and the ports it publishes them on.
|
|
3
|
+
*
|
|
4
|
+
* One object, {@link EuclidEag}, built from a session that has already logged in:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const eag = session.eag();
|
|
8
|
+
*
|
|
9
|
+
* await eag.createRoute("orders", "/api/orders", { applicationId: "order-service" });
|
|
10
|
+
* await eag.createModuleRoute("login", "/euclid/login", "eam", "login");
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* A route publishes a path prefix and says where everything beneath it goes: to an application euclid runs,
|
|
14
|
+
* or to one action of a euclid module. It is one or the other, never both and never neither - those are
|
|
15
|
+
* reached in entirely different ways, and a route that named both would leave which one wins up to the
|
|
16
|
+
* proxy.
|
|
17
|
+
*
|
|
18
|
+
* Module routes are the way in for something outside euclid that needs euclid itself - a browser that has to
|
|
19
|
+
* log in before it can call anything. Without one, a front end would talk to the API gateway for the
|
|
20
|
+
* application and to euclid's own gateway for its credentials: two ports, two origins, and CORS between
|
|
21
|
+
* them.
|
|
22
|
+
*
|
|
23
|
+
* Every action here is administrator-only, server-side. {@link EuclidSession.isAdmin} says whether the
|
|
24
|
+
* logged-in user is one, though the server enforces it regardless.
|
|
25
|
+
*/
|
|
26
|
+
import { toListListenersResult, toRoute, } from "../dto/eag.js";
|
|
27
|
+
import { ModuleClient } from "./base.js";
|
|
28
|
+
export const TARGET = "eag";
|
|
29
|
+
/** Proxied as it arrives: whatever the application requires, it enforces itself. */
|
|
30
|
+
export const ROUTE_AUTH_NONE = "NONE";
|
|
31
|
+
/**
|
|
32
|
+
* A euclid credential is required and verified before anything is forwarded - whatever euclid's own gateway
|
|
33
|
+
* accepts, which is a bearer token, an RFC 9421 signature or SigV4.
|
|
34
|
+
*/
|
|
35
|
+
export const ROUTE_AUTH_EUCLID = "EUCLID";
|
|
36
|
+
/**
|
|
37
|
+
* HTTP Basic against a euclid user's password, for the callers a euclid credential does not suit: a browser,
|
|
38
|
+
* which prompts when it is answered with `WWW-Authenticate`, and a script with nothing but curl.
|
|
39
|
+
*/
|
|
40
|
+
export const ROUTE_AUTH_BASIC = "BASIC";
|
|
41
|
+
/** What a listener speaks, as {@link import("../dto/eag.js").Listener} reports it. */
|
|
42
|
+
export const PROTOCOL_HTTP = "http";
|
|
43
|
+
export const PROTOCOL_HTTPS = "https";
|
|
44
|
+
/** The fields an update sends when - and only when - it was given them. */
|
|
45
|
+
const UPDATABLE = [
|
|
46
|
+
"path",
|
|
47
|
+
"applicationId",
|
|
48
|
+
"moduleTarget",
|
|
49
|
+
"moduleAction",
|
|
50
|
+
"methods",
|
|
51
|
+
"authentication",
|
|
52
|
+
"active",
|
|
53
|
+
"namespace",
|
|
54
|
+
"region",
|
|
55
|
+
];
|
|
56
|
+
/**
|
|
57
|
+
* EAG's operations, on the credentials of the session that created it.
|
|
58
|
+
*
|
|
59
|
+
* Built by {@link EuclidSession.eag} rather than directly, so that it shares that session's identity,
|
|
60
|
+
* namespace and connection settings - and follows them as they change.
|
|
61
|
+
*/
|
|
62
|
+
export class EuclidEag extends ModuleClient {
|
|
63
|
+
constructor(session) {
|
|
64
|
+
super(session, { target: TARGET });
|
|
65
|
+
}
|
|
66
|
+
// -- routes ----------------------------------------------------------------------------------
|
|
67
|
+
/**
|
|
68
|
+
* Publishes a path, and sends everything beneath it to an application or to a module.
|
|
69
|
+
*
|
|
70
|
+
* Refused with HTTP 409 if the route ID is taken, or if another route already answers for this path and
|
|
71
|
+
* one of these methods. An application that does not exist is refused with 404 rather than becoming a
|
|
72
|
+
* route that answers 503 for every request - which looks like an application that is down rather than one
|
|
73
|
+
* that was never deployed.
|
|
74
|
+
*
|
|
75
|
+
* @param routeId the name to manage this route under, unique across the installation.
|
|
76
|
+
* @param path the path prefix to publish, which has to start with `/`.
|
|
77
|
+
* @param options request options
|
|
78
|
+
* @throws {Error} if neither an application nor a module was named, or both were. The server refuses that
|
|
79
|
+
* too; this just says so before the round trip.
|
|
80
|
+
*/
|
|
81
|
+
async createRoute(routeId, path, options = {}) {
|
|
82
|
+
const applicationId = options.applicationId ?? "";
|
|
83
|
+
const moduleTarget = options.moduleTarget ?? "";
|
|
84
|
+
if (Boolean(applicationId) === Boolean(moduleTarget)) {
|
|
85
|
+
throw new Error("name either an applicationId or a moduleTarget, not both and not neither");
|
|
86
|
+
}
|
|
87
|
+
if (moduleTarget && !options.moduleAction) {
|
|
88
|
+
throw new Error("moduleAction is required when a moduleTarget is named");
|
|
89
|
+
}
|
|
90
|
+
const payload = {
|
|
91
|
+
routeId,
|
|
92
|
+
path,
|
|
93
|
+
methods: [...(options.methods ?? [])],
|
|
94
|
+
authentication: options.authentication ?? ROUTE_AUTH_NONE,
|
|
95
|
+
active: options.active ?? true,
|
|
96
|
+
};
|
|
97
|
+
// Only when the caller named one: the server reads an empty string as the empty namespace rather than
|
|
98
|
+
// as "unspecified", so sending one would scope the route to nothing.
|
|
99
|
+
if (applicationId)
|
|
100
|
+
payload["applicationId"] = applicationId;
|
|
101
|
+
if (moduleTarget) {
|
|
102
|
+
payload["moduleTarget"] = moduleTarget;
|
|
103
|
+
payload["moduleAction"] = options.moduleAction;
|
|
104
|
+
}
|
|
105
|
+
if (options.namespace)
|
|
106
|
+
payload["namespace"] = options.namespace;
|
|
107
|
+
if (options.region)
|
|
108
|
+
payload["region"] = options.region;
|
|
109
|
+
return toRoute(await this.call("create-route", payload));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Publishes a path that reaches one action of a euclid module rather than an application.
|
|
113
|
+
*
|
|
114
|
+
* The same call as {@link createRoute} with the module pair required rather than optional, because that
|
|
115
|
+
* pair is what makes a module route: a target with no action gives the gateway nothing to dispatch on, and
|
|
116
|
+
* would answer 400 for every request the route ever carries.
|
|
117
|
+
*/
|
|
118
|
+
async createModuleRoute(routeId, path, moduleTarget, moduleAction, options = {}) {
|
|
119
|
+
return this.createRoute(routeId, path, { ...options, moduleTarget, moduleAction });
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Changes a route that already exists. Only what `changes` names changes - see
|
|
123
|
+
* {@link UpdateRouteChanges}.
|
|
124
|
+
*/
|
|
125
|
+
async updateRoute(routeId, changes = {}) {
|
|
126
|
+
const payload = { routeId };
|
|
127
|
+
for (const field of UPDATABLE) {
|
|
128
|
+
const value = changes[field];
|
|
129
|
+
if (value === undefined)
|
|
130
|
+
continue;
|
|
131
|
+
payload[field] = field === "methods" ? [...value] : value;
|
|
132
|
+
}
|
|
133
|
+
return toRoute(await this.call("update-route", payload));
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Takes a route out of service, or puts it back, without changing anything else about it.
|
|
137
|
+
*
|
|
138
|
+
* This is how something stops being exposed in a hurry: the route stays exactly as it was and comes back
|
|
139
|
+
* the same when it is reactivated, which deleting and recreating it would not guarantee.
|
|
140
|
+
*/
|
|
141
|
+
async setRouteActive(routeId, active) {
|
|
142
|
+
return this.updateRoute(routeId, { active });
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The routes whose path starts with a prefix - what somebody asking "what is published under `/api`"
|
|
146
|
+
* wants. An empty prefix lists them all.
|
|
147
|
+
*
|
|
148
|
+
* The prefix filters on the path rather than on the route ID, and is matched literally rather than as a
|
|
149
|
+
* pattern, so `/api/v1.0` does not also match `/api/v1X0`.
|
|
150
|
+
*/
|
|
151
|
+
async listRoutes(pathPrefix = "") {
|
|
152
|
+
const response = await this.call("list-routes", { prefix: pathPrefix });
|
|
153
|
+
const routes = response["routes"];
|
|
154
|
+
return Array.isArray(routes) ? routes.map(toRoute) : [];
|
|
155
|
+
}
|
|
156
|
+
/** One route, by its ID. */
|
|
157
|
+
async getRoute(routeId) {
|
|
158
|
+
return toRoute(await this.call("get-route", { routeId }));
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Deletes a route, which stops the gateway serving its path.
|
|
162
|
+
*
|
|
163
|
+
* Deleting is not how something is taken out of service temporarily - see {@link setRouteActive}, which
|
|
164
|
+
* leaves the route as it was so it returns exactly the same.
|
|
165
|
+
*/
|
|
166
|
+
async deleteRoute(routeId) {
|
|
167
|
+
await this.call("delete-route", { routeId });
|
|
168
|
+
}
|
|
169
|
+
// -- listeners -------------------------------------------------------------------------------
|
|
170
|
+
/**
|
|
171
|
+
* The ports the gateway was configured to answer on, and whether it is answering.
|
|
172
|
+
*
|
|
173
|
+
* A listener whose port was taken, or whose certificate could not be loaded, is still listed - it is the
|
|
174
|
+
* one somebody is looking for - and `serving` is what says whether anything is bound. For an HTTPS
|
|
175
|
+
* listener the certificate comes with it, including whether euclid minted it itself.
|
|
176
|
+
*/
|
|
177
|
+
async listListeners() {
|
|
178
|
+
return toListListenersResult(await this.call("list-listeners"));
|
|
179
|
+
}
|
|
180
|
+
// -- monitoring ------------------------------------------------------------------------------
|
|
181
|
+
/**
|
|
182
|
+
* EAG's own metrics, as the server collects them. Answered unparsed - the shape belongs to the monitoring
|
|
183
|
+
* module rather than to EAG.
|
|
184
|
+
*/
|
|
185
|
+
async metrics() {
|
|
186
|
+
return this.call("get-metrics");
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=eag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eag.js","sourceRoot":"","sources":["../../src/modules/eag.ts"],"names":[],"mappings":"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"}
|