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