@fluid-app/exigo-connection-sdk 0.1.6 → 0.1.8
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/README.md +44 -0
- package/dist/errors.d.mts +15 -1
- package/dist/errors.mjs +16 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/rest.d.mts +35 -3
- package/dist/rest.mjs +102 -7
- package/dist/sql.d.mts +2 -2
- package/dist/sql.mjs +2 -2
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -13,6 +13,8 @@ pnpm add @fluid-app/exigo-connection-sdk
|
|
|
13
13
|
# Only for apps that query the SQL replica:
|
|
14
14
|
pnpm add mssql
|
|
15
15
|
pnpm add -D @types/mssql
|
|
16
|
+
# Only for apps that reach Exigo through Fluid's egress proxy:
|
|
17
|
+
pnpm add undici
|
|
16
18
|
```
|
|
17
19
|
|
|
18
20
|
| Import | Provides | Needs `mssql` |
|
|
@@ -114,6 +116,47 @@ The client:
|
|
|
114
116
|
`get`, `delete` take `params`, `query`, `headers`, `timeoutMs` and `signal`;
|
|
115
117
|
`post`, `put`, `patch` also take a `body`, sent as JSON.
|
|
116
118
|
|
|
119
|
+
### Through Fluid's egress proxy (Mist apps)
|
|
120
|
+
|
|
121
|
+
Exigo allowlists callers by source IP, and a Mist app on Vercel has no fixed
|
|
122
|
+
outbound IP. Fluid's egress proxy (`services/egress-proxy`) lends it one: a
|
|
123
|
+
CONNECT-only forward proxy whose static IP Exigo tenants allowlist. TLS stays
|
|
124
|
+
end to end between the app and Exigo, so the proxy never sees the Exigo
|
|
125
|
+
credentials.
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
pnpm add undici
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const exigo = createExigoRestClient(rest, {
|
|
133
|
+
egressProxy: process.env.FLUID_EGRESS_PROXY_URL
|
|
134
|
+
? {
|
|
135
|
+
url: process.env.FLUID_EGRESS_PROXY_URL,
|
|
136
|
+
// The install webhook's authentication_token (dit_…).
|
|
137
|
+
token: installation.authenticationToken,
|
|
138
|
+
}
|
|
139
|
+
: undefined,
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
- Fluid sets `FLUID_EGRESS_PROXY_URL` on a droplet-linked Mist app when its
|
|
144
|
+
environment has a proxy. `fluid mist env pull` leaves it out on purpose: the
|
|
145
|
+
proxy refuses callers off Fluid's platform, so local runs call Exigo
|
|
146
|
+
directly, and the code above does that when the variable is unset.
|
|
147
|
+
- The proxy authenticates the droplet installation token, and lets through
|
|
148
|
+
only droplets and Exigo hosts on its own allowlists.
|
|
149
|
+
- The URL must be `https` (`http` only for a loopback proxy run locally),
|
|
150
|
+
since the CONNECT request carries the token outside the tunnel.
|
|
151
|
+
- A refusal throws `ExigoProxyError`, a subclass of `ExigoConnectionError`
|
|
152
|
+
whose `status` is the proxy's answer: 407 for a token it does not accept, 403
|
|
153
|
+
for a droplet or host it does not let through, 502 or 504 when it could not
|
|
154
|
+
reach Exigo, 503 when it could not verify the token or is at capacity.
|
|
155
|
+
- Clients built with the same proxy URL and token share one agent, so a
|
|
156
|
+
client per request still reuses open tunnels.
|
|
157
|
+
- REST only. The proxy does not carry the SQL replica's port, and `mssql`
|
|
158
|
+
cannot use a proxy.
|
|
159
|
+
|
|
117
160
|
## 3. SQL
|
|
118
161
|
|
|
119
162
|
```ts
|
|
@@ -179,6 +222,7 @@ Authorization header.
|
|
|
179
222
|
| `ExigoAuthError` | REST 401/403, or a SQL login failure |
|
|
180
223
|
| `ExigoTimeoutError` | a REST call, SQL connect or SQL query ran too long |
|
|
181
224
|
| `ExigoConnectionError` | DNS, refused connection, TLS, network |
|
|
225
|
+
| `ExigoProxyError` | the egress proxy refused the tunnel (`status`) |
|
|
182
226
|
| `ExigoApiError` | REST answered with an error or a body that isn't JSON |
|
|
183
227
|
|
|
184
228
|
`source` is `"rest"` or `"sql"` on the auth, timeout and connection errors.
|
package/dist/errors.d.mts
CHANGED
|
@@ -52,6 +52,20 @@ declare class ExigoConnectionError extends ExigoError {
|
|
|
52
52
|
readonly source: ExigoSource;
|
|
53
53
|
constructor(message: string, source: ExigoSource, options?: ExigoErrorOptions);
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Fluid's egress proxy refused to open a tunnel to Exigo. `status` is the
|
|
57
|
+
* proxy's answer to CONNECT: 407 for a token it does not accept, 403 for a
|
|
58
|
+
* droplet or host it does not let through, 502/504 when it could not reach
|
|
59
|
+
* Exigo, 503 when it could not verify the token or is at capacity.
|
|
60
|
+
*/
|
|
61
|
+
declare class ExigoProxyError extends ExigoConnectionError {
|
|
62
|
+
name: string;
|
|
63
|
+
/** The proxy's HTTP status for the CONNECT request. */
|
|
64
|
+
readonly status: number;
|
|
65
|
+
constructor(message: string, source: ExigoSource, options: ExigoErrorOptions & {
|
|
66
|
+
readonly status: number;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
55
69
|
interface ExigoApiErrorDetails {
|
|
56
70
|
readonly status: number;
|
|
57
71
|
readonly method: string;
|
|
@@ -76,4 +90,4 @@ declare class ExigoApiError extends ExigoError {
|
|
|
76
90
|
constructor(message: string, details: ExigoApiErrorDetails);
|
|
77
91
|
}
|
|
78
92
|
//#endregion
|
|
79
|
-
export { ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoErrorOptions, ExigoSource, ExigoTimeoutError };
|
|
93
|
+
export { ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoErrorOptions, ExigoProxyError, ExigoSource, ExigoTimeoutError };
|
package/dist/errors.mjs
CHANGED
|
@@ -49,6 +49,21 @@ var ExigoConnectionError = class extends ExigoError {
|
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
51
|
/**
|
|
52
|
+
* Fluid's egress proxy refused to open a tunnel to Exigo. `status` is the
|
|
53
|
+
* proxy's answer to CONNECT: 407 for a token it does not accept, 403 for a
|
|
54
|
+
* droplet or host it does not let through, 502/504 when it could not reach
|
|
55
|
+
* Exigo, 503 when it could not verify the token or is at capacity.
|
|
56
|
+
*/
|
|
57
|
+
var ExigoProxyError = class extends ExigoConnectionError {
|
|
58
|
+
name = "ExigoProxyError";
|
|
59
|
+
/** The proxy's HTTP status for the CONNECT request. */
|
|
60
|
+
status;
|
|
61
|
+
constructor(message, source, options) {
|
|
62
|
+
super(message, source, options);
|
|
63
|
+
this.status = options.status;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
52
67
|
* The REST API answered, but not with a usable result: a non-2xx status other
|
|
53
68
|
* than an auth refusal, or a success whose body is not JSON (Exigo serves an
|
|
54
69
|
* HTML page for a wrong URL with a 200).
|
|
@@ -70,4 +85,4 @@ var ExigoApiError = class extends ExigoError {
|
|
|
70
85
|
}
|
|
71
86
|
};
|
|
72
87
|
//#endregion
|
|
73
|
-
export { ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoTimeoutError };
|
|
88
|
+
export { ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoProxyError, ExigoTimeoutError };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { ExigoCredentialResolver, ExigoCredentials, ExigoRestCredentials, ExigoSqlCredentials, assertExigoRestCredentials, assertExigoSqlCredentials, defineExigoCredentials } from "./credentials.mjs";
|
|
2
|
-
import { ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoErrorOptions, ExigoSource, ExigoTimeoutError } from "./errors.mjs";
|
|
3
|
-
export { ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoConnectionError, type ExigoCredentialResolver, type ExigoCredentials, ExigoCredentialsError, ExigoError, ExigoErrorOptions, type ExigoRestCredentials, ExigoSource, type ExigoSqlCredentials, ExigoTimeoutError, assertExigoRestCredentials, assertExigoSqlCredentials, defineExigoCredentials };
|
|
2
|
+
import { ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoErrorOptions, ExigoProxyError, ExigoSource, ExigoTimeoutError } from "./errors.mjs";
|
|
3
|
+
export { ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoConnectionError, type ExigoCredentialResolver, type ExigoCredentials, ExigoCredentialsError, ExigoError, ExigoErrorOptions, ExigoProxyError, type ExigoRestCredentials, ExigoSource, type ExigoSqlCredentials, ExigoTimeoutError, assertExigoRestCredentials, assertExigoSqlCredentials, defineExigoCredentials };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoTimeoutError } from "./errors.mjs";
|
|
1
|
+
import { ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoProxyError, ExigoTimeoutError } from "./errors.mjs";
|
|
2
2
|
import { assertExigoRestCredentials, assertExigoSqlCredentials, defineExigoCredentials } from "./credentials.mjs";
|
|
3
|
-
export { ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoTimeoutError, assertExigoRestCredentials, assertExigoSqlCredentials, defineExigoCredentials };
|
|
3
|
+
export { ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoProxyError, ExigoTimeoutError, assertExigoRestCredentials, assertExigoSqlCredentials, defineExigoCredentials };
|
package/dist/rest.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ExigoRestCredentials } from "./credentials.mjs";
|
|
2
|
-
import { ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoErrorOptions, ExigoSource, ExigoTimeoutError } from "./errors.mjs";
|
|
2
|
+
import { ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoErrorOptions, ExigoProxyError, ExigoSource, ExigoTimeoutError } from "./errors.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/rest.d.ts
|
|
5
5
|
declare const EXIGO_API_VERSION = "3.0";
|
|
@@ -21,11 +21,43 @@ interface ExigoRequestOptions {
|
|
|
21
21
|
}
|
|
22
22
|
type ExigoMethodOptions = Omit<ExigoRequestOptions, "body">;
|
|
23
23
|
type ExigoBodyMethodOptions = ExigoRequestOptions;
|
|
24
|
+
/**
|
|
25
|
+
* Fluid's egress proxy: a CONNECT-only forward proxy with a static outbound IP
|
|
26
|
+
* that Exigo tenants allowlist, for apps on hosts whose egress IPs are not
|
|
27
|
+
* Fluid's (Mist apps on Vercel). TLS stays end to end between the app and
|
|
28
|
+
* Exigo; the proxy never sees the Exigo credentials.
|
|
29
|
+
*/
|
|
30
|
+
interface ExigoEgressProxy {
|
|
31
|
+
/**
|
|
32
|
+
* The proxy URL, which Fluid provides to a droplet-linked Mist app as
|
|
33
|
+
* `FLUID_EGRESS_PROXY_URL`. Must be https: the CONNECT request carries the
|
|
34
|
+
* token outside the tunnel. Plain http is accepted only for a loopback host,
|
|
35
|
+
* such as a proxy run locally.
|
|
36
|
+
*/
|
|
37
|
+
readonly url: string;
|
|
38
|
+
/**
|
|
39
|
+
* The droplet installation token (`dit_…`, the `authentication_token` of the
|
|
40
|
+
* install webhook) the proxy authenticates the app with. A `Bearer ` prefix
|
|
41
|
+
* is optional.
|
|
42
|
+
*/
|
|
43
|
+
readonly token: string;
|
|
44
|
+
}
|
|
24
45
|
interface ExigoRestClientOptions {
|
|
25
46
|
/** Per-request timeout. Defaults to {@link DEFAULT_EXIGO_TIMEOUT_MS}. */
|
|
26
47
|
readonly timeoutMs?: number;
|
|
27
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* For tests and instrumentation. Defaults to the global `fetch`, or to
|
|
50
|
+
* undici's when `egressProxy` is set; a custom one then receives the proxy
|
|
51
|
+
* as the `dispatcher` init option.
|
|
52
|
+
*/
|
|
28
53
|
readonly fetch?: typeof fetch;
|
|
54
|
+
/**
|
|
55
|
+
* Sends every request through Fluid's egress proxy. Needs the `undici`
|
|
56
|
+
* package. Leave it unset to call Exigo directly, as in local development,
|
|
57
|
+
* where Fluid withholds the proxy URL because the proxy refuses callers off
|
|
58
|
+
* its own platform.
|
|
59
|
+
*/
|
|
60
|
+
readonly egressProxy?: ExigoEgressProxy;
|
|
29
61
|
}
|
|
30
62
|
interface ExigoRestClient {
|
|
31
63
|
/** The normalized base URL, including the API version. */
|
|
@@ -62,4 +94,4 @@ declare function normalizeExigoBaseUrl(baseUrl: string): string;
|
|
|
62
94
|
*/
|
|
63
95
|
declare function createExigoRestClient(credentials: ExigoRestCredentials | null | undefined, options?: ExigoRestClientOptions): ExigoRestClient;
|
|
64
96
|
//#endregion
|
|
65
|
-
export { DEFAULT_EXIGO_TIMEOUT_MS, EXIGO_API_VERSION, ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoBodyMethodOptions, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoErrorOptions, ExigoHttpMethod, ExigoMethodOptions, ExigoQueryValue, ExigoRequestOptions, ExigoRestClient, ExigoRestClientOptions, type ExigoRestCredentials, ExigoSource, ExigoTimeoutError, createExigoRestClient, normalizeExigoBaseUrl };
|
|
97
|
+
export { DEFAULT_EXIGO_TIMEOUT_MS, EXIGO_API_VERSION, ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoBodyMethodOptions, ExigoConnectionError, ExigoCredentialsError, ExigoEgressProxy, ExigoError, ExigoErrorOptions, ExigoHttpMethod, ExigoMethodOptions, ExigoProxyError, ExigoQueryValue, ExigoRequestOptions, ExigoRestClient, ExigoRestClientOptions, type ExigoRestCredentials, ExigoSource, ExigoTimeoutError, createExigoRestClient, normalizeExigoBaseUrl };
|
package/dist/rest.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoTimeoutError } from "./errors.mjs";
|
|
1
|
+
import { ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoProxyError, ExigoTimeoutError } from "./errors.mjs";
|
|
2
2
|
import { assertExigoRestCredentials } from "./credentials.mjs";
|
|
3
3
|
//#region src/rest.ts
|
|
4
4
|
/**
|
|
@@ -11,7 +11,8 @@ import { assertExigoRestCredentials } from "./credentials.mjs";
|
|
|
11
11
|
* in each app.
|
|
12
12
|
*
|
|
13
13
|
* Uses the global `fetch`, so it runs on Node 20+ (Cloud Run, Vercel's Node
|
|
14
|
-
* runtime) without Node-only APIs.
|
|
14
|
+
* runtime) without Node-only APIs. Routing through Fluid's egress proxy is the
|
|
15
|
+
* exception: it needs `undici`, loaded only when `egressProxy` is set.
|
|
15
16
|
*/
|
|
16
17
|
const EXIGO_API_VERSION = "3.0";
|
|
17
18
|
const DEFAULT_EXIGO_TIMEOUT_MS = 15e3;
|
|
@@ -136,6 +137,75 @@ function validTimeout(timeoutMs) {
|
|
|
136
137
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_TIMEOUT_MS) throw new TypeError(`Exigo timeoutMs must be a positive number up to ${MAX_TIMEOUT_MS}, got ${timeoutMs}.`);
|
|
137
138
|
return timeoutMs;
|
|
138
139
|
}
|
|
140
|
+
/** Same rule as the base URL: the CONNECT request carries the token in clear. */
|
|
141
|
+
function normalizeEgressProxyUrl(proxyUrl) {
|
|
142
|
+
let url;
|
|
143
|
+
try {
|
|
144
|
+
url = new URL(proxyUrl.trim());
|
|
145
|
+
} catch {
|
|
146
|
+
throw new ExigoCredentialsError("Exigo egressProxy.url is not a valid URL.", ["egressProxy.url"]);
|
|
147
|
+
}
|
|
148
|
+
const loopbackHttp = url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname);
|
|
149
|
+
if (url.protocol !== "https:" && !loopbackHttp) throw new ExigoCredentialsError("Exigo egressProxy.url must be an https URL; the CONNECT request carries the token.", ["egressProxy.url"]);
|
|
150
|
+
if (url.username || url.password) throw new ExigoCredentialsError("Exigo egressProxy.url must not carry credentials; pass the token instead.", ["egressProxy.url"]);
|
|
151
|
+
return url.origin;
|
|
152
|
+
}
|
|
153
|
+
function normalizeEgressProxyToken(token) {
|
|
154
|
+
const bare = token.replace(/^\s*Bearer(?:\s+|$)/i, "").trim();
|
|
155
|
+
if (bare === "" || /\s/.test(bare)) throw new ExigoCredentialsError("Exigo egressProxy.token must be the droplet installation token (dit_…).", ["egressProxy.token"]);
|
|
156
|
+
return bare;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* One proxy agent per proxy URL and token, shared by every client that uses
|
|
160
|
+
* them, so a droplet building a client per request still reuses its tunnels.
|
|
161
|
+
*/
|
|
162
|
+
const proxyTransports = /* @__PURE__ */ new Map();
|
|
163
|
+
async function loadUndici() {
|
|
164
|
+
try {
|
|
165
|
+
return await import("undici");
|
|
166
|
+
} catch (error) {
|
|
167
|
+
throw new ExigoError("Routing Exigo calls through egressProxy needs the undici package. Install it next to @fluid-app/exigo-connection-sdk.", { cause: error });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function proxyTransport(proxyUrl, token) {
|
|
171
|
+
const key = `${proxyUrl}\n${token}`;
|
|
172
|
+
let transport = proxyTransports.get(key);
|
|
173
|
+
if (!transport) {
|
|
174
|
+
transport = loadUndici().then((undici) => ({
|
|
175
|
+
fetch: undici.fetch,
|
|
176
|
+
dispatcher: new undici.ProxyAgent({
|
|
177
|
+
uri: proxyUrl,
|
|
178
|
+
token: `Bearer ${token}`
|
|
179
|
+
})
|
|
180
|
+
}));
|
|
181
|
+
transport.catch(() => proxyTransports.delete(key));
|
|
182
|
+
proxyTransports.set(key, transport);
|
|
183
|
+
}
|
|
184
|
+
return transport;
|
|
185
|
+
}
|
|
186
|
+
/** undici reports a refused CONNECT as `Proxy response (407) !== 200 ...`. */
|
|
187
|
+
function proxyRefusalStatus(error) {
|
|
188
|
+
const seen = /* @__PURE__ */ new Set();
|
|
189
|
+
let current = error;
|
|
190
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
191
|
+
seen.add(current);
|
|
192
|
+
const match = /Proxy response \((\d{3})\)/.exec(current.message);
|
|
193
|
+
if (match) return Number(match[1]);
|
|
194
|
+
current = current.cause;
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
function describeProxyRefusal(status, host, proxyHost) {
|
|
199
|
+
const prefix = `Fluid's egress proxy at ${proxyHost} refused the tunnel to ${host} (HTTP ${status})`;
|
|
200
|
+
switch (status) {
|
|
201
|
+
case 407: return `${prefix}: it did not accept the token. Pass the droplet installation token (dit_…) as egressProxy.token.`;
|
|
202
|
+
case 403: return `${prefix}: this droplet may not egress through it, or ${host} is not on its allowlist.`;
|
|
203
|
+
case 502: return `${prefix}: it could not reach Exigo.`;
|
|
204
|
+
case 503: return `${prefix}: it could not verify the token with Fluid or is at capacity. Retry.`;
|
|
205
|
+
case 504: return `${prefix}: its connection to Exigo timed out. Retry.`;
|
|
206
|
+
default: return `${prefix}.`;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
139
209
|
/**
|
|
140
210
|
* Creates a client for one set of REST credentials.
|
|
141
211
|
*
|
|
@@ -149,9 +219,26 @@ function createExigoRestClient(credentials, options = {}) {
|
|
|
149
219
|
const baseUrl = normalizeExigoBaseUrl(credentials.baseUrl);
|
|
150
220
|
const host = new URL(baseUrl).host;
|
|
151
221
|
const authorization = authorizationHeader(credentials);
|
|
152
|
-
const
|
|
222
|
+
const egressProxy = options.egressProxy ? {
|
|
223
|
+
url: normalizeEgressProxyUrl(options.egressProxy.url),
|
|
224
|
+
token: normalizeEgressProxyToken(options.egressProxy.token)
|
|
225
|
+
} : null;
|
|
226
|
+
const proxyHost = egressProxy ? new URL(egressProxy.url).host : null;
|
|
227
|
+
const secrets = [
|
|
228
|
+
credentials.password,
|
|
229
|
+
authorization.replace(/^Basic /, ""),
|
|
230
|
+
...egressProxy ? [egressProxy.token] : []
|
|
231
|
+
];
|
|
153
232
|
const defaultTimeout = validTimeout(options.timeoutMs ?? 15e3);
|
|
154
|
-
|
|
233
|
+
async function send(url, init) {
|
|
234
|
+
if (!egressProxy) return (options.fetch ?? globalThis.fetch)(url, init);
|
|
235
|
+
const transport = await proxyTransport(egressProxy.url, egressProxy.token);
|
|
236
|
+
const proxiedInit = {
|
|
237
|
+
...init,
|
|
238
|
+
dispatcher: transport.dispatcher
|
|
239
|
+
};
|
|
240
|
+
return (options.fetch ?? transport.fetch)(url, proxiedInit);
|
|
241
|
+
}
|
|
155
242
|
async function request(method, path, requestOptions = {}) {
|
|
156
243
|
const relative = expandPath(path, requestOptions.params);
|
|
157
244
|
const url = new URL(`${baseUrl}${relative}`);
|
|
@@ -178,7 +265,7 @@ function createExigoRestClient(credentials, options = {}) {
|
|
|
178
265
|
let response;
|
|
179
266
|
let text;
|
|
180
267
|
try {
|
|
181
|
-
response = await
|
|
268
|
+
response = await send(url, {
|
|
182
269
|
method,
|
|
183
270
|
headers,
|
|
184
271
|
body,
|
|
@@ -191,7 +278,15 @@ function createExigoRestClient(credentials, options = {}) {
|
|
|
191
278
|
timeoutMs
|
|
192
279
|
});
|
|
193
280
|
if (callerSignal?.aborted) throw callerSignal.reason;
|
|
194
|
-
|
|
281
|
+
if (error instanceof ExigoError) throw error;
|
|
282
|
+
if (proxyHost) {
|
|
283
|
+
const status = proxyRefusalStatus(error);
|
|
284
|
+
if (status !== null) throw new ExigoProxyError(describeProxyRefusal(status, host, proxyHost), "rest", {
|
|
285
|
+
cause: error,
|
|
286
|
+
status
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
throw new ExigoConnectionError(`Could not reach Exigo at ${host}${proxyHost ? ` through the egress proxy at ${proxyHost}` : ""}: ${redact(describeNetworkFailure(error), secrets)}`, "rest", { cause: error });
|
|
195
290
|
} finally {
|
|
196
291
|
clearTimeout(timer);
|
|
197
292
|
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
@@ -228,4 +323,4 @@ function createExigoRestClient(credentials, options = {}) {
|
|
|
228
323
|
};
|
|
229
324
|
}
|
|
230
325
|
//#endregion
|
|
231
|
-
export { DEFAULT_EXIGO_TIMEOUT_MS, EXIGO_API_VERSION, ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoTimeoutError, createExigoRestClient, normalizeExigoBaseUrl };
|
|
326
|
+
export { DEFAULT_EXIGO_TIMEOUT_MS, EXIGO_API_VERSION, ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoProxyError, ExigoTimeoutError, createExigoRestClient, normalizeExigoBaseUrl };
|
package/dist/sql.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ExigoSqlCredentials } from "./credentials.mjs";
|
|
2
|
-
import { ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoErrorOptions, ExigoSource, ExigoTimeoutError } from "./errors.mjs";
|
|
2
|
+
import { ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoErrorOptions, ExigoProxyError, ExigoSource, ExigoTimeoutError } from "./errors.mjs";
|
|
3
3
|
import { ConnectionPool, ConnectionPool as ConnectionPool$1 } from "mssql";
|
|
4
4
|
|
|
5
5
|
//#region src/sql.d.ts
|
|
@@ -50,4 +50,4 @@ declare function closeExigoPool(credentials: Pick<ExigoSqlCredentials, "host" |
|
|
|
50
50
|
/** Closes every pool this package opened; for shutdown hooks and tests. */
|
|
51
51
|
declare function closeAllExigoPools(): Promise<void>;
|
|
52
52
|
//#endregion
|
|
53
|
-
export { type ConnectionPool, EXIGO_SQL_DEFAULTS, ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoErrorOptions, ExigoPoolOptions, ExigoSource, type ExigoSqlCredentials, ExigoTimeoutError, closeAllExigoPools, closeExigoPool, getExigoPool, translateExigoSqlError };
|
|
53
|
+
export { type ConnectionPool, EXIGO_SQL_DEFAULTS, ExigoApiError, ExigoApiErrorDetails, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoErrorOptions, ExigoPoolOptions, ExigoProxyError, ExigoSource, type ExigoSqlCredentials, ExigoTimeoutError, closeAllExigoPools, closeExigoPool, getExigoPool, translateExigoSqlError };
|
package/dist/sql.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoTimeoutError } from "./errors.mjs";
|
|
1
|
+
import { ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoProxyError, ExigoTimeoutError } from "./errors.mjs";
|
|
2
2
|
import { assertExigoSqlCredentials } from "./credentials.mjs";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
//#region src/sql.ts
|
|
@@ -292,4 +292,4 @@ async function closeEntry(entry) {
|
|
|
292
292
|
await pool.close();
|
|
293
293
|
}
|
|
294
294
|
//#endregion
|
|
295
|
-
export { EXIGO_SQL_DEFAULTS, ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoTimeoutError, closeAllExigoPools, closeExigoPool, getExigoPool, translateExigoSqlError };
|
|
295
|
+
export { EXIGO_SQL_DEFAULTS, ExigoApiError, ExigoAuthError, ExigoConnectionError, ExigoCredentialsError, ExigoError, ExigoProxyError, ExigoTimeoutError, closeAllExigoPools, closeExigoPool, getExigoPool, translateExigoSqlError };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluid-app/exigo-connection-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Exigo REST client, SQL pool and credential interface for Fluid droplets and Mist apps",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -41,11 +41,13 @@
|
|
|
41
41
|
"mssql": "^12.7.2",
|
|
42
42
|
"tsdown": "^0.21.0",
|
|
43
43
|
"typescript": "^5",
|
|
44
|
+
"undici": "^7.28.0",
|
|
44
45
|
"vitest": "^4.0.18"
|
|
45
46
|
},
|
|
46
47
|
"peerDependencies": {
|
|
47
48
|
"@types/mssql": ">=9",
|
|
48
|
-
"mssql": ">=11 <13"
|
|
49
|
+
"mssql": ">=11 <13",
|
|
50
|
+
"undici": ">=6 <8"
|
|
49
51
|
},
|
|
50
52
|
"peerDependenciesMeta": {
|
|
51
53
|
"@types/mssql": {
|
|
@@ -53,6 +55,9 @@
|
|
|
53
55
|
},
|
|
54
56
|
"mssql": {
|
|
55
57
|
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"undici": {
|
|
60
|
+
"optional": true
|
|
56
61
|
}
|
|
57
62
|
},
|
|
58
63
|
"scripts": {
|