@allisall/sdk 0.1.0 → 0.4.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/README.md +145 -4
- package/dist/client.d.ts +87 -37
- package/dist/client.js +144 -73
- package/dist/generated/openapi.d.ts +9497 -0
- package/dist/generated/openapi.js +4 -0
- package/dist/generated/operations.d.ts +42 -0
- package/dist/generated/operations.js +1167 -0
- package/dist/import-mapping.d.ts +10 -0
- package/dist/import-mapping.js +20 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +2 -0
- package/dist/ingest.d.ts +1 -1
- package/dist/ingest.js +2 -2
- package/dist/money.d.ts +57 -0
- package/dist/money.js +284 -0
- package/dist/multipart.d.ts +29 -0
- package/dist/multipart.js +48 -0
- package/dist/options.d.ts +50 -0
- package/dist/options.js +73 -0
- package/dist/pagination.d.ts +55 -0
- package/dist/pagination.js +85 -0
- package/dist/request.d.ts +126 -0
- package/dist/request.js +403 -0
- package/dist/resources/agents.d.ts +58 -0
- package/dist/resources/agents.js +72 -0
- package/dist/resources/analytics.d.ts +42 -0
- package/dist/resources/analytics.js +68 -0
- package/dist/resources/api-keys.d.ts +54 -0
- package/dist/resources/api-keys.js +63 -0
- package/dist/resources/calculations.d.ts +98 -0
- package/dist/resources/calculations.js +133 -0
- package/dist/resources/clients.d.ts +93 -0
- package/dist/resources/clients.js +113 -0
- package/dist/resources/cost.d.ts +76 -0
- package/dist/resources/cost.js +96 -0
- package/dist/resources/dashboard.d.ts +32 -0
- package/dist/resources/dashboard.js +36 -0
- package/dist/resources/events.d.ts +116 -0
- package/dist/resources/events.js +145 -0
- package/dist/resources/health.d.ts +14 -0
- package/dist/resources/health.js +17 -0
- package/dist/resources/imports.d.ts +56 -0
- package/dist/resources/imports.js +63 -0
- package/dist/resources/onboarding.d.ts +29 -0
- package/dist/resources/onboarding.js +34 -0
- package/dist/resources/outcome-types.d.ts +25 -0
- package/dist/resources/outcome-types.js +33 -0
- package/dist/resources/reports.d.ts +92 -0
- package/dist/resources/reports.js +120 -0
- package/dist/resources/value-models.d.ts +99 -0
- package/dist/resources/value-models.js +133 -0
- package/dist/resources/workspace.d.ts +15 -0
- package/dist/resources/workspace.js +18 -0
- package/dist/runtime.d.ts +2 -0
- package/dist/runtime.js +2 -1
- package/dist/share.d.ts +52 -0
- package/dist/share.js +60 -0
- package/dist/transport.d.ts +54 -5
- package/dist/transport.js +81 -32
- package/dist/types.d.ts +163 -3
- package/dist/types.js +18 -3
- package/dist/unicode.d.ts +2 -0
- package/dist/unicode.js +11 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// The `valueModels` operations: value models, read by their own id or
|
|
2
|
+
// through the client they belong to, and value templates.
|
|
3
|
+
import { call, callPage } from "../request.js";
|
|
4
|
+
/** The `valueModels` operations. */
|
|
5
|
+
export class ValueModels {
|
|
6
|
+
#config;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.#config = config;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Lists value models. Await the result for one page; iterate it with
|
|
12
|
+
* `for await` for every value model of every page.
|
|
13
|
+
*
|
|
14
|
+
* Pass `page` for a numbered page: its `body` then holds `page` and
|
|
15
|
+
* `total`. Only that first page is counted; every later page of a walk
|
|
16
|
+
* continues by cursor from it, and its `page` and `total` are `null`. To
|
|
17
|
+
* show another numbered page, call this method again with its `page`
|
|
18
|
+
* rather than `getNextPage()`. A `page` past the end has no items, and the
|
|
19
|
+
* walk ends there.
|
|
20
|
+
*
|
|
21
|
+
* Needs the `read` scope. Operation `listValueModels`.
|
|
22
|
+
*/
|
|
23
|
+
list(query, options) {
|
|
24
|
+
return callPage(this.#config, "listValueModels", { query, options });
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Reads a value model by its own id: any model in the workspace,
|
|
28
|
+
* archived ones included.
|
|
29
|
+
*
|
|
30
|
+
* Needs the `read` scope. Operation `getValueModel`.
|
|
31
|
+
*
|
|
32
|
+
* @param id - the value model's id, not its client's.
|
|
33
|
+
*/
|
|
34
|
+
get(id, options) {
|
|
35
|
+
return call(this.#config, "getValueModel", { path: { id }, options });
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Reads a client's active value model.
|
|
39
|
+
*
|
|
40
|
+
* Needs the `read` scope. Operation `getClientValueModel`.
|
|
41
|
+
*
|
|
42
|
+
* @param clientId - the client's id.
|
|
43
|
+
*/
|
|
44
|
+
getForClient(clientId, options) {
|
|
45
|
+
return call(this.#config, "getClientValueModel", {
|
|
46
|
+
path: { id: clientId },
|
|
47
|
+
options,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Creates a client's value model.
|
|
52
|
+
*
|
|
53
|
+
* Every attempt of the call sends one `Idempotency-Key`, so a retry
|
|
54
|
+
* cannot create a second value model.
|
|
55
|
+
*
|
|
56
|
+
* Needs the `write` scope. Operation `createValueModel`.
|
|
57
|
+
*
|
|
58
|
+
* @param clientId - the client's id.
|
|
59
|
+
*/
|
|
60
|
+
createForClient(clientId, body, options) {
|
|
61
|
+
return call(this.#config, "createValueModel", {
|
|
62
|
+
path: { id: clientId },
|
|
63
|
+
body,
|
|
64
|
+
options,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Updates a client's active value model with a merge patch: a member
|
|
69
|
+
* left out keeps its value, `null` clears it, and any other value sets
|
|
70
|
+
* it.
|
|
71
|
+
*
|
|
72
|
+
* Never retried.
|
|
73
|
+
*
|
|
74
|
+
* Needs the `write` scope. Operation `updateValueModel`.
|
|
75
|
+
*
|
|
76
|
+
* @param clientId - the client's id.
|
|
77
|
+
*/
|
|
78
|
+
updateForClient(clientId, patch, options) {
|
|
79
|
+
return call(this.#config, "updateValueModel", {
|
|
80
|
+
path: { id: clientId },
|
|
81
|
+
body: patch,
|
|
82
|
+
options,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Deletes a client's active value model.
|
|
87
|
+
*
|
|
88
|
+
* A retry after a lost response can answer `404` for a delete that
|
|
89
|
+
* succeeded.
|
|
90
|
+
*
|
|
91
|
+
* Needs the `write` scope. Operation `deleteValueModel`.
|
|
92
|
+
*
|
|
93
|
+
* @param clientId - the client's id.
|
|
94
|
+
*/
|
|
95
|
+
deleteForClient(clientId, options) {
|
|
96
|
+
return call(this.#config, "deleteValueModel", {
|
|
97
|
+
path: { id: clientId },
|
|
98
|
+
options,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Replaces the line items of a client's active value model, as a whole
|
|
103
|
+
* set. It is guarded by `version`: a stale version is refused rather
|
|
104
|
+
* than overwriting a co-editor's change.
|
|
105
|
+
*
|
|
106
|
+
* Each `unit_value` takes a `Decimal` or a `number`.
|
|
107
|
+
*
|
|
108
|
+
* Never retried, because the `version` guard refuses a repeat of a write
|
|
109
|
+
* that already landed as a stale version. On a `ConnectionError` the
|
|
110
|
+
* write may have landed: read the value model again for its current
|
|
111
|
+
* `version` before trying again.
|
|
112
|
+
*
|
|
113
|
+
* Needs the `write` scope. Operation `setValueModelLineItems`.
|
|
114
|
+
*
|
|
115
|
+
* @param clientId - the client's id.
|
|
116
|
+
*/
|
|
117
|
+
setLineItemsForClient(clientId, body, options) {
|
|
118
|
+
return call(this.#config, "setValueModelLineItems", {
|
|
119
|
+
path: { id: clientId },
|
|
120
|
+
body,
|
|
121
|
+
options,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Lists value templates. Await the result for one page; iterate it with
|
|
126
|
+
* `for await` for every template of every page.
|
|
127
|
+
*
|
|
128
|
+
* Needs the `read` scope. Operation `listValueTemplates`.
|
|
129
|
+
*/
|
|
130
|
+
listTemplates(query, options) {
|
|
131
|
+
return callPage(this.#config, "listValueTemplates", { query, options });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RequestOptions } from "../options.js";
|
|
2
|
+
import type { TransportConfig } from "../transport.js";
|
|
3
|
+
import type { Workspace } from "../types.js";
|
|
4
|
+
/** The `workspace` operations. */
|
|
5
|
+
export declare class WorkspaceOperations {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(config: TransportConfig);
|
|
8
|
+
/**
|
|
9
|
+
* Reads the workspace the credential belongs to: its name, its currency
|
|
10
|
+
* and when it was created.
|
|
11
|
+
*
|
|
12
|
+
* Needs the `read` scope. Operation `getWorkspace`.
|
|
13
|
+
*/
|
|
14
|
+
get(options?: RequestOptions): Promise<Workspace>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// The `workspace` operations.
|
|
2
|
+
import { call } from "../request.js";
|
|
3
|
+
/** The `workspace` operations. */
|
|
4
|
+
export class WorkspaceOperations {
|
|
5
|
+
#config;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.#config = config;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Reads the workspace the credential belongs to: its name, its currency
|
|
11
|
+
* and when it was created.
|
|
12
|
+
*
|
|
13
|
+
* Needs the `read` scope. Operation `getWorkspace`.
|
|
14
|
+
*/
|
|
15
|
+
get(options) {
|
|
16
|
+
return call(this.#config, "getWorkspace", { options });
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Reads a global by name, so a runtime without it is never touched. */
|
|
2
|
+
export declare function globalValue(name: string): unknown;
|
|
1
3
|
/**
|
|
2
4
|
* Reports whether the SDK runs in a browser: a window, where `document`
|
|
3
5
|
* is defined, or a Web Worker, where `globalThis` is an instance of
|
package/dist/runtime.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// `globalThis` by name, so a runtime without them, such as a browser
|
|
4
4
|
// without `process`, is never touched.
|
|
5
5
|
import { VERSION } from "./version.js";
|
|
6
|
-
|
|
6
|
+
/** Reads a global by name, so a runtime without it is never touched. */
|
|
7
|
+
export function globalValue(name) {
|
|
7
8
|
const value = Reflect.get(globalThis, name);
|
|
8
9
|
return value;
|
|
9
10
|
}
|
package/dist/share.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type FetchLike } from "./transport.js";
|
|
2
|
+
import type { PublicReport } from "./types.js";
|
|
3
|
+
/** The options of `getSharedReport`. Every one is optional. */
|
|
4
|
+
export interface SharedReportOptions {
|
|
5
|
+
/**
|
|
6
|
+
* The API's base URL, absolute, `http` or `https`. Defaults to
|
|
7
|
+
* `AIA_BASE_URL` from the environment, then `https://api.allisai.ai`.
|
|
8
|
+
* In a browser it may also be a path on the page's own origin, starting
|
|
9
|
+
* with a single `/`.
|
|
10
|
+
*/
|
|
11
|
+
baseURL?: string | undefined;
|
|
12
|
+
/** The timeout of each attempt, in milliseconds. Defaults to 60,000. */
|
|
13
|
+
timeout?: number | undefined;
|
|
14
|
+
/** How many times a failure worth repeating is retried. Defaults to 2; 0 turns retries off. */
|
|
15
|
+
maxRetries?: number | undefined;
|
|
16
|
+
/** The `fetch` to send the request with. Defaults to the platform's own. */
|
|
17
|
+
fetch?: FetchLike | undefined;
|
|
18
|
+
/** Cancels the call, including any wait between retries. */
|
|
19
|
+
signal?: AbortSignal | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Extra headers to send. They may not name `Authorization`,
|
|
22
|
+
* `Content-Type`, `Idempotency-Key` or `X-Request-Id`, which the SDK sets.
|
|
23
|
+
*/
|
|
24
|
+
headers?: Readonly<Record<string, string>> | undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Reads a shared report with its share token, as the report's recipient
|
|
28
|
+
* sees it. Needs no API key and no session, and never reads `AIA_API_KEY`.
|
|
29
|
+
*
|
|
30
|
+
* A share token is a bearer capability: anyone holding it can read the
|
|
31
|
+
* report. Handle it like a secret, and keep it out of URLs and logs. The
|
|
32
|
+
* SDK sends it only in the `Authorization` header, as
|
|
33
|
+
* `Authorization: Report <token>`, and keeps it for this call alone.
|
|
34
|
+
*
|
|
35
|
+
* Every failure, a missing, unknown or revoked token included, is the same
|
|
36
|
+
* `404`, as an `ApiError`. Each read stamps the report's view times, and a
|
|
37
|
+
* retry after a lost response stamps them again.
|
|
38
|
+
*
|
|
39
|
+
* Operation `getSharedReport`.
|
|
40
|
+
*
|
|
41
|
+
* @param shareToken - the report's share token.
|
|
42
|
+
* @throws {AllisallError} when the share token is empty or holds a
|
|
43
|
+
* character other than printable ASCII, or an option is one a client would
|
|
44
|
+
* refuse, before anything is sent.
|
|
45
|
+
* @throws {TypeError} when `options.headers` is not a plain object of
|
|
46
|
+
* header names and string values, or names a header the SDK sets, before
|
|
47
|
+
* anything is sent.
|
|
48
|
+
* @throws {ApiError} for an error response, such as the `404` for a token
|
|
49
|
+
* that reads no report.
|
|
50
|
+
* @throws {ConnectionError} when no response arrived, after any retries.
|
|
51
|
+
*/
|
|
52
|
+
export declare function getSharedReport(shareToken: string, options?: SharedReportOptions): Promise<PublicReport>;
|
package/dist/share.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Reading a shared report with its share token. It is a function, not a
|
|
2
|
+
// client method: its caller, such as a server rendering the report's page,
|
|
3
|
+
// holds no API key and no session, which a client requires.
|
|
4
|
+
import { transportSettings } from "./client.js";
|
|
5
|
+
import { AllisallError } from "./errors.js";
|
|
6
|
+
import { request } from "./request.js";
|
|
7
|
+
import { isBrowser } from "./runtime.js";
|
|
8
|
+
import { isPrintableCredential, } from "./transport.js";
|
|
9
|
+
/** Trims a share token and checks it can travel in a header. */
|
|
10
|
+
function checkedShareToken(shareToken) {
|
|
11
|
+
const token = typeof shareToken === "string" ? shareToken.trim() : "";
|
|
12
|
+
if (token === "") {
|
|
13
|
+
throw new AllisallError("allisall: the share token is empty");
|
|
14
|
+
}
|
|
15
|
+
if (!isPrintableCredential(token)) {
|
|
16
|
+
throw new AllisallError("allisall: the share token holds a character other than printable ASCII, which cannot be sent");
|
|
17
|
+
}
|
|
18
|
+
return token;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Reads a shared report with its share token, as the report's recipient
|
|
22
|
+
* sees it. Needs no API key and no session, and never reads `AIA_API_KEY`.
|
|
23
|
+
*
|
|
24
|
+
* A share token is a bearer capability: anyone holding it can read the
|
|
25
|
+
* report. Handle it like a secret, and keep it out of URLs and logs. The
|
|
26
|
+
* SDK sends it only in the `Authorization` header, as
|
|
27
|
+
* `Authorization: Report <token>`, and keeps it for this call alone.
|
|
28
|
+
*
|
|
29
|
+
* Every failure, a missing, unknown or revoked token included, is the same
|
|
30
|
+
* `404`, as an `ApiError`. Each read stamps the report's view times, and a
|
|
31
|
+
* retry after a lost response stamps them again.
|
|
32
|
+
*
|
|
33
|
+
* Operation `getSharedReport`.
|
|
34
|
+
*
|
|
35
|
+
* @param shareToken - the report's share token.
|
|
36
|
+
* @throws {AllisallError} when the share token is empty or holds a
|
|
37
|
+
* character other than printable ASCII, or an option is one a client would
|
|
38
|
+
* refuse, before anything is sent.
|
|
39
|
+
* @throws {TypeError} when `options.headers` is not a plain object of
|
|
40
|
+
* header names and string values, or names a header the SDK sets, before
|
|
41
|
+
* anything is sent.
|
|
42
|
+
* @throws {ApiError} for an error response, such as the `404` for a token
|
|
43
|
+
* that reads no report.
|
|
44
|
+
* @throws {ConnectionError} when no response arrived, after any retries.
|
|
45
|
+
*/
|
|
46
|
+
export async function getSharedReport(shareToken, options = {}) {
|
|
47
|
+
const token = checkedShareToken(shareToken);
|
|
48
|
+
const config = {
|
|
49
|
+
...transportSettings(options, isBrowser()),
|
|
50
|
+
// The share token is the call's only credential, and it is not an API
|
|
51
|
+
// key, so no browser opt-in applies. No client sends default headers.
|
|
52
|
+
credential: undefined,
|
|
53
|
+
refuseApiKeys: false,
|
|
54
|
+
defaultHeaders: {},
|
|
55
|
+
};
|
|
56
|
+
return request(config, "getSharedReport", {
|
|
57
|
+
shareToken: token,
|
|
58
|
+
options: { signal: options.signal, headers: options.headers },
|
|
59
|
+
});
|
|
60
|
+
}
|
package/dist/transport.d.ts
CHANGED
|
@@ -14,7 +14,11 @@ export type Credential = {
|
|
|
14
14
|
/** Everything an attempt needs, fixed when the client is constructed. */
|
|
15
15
|
export interface TransportConfig {
|
|
16
16
|
readonly baseURL: URL;
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The client's credential. Absent for a shared report, whose call carries
|
|
19
|
+
* its own share token and sends no client credential.
|
|
20
|
+
*/
|
|
21
|
+
readonly credential: Credential | undefined;
|
|
18
22
|
readonly timeout: number;
|
|
19
23
|
readonly maxRetries: number;
|
|
20
24
|
readonly fetch: FetchLike | undefined;
|
|
@@ -22,6 +26,8 @@ export interface TransportConfig {
|
|
|
22
26
|
readonly refuseApiKeys: boolean;
|
|
23
27
|
/** The `User-Agent` to send; `undefined` in a browser. */
|
|
24
28
|
readonly userAgent: string | undefined;
|
|
29
|
+
/** Headers sent on every request, already checked. */
|
|
30
|
+
readonly defaultHeaders: Readonly<Record<string, string>>;
|
|
25
31
|
}
|
|
26
32
|
/** The message of the error refusing an API key in a browser. */
|
|
27
33
|
export declare const BROWSER_KEY_MESSAGE: string;
|
|
@@ -46,8 +52,51 @@ export declare function isPrintableCredential(credential: string): boolean;
|
|
|
46
52
|
*/
|
|
47
53
|
export declare function readProblem(contentType: string | null, text: string): Problem | undefined;
|
|
48
54
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* a fresh `X-Request-Id`.
|
|
55
|
+
* How a successful body is read. `undefined` means the body was not the
|
|
56
|
+
* expected shape, which is an `ApiError` carrying the status.
|
|
52
57
|
*/
|
|
53
|
-
export
|
|
58
|
+
export type ReadBody<T> = (text: string) => {
|
|
59
|
+
readonly value: T;
|
|
60
|
+
} | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* The `Authorization` a request sends: the client's credential as a bearer,
|
|
63
|
+
* none, or a share token under the `Report` scheme.
|
|
64
|
+
*/
|
|
65
|
+
export type Authorization = "bearer" | "none" | {
|
|
66
|
+
readonly report: string;
|
|
67
|
+
};
|
|
68
|
+
/** One request, fixed before the first attempt and sent unchanged on each. */
|
|
69
|
+
export interface Outgoing {
|
|
70
|
+
readonly method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
71
|
+
readonly url: string;
|
|
72
|
+
readonly authorization: Authorization;
|
|
73
|
+
/** The body: bytes, or a form whose boundary `fetch` writes. */
|
|
74
|
+
readonly body: Uint8Array | FormData | undefined;
|
|
75
|
+
/** The body's `Content-Type`; `undefined` for no body, or a form. */
|
|
76
|
+
readonly contentType: string | undefined;
|
|
77
|
+
/** The `Idempotency-Key` sent on every attempt, if any. */
|
|
78
|
+
readonly idempotencyKey: string | undefined;
|
|
79
|
+
}
|
|
80
|
+
/** What one call may do, from its operation and its options. */
|
|
81
|
+
export interface CallPolicy {
|
|
82
|
+
readonly timeout: number;
|
|
83
|
+
readonly maxRetries: number;
|
|
84
|
+
/** The call's headers, already checked. */
|
|
85
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
86
|
+
readonly signal: AbortSignal | undefined;
|
|
87
|
+
/** Whether a retry cannot apply the request twice, so it may be retried at all. */
|
|
88
|
+
readonly eligible: boolean;
|
|
89
|
+
/** Whether a `409` `idempotency_key_in_use` is retried: the request carries a reused key. */
|
|
90
|
+
readonly retryKeyInUse: boolean;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Sends a request, retrying a failure worth repeating when the request is
|
|
94
|
+
* eligible. The body is the same on every attempt. Each attempt reads a
|
|
95
|
+
* fresh token and sends a fresh `X-Request-Id`.
|
|
96
|
+
*/
|
|
97
|
+
export declare function send<T>(config: TransportConfig, out: Outgoing, read: ReadBody<T>, policy: CallPolicy): Promise<T>;
|
|
98
|
+
/**
|
|
99
|
+
* Posts a JSON body that a retry cannot apply twice, such as an ingest
|
|
100
|
+
* batch, retrying a failure worth repeating.
|
|
101
|
+
*/
|
|
102
|
+
export declare function post<T>(config: TransportConfig, path: string, body: Uint8Array, read: (body: unknown) => T | undefined, policy: Omit<CallPolicy, "eligible" | "retryKeyInUse">): Promise<T>;
|
package/dist/transport.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// One HTTP attempt (headers, timeout, reading the response) and the loop
|
|
2
|
-
// that repeats it under the retry rules in retry.ts
|
|
2
|
+
// that repeats it under the retry rules in retry.ts, for any method and
|
|
3
|
+
// any body.
|
|
3
4
|
import { AllisallError, ApiError, ConnectionError } from "./errors.js";
|
|
5
|
+
import { mergeHeaders } from "./options.js";
|
|
4
6
|
import { backoffMs, isRetryableStatus, MAX_RETRY_AFTER_MS, parseRetryAfter, sleep, } from "./retry.js";
|
|
5
7
|
import { uuidv4 } from "./uuid.js";
|
|
6
8
|
/** The message of the error refusing an API key in a browser. */
|
|
@@ -33,6 +35,9 @@ export function isPrintableCredential(credential) {
|
|
|
33
35
|
}
|
|
34
36
|
async function bearerToken(config) {
|
|
35
37
|
const { credential } = config;
|
|
38
|
+
if (credential === undefined) {
|
|
39
|
+
throw new AllisallError("allisall: this call has no client credential");
|
|
40
|
+
}
|
|
36
41
|
if (credential.kind === "apiKey")
|
|
37
42
|
return credential.apiKey;
|
|
38
43
|
const raw = await credential.provider();
|
|
@@ -121,33 +126,41 @@ function headerOrUndefined(headers, name) {
|
|
|
121
126
|
return headers.get(name) ?? undefined;
|
|
122
127
|
}
|
|
123
128
|
/**
|
|
124
|
-
* Sends one attempt. Resolves with the
|
|
129
|
+
* Sends one attempt. Resolves with the read `2xx` body; throws an
|
|
125
130
|
* `ApiError` for any other status or an unreadable `2xx` body, a
|
|
126
131
|
* `ConnectionError` when no response arrived, or the caller's abort reason.
|
|
127
132
|
*/
|
|
128
|
-
async function attempt(config,
|
|
129
|
-
const headers = {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
async function attempt(config, out, read, authorization, requestId, policy) {
|
|
134
|
+
const headers = {};
|
|
135
|
+
if (authorization !== undefined)
|
|
136
|
+
headers.Authorization = authorization;
|
|
137
|
+
if (out.contentType !== undefined)
|
|
138
|
+
headers["Content-Type"] = out.contentType;
|
|
139
|
+
headers["X-Request-Id"] = requestId;
|
|
134
140
|
if (config.userAgent !== undefined)
|
|
135
141
|
headers["User-Agent"] = config.userAgent;
|
|
136
|
-
|
|
142
|
+
if (out.idempotencyKey !== undefined) {
|
|
143
|
+
headers["Idempotency-Key"] = out.idempotencyKey;
|
|
144
|
+
}
|
|
145
|
+
mergeHeaders(headers, config.defaultHeaders);
|
|
146
|
+
mergeHeaders(headers, policy.headers);
|
|
147
|
+
const { signal } = policy;
|
|
148
|
+
const timeout = AbortSignal.timeout(policy.timeout);
|
|
137
149
|
const combined = signal === undefined ? timeout : AbortSignal.any([signal, timeout]);
|
|
138
150
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
151
|
+
const init = {
|
|
152
|
+
method: out.method,
|
|
153
|
+
headers,
|
|
154
|
+
...(out.body === undefined ? {} : { body: out.body }),
|
|
155
|
+
signal: combined,
|
|
156
|
+
// A redirect is never followed, so Authorization never travels to
|
|
157
|
+
// another location.
|
|
158
|
+
redirect: "manual",
|
|
159
|
+
};
|
|
139
160
|
let response;
|
|
140
161
|
let text;
|
|
141
162
|
try {
|
|
142
|
-
response = await doFetch(url,
|
|
143
|
-
method: "POST",
|
|
144
|
-
headers,
|
|
145
|
-
body,
|
|
146
|
-
signal: combined,
|
|
147
|
-
// A redirect is never followed, so Authorization never travels to
|
|
148
|
-
// another location.
|
|
149
|
-
redirect: "manual",
|
|
150
|
-
});
|
|
163
|
+
response = await doFetch(out.url, init);
|
|
151
164
|
text = await response.text();
|
|
152
165
|
}
|
|
153
166
|
catch (err) {
|
|
@@ -167,10 +180,9 @@ async function attempt(config, url, body, read, token, requestId, signal) {
|
|
|
167
180
|
});
|
|
168
181
|
}
|
|
169
182
|
if (status >= 200 && status < 300) {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return value;
|
|
183
|
+
const result = read(text);
|
|
184
|
+
if (result !== undefined)
|
|
185
|
+
return result.value;
|
|
174
186
|
throw new ApiError({
|
|
175
187
|
status,
|
|
176
188
|
headers: response.headers,
|
|
@@ -186,24 +198,43 @@ async function attempt(config, url, body, read, token, requestId, signal) {
|
|
|
186
198
|
headers: response.headers,
|
|
187
199
|
});
|
|
188
200
|
}
|
|
201
|
+
function worthRepeating(err, policy) {
|
|
202
|
+
if (err instanceof ConnectionError)
|
|
203
|
+
return true;
|
|
204
|
+
if (isRetryableStatus(err.status))
|
|
205
|
+
return true;
|
|
206
|
+
// The first request with this key is still running; a later attempt can
|
|
207
|
+
// collect its outcome.
|
|
208
|
+
return (policy.retryKeyInUse &&
|
|
209
|
+
err.status === 409 &&
|
|
210
|
+
err.code === "idempotency_key_in_use");
|
|
211
|
+
}
|
|
212
|
+
/** The `Authorization` value of one attempt, reading a fresh bearer token. */
|
|
213
|
+
async function authorizationOf(config, out) {
|
|
214
|
+
const { authorization } = out;
|
|
215
|
+
if (authorization === "none")
|
|
216
|
+
return undefined;
|
|
217
|
+
if (authorization === "bearer")
|
|
218
|
+
return `Bearer ${await bearerToken(config)}`;
|
|
219
|
+
return `Report ${authorization.report}`;
|
|
220
|
+
}
|
|
189
221
|
/**
|
|
190
|
-
*
|
|
191
|
-
* same
|
|
192
|
-
* a fresh `X-Request-Id`.
|
|
222
|
+
* Sends a request, retrying a failure worth repeating when the request is
|
|
223
|
+
* eligible. The body is the same on every attempt. Each attempt reads a
|
|
224
|
+
* fresh token and sends a fresh `X-Request-Id`.
|
|
193
225
|
*/
|
|
194
|
-
export async function
|
|
195
|
-
const
|
|
226
|
+
export async function send(config, out, read, policy) {
|
|
227
|
+
const maxRetries = policy.eligible ? policy.maxRetries : 0;
|
|
196
228
|
for (let retry = 0;; retry++) {
|
|
197
229
|
// An error from the token provider ends the call unwrapped and unretried.
|
|
198
|
-
const
|
|
230
|
+
const authorization = await authorizationOf(config, out);
|
|
199
231
|
try {
|
|
200
|
-
return await attempt(config,
|
|
232
|
+
return await attempt(config, out, read, authorization, uuidv4(), policy);
|
|
201
233
|
}
|
|
202
234
|
catch (err) {
|
|
203
235
|
if (!(err instanceof ApiError || err instanceof ConnectionError))
|
|
204
236
|
throw err;
|
|
205
|
-
|
|
206
|
-
if (!retryable || retry >= config.maxRetries)
|
|
237
|
+
if (!worthRepeating(err, policy) || retry >= maxRetries)
|
|
207
238
|
throw err;
|
|
208
239
|
let wait;
|
|
209
240
|
if (err instanceof ApiError && err.retryAfter !== undefined) {
|
|
@@ -214,7 +245,25 @@ export async function post(config, path, body, read, signal) {
|
|
|
214
245
|
else {
|
|
215
246
|
wait = backoffMs(retry + 1);
|
|
216
247
|
}
|
|
217
|
-
await sleep(wait, signal);
|
|
248
|
+
await sleep(wait, policy.signal);
|
|
218
249
|
}
|
|
219
250
|
}
|
|
220
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Posts a JSON body that a retry cannot apply twice, such as an ingest
|
|
254
|
+
* batch, retrying a failure worth repeating.
|
|
255
|
+
*/
|
|
256
|
+
export function post(config, path, body, read, policy) {
|
|
257
|
+
return send(config, {
|
|
258
|
+
method: "POST",
|
|
259
|
+
url: joinURL(config.baseURL, path),
|
|
260
|
+
authorization: "bearer",
|
|
261
|
+
body,
|
|
262
|
+
contentType: "application/json",
|
|
263
|
+
idempotencyKey: undefined,
|
|
264
|
+
}, (text) => {
|
|
265
|
+
const parsed = parseJSON(text);
|
|
266
|
+
const value = parsed === undefined ? undefined : read(parsed);
|
|
267
|
+
return value === undefined ? undefined : { value };
|
|
268
|
+
}, { ...policy, eligible: true, retryKeyInUse: false });
|
|
269
|
+
}
|