@allisall/sdk 0.1.0 → 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.
Files changed (60) hide show
  1. package/README.md +142 -4
  2. package/dist/client.d.ts +81 -37
  3. package/dist/client.js +136 -73
  4. package/dist/generated/openapi.d.ts +7724 -0
  5. package/dist/generated/openapi.js +4 -0
  6. package/dist/generated/operations.d.ts +37 -0
  7. package/dist/generated/operations.js +976 -0
  8. package/dist/index.d.ts +7 -2
  9. package/dist/index.js +1 -0
  10. package/dist/ingest.d.ts +1 -1
  11. package/dist/ingest.js +2 -2
  12. package/dist/money.d.ts +57 -0
  13. package/dist/money.js +284 -0
  14. package/dist/multipart.d.ts +27 -0
  15. package/dist/multipart.js +48 -0
  16. package/dist/options.d.ts +50 -0
  17. package/dist/options.js +73 -0
  18. package/dist/pagination.d.ts +55 -0
  19. package/dist/pagination.js +85 -0
  20. package/dist/request.d.ts +117 -0
  21. package/dist/request.js +363 -0
  22. package/dist/resources/agents.d.ts +51 -0
  23. package/dist/resources/agents.js +65 -0
  24. package/dist/resources/analytics.d.ts +42 -0
  25. package/dist/resources/analytics.js +68 -0
  26. package/dist/resources/api-keys.d.ts +54 -0
  27. package/dist/resources/api-keys.js +63 -0
  28. package/dist/resources/calculations.d.ts +78 -0
  29. package/dist/resources/calculations.js +111 -0
  30. package/dist/resources/clients.d.ts +65 -0
  31. package/dist/resources/clients.js +85 -0
  32. package/dist/resources/cost.d.ts +69 -0
  33. package/dist/resources/cost.js +89 -0
  34. package/dist/resources/dashboard.d.ts +24 -0
  35. package/dist/resources/dashboard.js +28 -0
  36. package/dist/resources/events.d.ts +47 -0
  37. package/dist/resources/events.js +69 -0
  38. package/dist/resources/health.d.ts +14 -0
  39. package/dist/resources/health.js +17 -0
  40. package/dist/resources/imports.d.ts +33 -0
  41. package/dist/resources/imports.js +37 -0
  42. package/dist/resources/outcome-types.d.ts +25 -0
  43. package/dist/resources/outcome-types.js +33 -0
  44. package/dist/resources/reports.d.ts +77 -0
  45. package/dist/resources/reports.js +103 -0
  46. package/dist/resources/value-models.d.ts +92 -0
  47. package/dist/resources/value-models.js +126 -0
  48. package/dist/runtime.d.ts +2 -0
  49. package/dist/runtime.js +2 -1
  50. package/dist/share.d.ts +52 -0
  51. package/dist/share.js +60 -0
  52. package/dist/transport.d.ts +54 -5
  53. package/dist/transport.js +81 -32
  54. package/dist/types.d.ts +135 -2
  55. package/dist/types.js +12 -3
  56. package/dist/unicode.d.ts +2 -0
  57. package/dist/unicode.js +11 -0
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +1 -1
  60. package/package.json +4 -3
@@ -0,0 +1,85 @@
1
+ // A list call's result: a page, and the walk over every following page.
2
+ import { AllisallError } from "./errors.js";
3
+ /**
4
+ * One page of a list. `data` holds its items and `body` the whole response,
5
+ * such as the event drill-down's `total`. Iterate it with `for await` to
6
+ * walk its items and then every following page's.
7
+ */
8
+ export class Page {
9
+ /** The page's items. */
10
+ data;
11
+ /** The whole decoded response. */
12
+ body;
13
+ #next;
14
+ /** Pages are made by list methods; you do not construct one. */
15
+ constructor(body, next) {
16
+ this.body = body;
17
+ // The envelope was checked as an array of the list's items when the
18
+ // response was read.
19
+ this.data = body.data;
20
+ this.#next = next;
21
+ }
22
+ /** Whether another page follows this one: the response's `has_more`. */
23
+ hasNextPage() {
24
+ return this.#next !== undefined;
25
+ }
26
+ /**
27
+ * Fetches the next page, with the same query and options as this one.
28
+ * `options` apply to this request alone, over the call's.
29
+ *
30
+ * @throws {AllisallError} when this is the last page.
31
+ */
32
+ getNextPage(options) {
33
+ if (this.#next === undefined) {
34
+ return Promise.reject(new AllisallError("allisall: this is the last page; no page follows it"));
35
+ }
36
+ return this.#next(options);
37
+ }
38
+ /** Yields this page's items, then each following page's, fetching each page as it is reached. */
39
+ async *[Symbol.asyncIterator]() {
40
+ let page = this;
41
+ for (;;) {
42
+ yield* page.data;
43
+ if (!page.hasNextPage())
44
+ return;
45
+ page = await page.getNextPage();
46
+ }
47
+ }
48
+ }
49
+ /**
50
+ * The result of a list method. Await it for the first `Page`; iterate it
51
+ * with `for await` for every item of every page.
52
+ *
53
+ * Nothing is sent until it is awaited or iterated, so a list call whose
54
+ * result is never used sends no request.
55
+ */
56
+ export class PagePromise {
57
+ #load;
58
+ #first;
59
+ /** Made by list methods; you do not construct one. */
60
+ constructor(load) {
61
+ this.#load = load;
62
+ }
63
+ /** The first page, requested once, on first use. */
64
+ #page() {
65
+ this.#first ??= this.#load();
66
+ return this.#first;
67
+ }
68
+ /** Resolves with the first page, sending its request on first use. */
69
+ // biome-ignore lint/suspicious/noThenProperty: a PagePromise is awaitable by design, so it must have then.
70
+ then(onfulfilled, onrejected) {
71
+ return this.#page().then(onfulfilled, onrejected);
72
+ }
73
+ /** As `Promise.prototype.catch`, on the first page. */
74
+ catch(onrejected) {
75
+ return this.#page().catch(onrejected);
76
+ }
77
+ /** As `Promise.prototype.finally`, on the first page. */
78
+ finally(onfinally) {
79
+ return this.#page().finally(onfinally);
80
+ }
81
+ /** Yields every item of every page, from the first. */
82
+ async *[Symbol.asyncIterator]() {
83
+ yield* await this.#page();
84
+ }
85
+ }
@@ -0,0 +1,117 @@
1
+ import type { operations } from "./generated/openapi.js";
2
+ import { type Operation, type OperationId } from "./generated/operations.js";
3
+ import { type MoneyInput } from "./money.js";
4
+ import { type ImportUpload } from "./multipart.js";
5
+ import { type IdempotentRequestOptions, type RequestOptions } from "./options.js";
6
+ import { type ListBody, PagePromise } from "./pagination.js";
7
+ import { type CallPolicy, type TransportConfig } from "./transport.js";
8
+ import type { QueryOf } from "./types.js";
9
+ type Responses<K extends OperationId> = operations[K]["responses"];
10
+ type SuccessStatus = 200 | 201 | 202 | 204;
11
+ /**
12
+ * The decoded body of an operation's success response, from the generated
13
+ * types: `undefined` for a response without a body.
14
+ */
15
+ export type SuccessBody<K extends OperationId> = Responses<K>[Extract<keyof Responses<K>, SuccessStatus>] extends {
16
+ content: {
17
+ "application/json": infer B;
18
+ };
19
+ } ? B : undefined;
20
+ /** What one call sends, beyond what the operation table fixes. */
21
+ export interface CallArgs {
22
+ /** The path parameters, by the name the path template gives each. */
23
+ readonly path?: Readonly<Record<string, string>> | undefined;
24
+ /** The query, written member by member in the object's own order. */
25
+ readonly query?: object | undefined;
26
+ /** The body: JSON for a JSON or merge-patch operation, an upload for a multipart one. */
27
+ readonly body?: unknown;
28
+ readonly options?: IdempotentRequestOptions | undefined;
29
+ /**
30
+ * The share token of a shared report, already checked. Only the shared
31
+ * report's operation takes one, and it takes nothing else.
32
+ */
33
+ readonly shareToken?: string | undefined;
34
+ }
35
+ /**
36
+ * Whether a retry cannot apply the request twice: a method RFC 9110 defines
37
+ * as idempotent, an operation sent with an `Idempotency-Key`, or ingest,
38
+ * whose `event_id`s deduplicate. The version-guarded `PUT` is the exception.
39
+ */
40
+ export declare function isEligible(id: OperationId, operation: Operation): boolean;
41
+ /**
42
+ * What one call may do: its operation's retry eligibility, and the call's
43
+ * options resolved over the client's.
44
+ *
45
+ * @throws {AllisallError} for a `timeout` or `maxRetries` the client would refuse.
46
+ * @throws {TypeError} for headers that are not a plain object of headers, or
47
+ * that name a header the SDK sets.
48
+ */
49
+ export declare function policyOf(config: TransportConfig, id: OperationId, operation: Operation, options: RequestOptions): CallPolicy;
50
+ /**
51
+ * Calls one operation and resolves with its decoded success body.
52
+ *
53
+ * Everything the call sends is fixed before the first attempt: its URL, its
54
+ * body's bytes and its `Idempotency-Key`. Retries follow the operation's
55
+ * eligibility. A `204` resolves with `undefined`.
56
+ */
57
+ export declare function request<K extends OperationId>(config: TransportConfig, id: K, args?: CallArgs): Promise<SuccessBody<K>>;
58
+ /** The item type of a list operation's page. */
59
+ type ItemOf<B> = B extends {
60
+ readonly data: readonly (infer I)[];
61
+ } ? I : never;
62
+ /**
63
+ * Calls a list operation lazily: nothing is sent until the result is
64
+ * awaited or iterated. Each following page sends the same query with
65
+ * `cursor` set to the previous page's `next_cursor` and without `page`.
66
+ *
67
+ * The walk remembers every cursor it has sent since the list call, with
68
+ * the position of the page each requested; a cursor the caller's own query
69
+ * held requested the first. A page with `has_more` whose `next_cursor` was
70
+ * sent for a page at another position would walk in a cycle, so it is
71
+ * unreadable: an `ApiError` with the page's status and no problem. Walking
72
+ * again from an earlier page sends each cursor for its own position, and is
73
+ * not refused.
74
+ */
75
+ export declare function requestPage<K extends OperationId>(config: TransportConfig, id: K, args?: CallArgs): PagePromise<ItemOf<SuccessBody<K>>, Extract<SuccessBody<K>, ListBody>>;
76
+ /**
77
+ * The arguments of one page's request: the call's own, with the query and
78
+ * options paging sets for that page. Every other member is carried forward.
79
+ */
80
+ export declare function pageArgs(args: CallArgs, query: object | undefined, options: RequestOptions): CallArgs;
81
+ /** An operation's path parameters, from the generated types. */
82
+ type PathOf<K extends OperationId> = NonNullable<operations[K]["parameters"]["path"]>;
83
+ /**
84
+ * The query member of an operation's arguments: required when the
85
+ * generated types require the query, which they do when any one of its
86
+ * parameters is required, and optional otherwise.
87
+ */
88
+ type QueryArgs<K extends OperationId> = undefined extends operations[K]["parameters"]["query"] ? {
89
+ readonly query?: QueryOf<K> | undefined;
90
+ } : {
91
+ readonly query: QueryOf<K>;
92
+ };
93
+ /**
94
+ * The body an operation takes, from the generated types: an `ImportUpload`
95
+ * for a form, and otherwise the JSON body with its money widened.
96
+ */
97
+ type BodyOf<K extends OperationId> = operations[K] extends {
98
+ requestBody: {
99
+ content: infer C;
100
+ };
101
+ } ? "multipart/form-data" extends keyof C ? ImportUpload : MoneyInput<C[keyof C]> : never;
102
+ /**
103
+ * What a resource method passes for one operation, typed from the generated
104
+ * types. A change to the API that retypes an operation's path, query or
105
+ * body, or that makes its query required, fails the compile of the method
106
+ * that calls it.
107
+ */
108
+ export type OperationArgs<K extends OperationId> = {
109
+ readonly path?: PathOf<K>;
110
+ readonly body?: BodyOf<K>;
111
+ readonly options?: IdempotentRequestOptions | undefined;
112
+ } & QueryArgs<K>;
113
+ /** Calls one operation with arguments typed for it; see `request`. */
114
+ export declare function call<K extends OperationId>(config: TransportConfig, id: K, args: OperationArgs<K>): Promise<SuccessBody<K>>;
115
+ /** Calls one list operation with arguments typed for it; see `requestPage`. */
116
+ export declare function callPage<K extends OperationId>(config: TransportConfig, id: K, args: OperationArgs<K>): PagePromise<ItemOf<SuccessBody<K>>, Extract<SuccessBody<K>, ListBody>>;
117
+ export {};
@@ -0,0 +1,363 @@
1
+ // One operation, driven by the operation table: the URL and its query, the
2
+ // headers, the body encoded once, the retries the operation is eligible
3
+ // for, and the decoded response. Every resource method calls `request` or
4
+ // `requestPage`; the ingest helper keeps its own path.
5
+ import { AllisallError } from "./errors.js";
6
+ import { OPERATIONS, } from "./generated/operations.js";
7
+ import { decodeJson, encodeJson } from "./money.js";
8
+ import { formOf } from "./multipart.js";
9
+ import { mergeHeaders, resolveHeaders, resolveMaxRetries, resolveTimeout, } from "./options.js";
10
+ import { Page, PagePromise } from "./pagination.js";
11
+ import { joinURL, send, } from "./transport.js";
12
+ import { hasLoneSurrogate } from "./unicode.js";
13
+ import { uuidv4 } from "./uuid.js";
14
+ const CONTENT_TYPES = {
15
+ json: "application/json",
16
+ "merge-patch": "application/merge-patch+json",
17
+ };
18
+ const PATH_PARAMETER = /\{([^{}]+)\}/g;
19
+ /** Fills the path template, each parameter percent-encoded as one segment. */
20
+ function fillPath(template, values) {
21
+ return template.replace(PATH_PARAMETER, (_, name) => {
22
+ const value = values[name];
23
+ if (typeof value !== "string") {
24
+ throw new TypeError(`allisall: the path parameter ${name} must be a string`);
25
+ }
26
+ // These cannot travel as one segment: a URL resolves "." and ".." as
27
+ // steps through the path, whether or not they are percent-encoded, and
28
+ // an empty segment changes which path is meant.
29
+ if (value === "" || value === "." || value === "..") {
30
+ throw new TypeError(`allisall: the path parameter ${name} is ${JSON.stringify(value)}, which cannot be sent as a path segment`);
31
+ }
32
+ // encodeURIComponent would throw a URIError naming nothing.
33
+ if (hasLoneSurrogate(value)) {
34
+ throw new TypeError(`allisall: the path parameter ${name} is not valid Unicode (a lone surrogate)`);
35
+ }
36
+ return encodeURIComponent(value);
37
+ });
38
+ }
39
+ /**
40
+ * A query value's text. A string holding a lone surrogate is refused: the
41
+ * URL would carry U+FFFD in its place.
42
+ */
43
+ function queryText(name, value) {
44
+ if (hasLoneSurrogate(name)) {
45
+ throw new TypeError("allisall: a query parameter name is not valid Unicode (a lone surrogate)");
46
+ }
47
+ if (value instanceof Date) {
48
+ if (Number.isNaN(value.getTime())) {
49
+ throw new TypeError(`allisall: the query parameter ${name} is an invalid Date`);
50
+ }
51
+ return value.toISOString();
52
+ }
53
+ if (typeof value === "string") {
54
+ if (hasLoneSurrogate(value)) {
55
+ throw new TypeError(`allisall: the query parameter ${name} is not valid Unicode (a lone surrogate)`);
56
+ }
57
+ return value;
58
+ }
59
+ if (typeof value === "number" || typeof value === "bigint") {
60
+ return String(value);
61
+ }
62
+ throw new TypeError(`allisall: the query parameter ${name} is ${value === null ? "null" : `a ${typeof value}`}; it must be a string, a number or a Date`);
63
+ }
64
+ /** Builds the URL: the base, the filled path, and the query in the caller's order. */
65
+ function buildURL(config, operation, path, query) {
66
+ const url = new URL(joinURL(config.baseURL, fillPath(operation.path, path)));
67
+ if (query !== undefined) {
68
+ for (const [name, value] of Object.entries(query)) {
69
+ // An absent member is left out; the SDK writes no parameter of its own.
70
+ if (value === undefined)
71
+ continue;
72
+ url.searchParams.append(name, queryText(name, value));
73
+ }
74
+ }
75
+ return url.href;
76
+ }
77
+ const utf8 = new TextEncoder();
78
+ /** Encodes the body once, before the first attempt. */
79
+ function encodeBody(operation, body) {
80
+ switch (operation.body) {
81
+ case "none":
82
+ // The API refuses any body here, so one passed is a mistake, not
83
+ // something to drop in silence.
84
+ if (body !== undefined) {
85
+ throw new TypeError("allisall: this operation takes no request body, but one was passed");
86
+ }
87
+ return { body: undefined, contentType: undefined };
88
+ case "json":
89
+ case "merge-patch":
90
+ return {
91
+ body: utf8.encode(encodeJson(body, operation.requestMoney)),
92
+ contentType: CONTENT_TYPES[operation.body],
93
+ };
94
+ case "multipart":
95
+ // The form's own boundary is written by fetch, with its Content-Type.
96
+ return {
97
+ // The operation table's multipart operations take an ImportUpload;
98
+ // the methods calling them are typed so.
99
+ body: formOf(body),
100
+ contentType: undefined,
101
+ };
102
+ }
103
+ }
104
+ function isRecord(value) {
105
+ return typeof value === "object" && value !== null && !Array.isArray(value);
106
+ }
107
+ /**
108
+ * Reads a success body: decoded with exact money, and a JSON object. A
109
+ * body `accept` refuses is unreadable, as a breached envelope is.
110
+ */
111
+ function readerFor(operation, accept = () => true) {
112
+ if (operation.status === 204)
113
+ return () => ({ value: undefined });
114
+ return (text) => {
115
+ let value;
116
+ try {
117
+ value = decodeJson(text, operation.responseMoney);
118
+ }
119
+ catch (err) {
120
+ if (err instanceof SyntaxError)
121
+ return undefined;
122
+ throw err;
123
+ }
124
+ if (!isRecord(value))
125
+ return undefined;
126
+ if (operation.list !== null && !isListBody(value))
127
+ return undefined;
128
+ if (!accept(value))
129
+ return undefined;
130
+ return { value };
131
+ };
132
+ }
133
+ /**
134
+ * Checks a list's envelope: an array `data`, a boolean `has_more`, and a
135
+ * `next_cursor` that is a string or `null`, and a non-empty string when
136
+ * `has_more` is true.
137
+ */
138
+ function isListBody(value) {
139
+ const { data, has_more, next_cursor } = value;
140
+ if (!Array.isArray(data) || typeof has_more !== "boolean")
141
+ return false;
142
+ if (next_cursor !== null && typeof next_cursor !== "string")
143
+ return false;
144
+ return !has_more || (typeof next_cursor === "string" && next_cursor !== "");
145
+ }
146
+ /**
147
+ * The one `PUT` a retry cannot repeat safely. Its `version` guard refuses a
148
+ * repeat of a write that already landed, as a stale version, so a retry
149
+ * after a lost response would report a failure for a write that succeeded.
150
+ */
151
+ const VERSION_GUARDED_PUT = "setValueModelLineItems";
152
+ /**
153
+ * Whether a retry cannot apply the request twice: a method RFC 9110 defines
154
+ * as idempotent, an operation sent with an `Idempotency-Key`, or ingest,
155
+ * whose `event_id`s deduplicate. The version-guarded `PUT` is the exception.
156
+ */
157
+ export function isEligible(id, operation) {
158
+ switch (operation.method) {
159
+ case "GET":
160
+ case "DELETE":
161
+ return true;
162
+ case "PUT":
163
+ return id !== VERSION_GUARDED_PUT;
164
+ case "PATCH":
165
+ return false;
166
+ case "POST":
167
+ return operation.idempotent || id === "ingestEvents";
168
+ }
169
+ }
170
+ /**
171
+ * What one call may do: its operation's retry eligibility, and the call's
172
+ * options resolved over the client's.
173
+ *
174
+ * @throws {AllisallError} for a `timeout` or `maxRetries` the client would refuse.
175
+ * @throws {TypeError} for headers that are not a plain object of headers, or
176
+ * that name a header the SDK sets.
177
+ */
178
+ export function policyOf(config, id, operation, options) {
179
+ return {
180
+ timeout: resolveTimeout(options.timeout, config.timeout),
181
+ maxRetries: resolveMaxRetries(options.maxRetries, config.maxRetries),
182
+ headers: resolveHeaders(options.headers, "headers"),
183
+ signal: options.signal,
184
+ eligible: isEligible(id, operation),
185
+ retryKeyInUse: operation.idempotent,
186
+ };
187
+ }
188
+ function outgoing(config, operation, args, options) {
189
+ return {
190
+ method: operation.method,
191
+ url: buildURL(config, operation, args.path ?? {}, args.query),
192
+ authorization: authorizationFor(operation, args.shareToken),
193
+ ...encodeBody(operation, args.body),
194
+ // One key per call, the same on every attempt.
195
+ idempotencyKey: operation.idempotent
196
+ ? (checkedKey(options.idempotencyKey) ?? uuidv4())
197
+ : undefined,
198
+ };
199
+ }
200
+ /**
201
+ * The credential an operation's access policy calls for. A shared report
202
+ * takes its share token, and only its own function passes one: the client's
203
+ * credential never reaches it, and a share token reaches nothing else.
204
+ *
205
+ * @throws {AllisallError} for a shared report without a share token, or a
206
+ * share token passed to any other operation.
207
+ */
208
+ function authorizationFor(operation, shareToken) {
209
+ if (operation.access === "ShareToken") {
210
+ if (shareToken === undefined) {
211
+ throw new AllisallError("allisall: a shared report is read with its share token, not with the client's credential");
212
+ }
213
+ return { report: shareToken };
214
+ }
215
+ if (shareToken !== undefined) {
216
+ throw new AllisallError("allisall: a share token is sent only to read a shared report");
217
+ }
218
+ return operation.access === "Public" ? "none" : "bearer";
219
+ }
220
+ /**
221
+ * A caller's `Idempotency-Key`, checked only as far as the platform can
222
+ * send it. Its syntax is the API's to judge. A key `fetch` cannot send
223
+ * would fail every attempt before sending, and each failure would read as
224
+ * a connection error worth retrying.
225
+ *
226
+ * @throws {TypeError} when the key is not a string, or not a valid header value.
227
+ */
228
+ function checkedKey(key) {
229
+ if (key === undefined)
230
+ return undefined;
231
+ if (typeof key !== "string") {
232
+ throw new TypeError(`allisall: idempotencyKey must be a string, not ${key === null ? "null" : `a ${typeof key}`}`);
233
+ }
234
+ try {
235
+ new Headers({ "Idempotency-Key": key });
236
+ }
237
+ catch {
238
+ throw new TypeError("allisall: idempotencyKey holds a character a header value cannot carry, such as a newline or a character above U+00FF");
239
+ }
240
+ return key;
241
+ }
242
+ /**
243
+ * Calls one operation and resolves with its decoded success body.
244
+ *
245
+ * Everything the call sends is fixed before the first attempt: its URL, its
246
+ * body's bytes and its `Idempotency-Key`. Retries follow the operation's
247
+ * eligibility. A `204` resolves with `undefined`.
248
+ */
249
+ export function request(config, id, args = {}) {
250
+ return requestAccepting(config, id, args, undefined);
251
+ }
252
+ /** `request`, with a check of the success body beyond the operation's own. */
253
+ async function requestAccepting(config, id, args, accept) {
254
+ const operation = OPERATIONS[id];
255
+ const options = args.options ?? {};
256
+ const policy = policyOf(config, id, operation, options);
257
+ const out = outgoing(config, operation, args, options);
258
+ const value = await send(config, out, readerFor(operation, accept), policy);
259
+ // The reader checked what the SDK relies on: a JSON object, a list's
260
+ // envelope, and money decoded at the operation's money paths. The rest
261
+ // is the server's to keep to the generated type.
262
+ return value;
263
+ }
264
+ /**
265
+ * Calls a list operation lazily: nothing is sent until the result is
266
+ * awaited or iterated. Each following page sends the same query with
267
+ * `cursor` set to the previous page's `next_cursor` and without `page`.
268
+ *
269
+ * The walk remembers every cursor it has sent since the list call, with
270
+ * the position of the page each requested; a cursor the caller's own query
271
+ * held requested the first. A page with `has_more` whose `next_cursor` was
272
+ * sent for a page at another position would walk in a cycle, so it is
273
+ * unreadable: an `ApiError` with the page's status and no problem. Walking
274
+ * again from an earlier page sends each cursor for its own position, and is
275
+ * not refused.
276
+ */
277
+ export function requestPage(config, id, args = {}) {
278
+ if (OPERATIONS[id].list === null) {
279
+ throw new TypeError(`allisall: ${id} is not a list operation`);
280
+ }
281
+ // The call's options apply to every page; an override passed to
282
+ // getNextPage applies to that one request, over them.
283
+ const callOptions = args.options ?? {};
284
+ // Each cursor sent, with the position of the page it requested: 0 for
285
+ // the first page, 1 for the next, and so on.
286
+ const sent = new Map();
287
+ // A next cursor that requested a page at another position would walk in
288
+ // a cycle. One recorded at the same position is the same page requested
289
+ // again, as when a walk restarts from an earlier page.
290
+ const breaksNoCycle = (nextPosition) => (value) => {
291
+ if (value.has_more !== true || typeof value.next_cursor !== "string") {
292
+ return true;
293
+ }
294
+ const position = sent.get(value.next_cursor);
295
+ return position === undefined || position === nextPosition;
296
+ };
297
+ const load = async (query, override, position) => {
298
+ const options = override === undefined ? callOptions : overlay(callOptions, override);
299
+ const cursor = query === undefined ? undefined : Reflect.get(query, "cursor");
300
+ if (typeof cursor === "string" && !sent.has(cursor)) {
301
+ sent.set(cursor, position);
302
+ }
303
+ const body = await requestAccepting(config, id, pageArgs(args, query, options), breaksNoCycle(position + 1));
304
+ // A list operation's reader checked the envelope.
305
+ const list = body;
306
+ const next = list.has_more && typeof list.next_cursor === "string"
307
+ ? nextQuery(query, list.next_cursor)
308
+ : undefined;
309
+ return new Page(list, next === undefined
310
+ ? undefined
311
+ : (nextOverride) => load(next, nextOverride, position + 1));
312
+ };
313
+ return new PagePromise(() => load(args.query, undefined, 0));
314
+ }
315
+ /**
316
+ * The arguments of one page's request: the call's own, with the query and
317
+ * options paging sets for that page. Every other member is carried forward.
318
+ */
319
+ export function pageArgs(args, query, options) {
320
+ return { ...args, query, options };
321
+ }
322
+ /**
323
+ * Lays one request's options over the call's. A member left out, or set to
324
+ * `undefined`, keeps the call's; `headers` are laid over the call's
325
+ * headers name by name, as a call's are over the client's.
326
+ *
327
+ * @throws {TypeError} when either set of headers is not a plain object of
328
+ * headers, or names a header the SDK sets.
329
+ */
330
+ function overlay(call, override) {
331
+ const merged = { ...call };
332
+ if (override.signal !== undefined)
333
+ merged.signal = override.signal;
334
+ if (override.timeout !== undefined)
335
+ merged.timeout = override.timeout;
336
+ if (override.maxRetries !== undefined) {
337
+ merged.maxRetries = override.maxRetries;
338
+ }
339
+ if (override.headers !== undefined) {
340
+ const headers = { ...resolveHeaders(call.headers, "headers") };
341
+ mergeHeaders(headers, resolveHeaders(override.headers, "headers"));
342
+ merged.headers = headers;
343
+ }
344
+ return merged;
345
+ }
346
+ /** The query of the next page: the same members, `page` dropped, `cursor` set. */
347
+ function nextQuery(query, cursor) {
348
+ const next = {};
349
+ for (const [name, value] of Object.entries(query ?? {})) {
350
+ if (name !== "page")
351
+ next[name] = value;
352
+ }
353
+ next.cursor = cursor;
354
+ return next;
355
+ }
356
+ /** Calls one operation with arguments typed for it; see `request`. */
357
+ export function call(config, id, args) {
358
+ return request(config, id, args);
359
+ }
360
+ /** Calls one list operation with arguments typed for it; see `requestPage`. */
361
+ export function callPage(config, id, args) {
362
+ return requestPage(config, id, args);
363
+ }
@@ -0,0 +1,51 @@
1
+ import type { IdempotentRequestOptions, RequestOptions } from "../options.js";
2
+ import type { PagePromise } from "../pagination.js";
3
+ import type { TransportConfig } from "../transport.js";
4
+ import type { Agent, AgentCreate, AgentDetail, AgentList, AgentListItem, AgentPatch, GetAgentQuery, ListAgentsQuery } from "../types.js";
5
+ /** The `agents` operations. */
6
+ export declare class Agents {
7
+ #private;
8
+ constructor(config: TransportConfig);
9
+ /**
10
+ * Lists agents. Await the result for one page; iterate it with
11
+ * `for await` for every agent of every page.
12
+ *
13
+ * Needs the `read` scope. Operation `listAgents`.
14
+ */
15
+ list(query?: ListAgentsQuery, options?: RequestOptions): PagePromise<AgentListItem, AgentList>;
16
+ /**
17
+ * Creates an agent.
18
+ *
19
+ * Every attempt of the call sends one `Idempotency-Key`, so a retry
20
+ * cannot create a second agent.
21
+ *
22
+ * Needs the `write` scope. Operation `createAgent`.
23
+ */
24
+ create(body: AgentCreate, options?: IdempotentRequestOptions): Promise<Agent>;
25
+ /**
26
+ * Reads an agent, with its statistics. `from` and `to` bound the
27
+ * statistics only; absent bounds mean all time.
28
+ *
29
+ * Needs the `read` scope. Operation `getAgent`.
30
+ */
31
+ get(id: string, query?: GetAgentQuery, options?: RequestOptions): Promise<AgentDetail>;
32
+ /**
33
+ * Updates an agent with a merge patch: a member left out keeps its value,
34
+ * `null` clears it, and any other value sets it.
35
+ *
36
+ * Never retried.
37
+ *
38
+ * Needs the `write` scope. Operation `updateAgent`.
39
+ */
40
+ update(id: string, patch: AgentPatch, options?: RequestOptions): Promise<Agent>;
41
+ /**
42
+ * Deletes an agent. Refused while events reference the agent; set its
43
+ * status to `retired` instead.
44
+ *
45
+ * A retry after a lost response can answer `404` for a delete that
46
+ * succeeded.
47
+ *
48
+ * Needs the `write` scope. Operation `deleteAgent`.
49
+ */
50
+ delete(id: string, options?: RequestOptions): Promise<void>;
51
+ }
@@ -0,0 +1,65 @@
1
+ // The `agents` operations.
2
+ import { call, callPage } from "../request.js";
3
+ /** The `agents` operations. */
4
+ export class Agents {
5
+ #config;
6
+ constructor(config) {
7
+ this.#config = config;
8
+ }
9
+ /**
10
+ * Lists agents. Await the result for one page; iterate it with
11
+ * `for await` for every agent of every page.
12
+ *
13
+ * Needs the `read` scope. Operation `listAgents`.
14
+ */
15
+ list(query, options) {
16
+ return callPage(this.#config, "listAgents", { query, options });
17
+ }
18
+ /**
19
+ * Creates an agent.
20
+ *
21
+ * Every attempt of the call sends one `Idempotency-Key`, so a retry
22
+ * cannot create a second agent.
23
+ *
24
+ * Needs the `write` scope. Operation `createAgent`.
25
+ */
26
+ create(body, options) {
27
+ return call(this.#config, "createAgent", { body, options });
28
+ }
29
+ /**
30
+ * Reads an agent, with its statistics. `from` and `to` bound the
31
+ * statistics only; absent bounds mean all time.
32
+ *
33
+ * Needs the `read` scope. Operation `getAgent`.
34
+ */
35
+ get(id, query, options) {
36
+ return call(this.#config, "getAgent", { path: { id }, query, options });
37
+ }
38
+ /**
39
+ * Updates an agent with a merge patch: a member left out keeps its value,
40
+ * `null` clears it, and any other value sets it.
41
+ *
42
+ * Never retried.
43
+ *
44
+ * Needs the `write` scope. Operation `updateAgent`.
45
+ */
46
+ update(id, patch, options) {
47
+ return call(this.#config, "updateAgent", {
48
+ path: { id },
49
+ body: patch,
50
+ options,
51
+ });
52
+ }
53
+ /**
54
+ * Deletes an agent. Refused while events reference the agent; set its
55
+ * status to `retired` instead.
56
+ *
57
+ * A retry after a lost response can answer `404` for a delete that
58
+ * succeeded.
59
+ *
60
+ * Needs the `write` scope. Operation `deleteAgent`.
61
+ */
62
+ delete(id, options) {
63
+ return call(this.#config, "deleteAgent", { path: { id }, options });
64
+ }
65
+ }