@elysra/tungsten-runtime 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +55 -0
- package/README.md +79 -2
- package/dist/auth.d.ts +50 -0
- package/dist/auth.js +264 -0
- package/dist/classify.d.ts +64 -0
- package/dist/classify.js +301 -0
- package/dist/client.d.ts +49 -0
- package/dist/client.js +2309 -0
- package/dist/confirm.d.ts +7 -0
- package/dist/confirm.js +32 -0
- package/dist/define.d.ts +13 -0
- package/dist/define.js +12 -0
- package/dist/envelope.d.ts +24 -0
- package/dist/envelope.js +152 -0
- package/dist/errors.d.ts +37 -0
- package/dist/errors.js +75 -0
- package/dist/expr.d.ts +24 -0
- package/dist/expr.js +198 -0
- package/dist/idempotency.d.ts +27 -0
- package/dist/idempotency.js +56 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +19 -0
- package/dist/node.d.ts +17 -0
- package/dist/node.js +98 -0
- package/dist/oauth.d.ts +124 -0
- package/dist/oauth.js +367 -0
- package/dist/serialize.d.ts +55 -0
- package/dist/serialize.js +323 -0
- package/dist/sse.d.ts +42 -0
- package/dist/sse.js +166 -0
- package/dist/streams.d.ts +65 -0
- package/dist/streams.js +96 -0
- package/dist/transport.d.ts +88 -0
- package/dist/transport.js +245 -0
- package/dist/types.d.ts +667 -0
- package/dist/types.js +1 -0
- package/dist/util.d.ts +73 -0
- package/dist/util.js +317 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +4 -0
- package/package.json +27 -4
package/dist/classify.js
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Classification of HTTP responses and lost requests into results and
|
|
4
|
+
* diagnostic envelopes (planning/06 "Diagnostic error envelope",
|
|
5
|
+
* "Unknown outcome", planning/04 "Remediation table resolution").
|
|
6
|
+
*/
|
|
7
|
+
import { categoryForStatus, diagnostic, GENERIC_REMEDIATION, isCategory, isRetryable } from "./envelope.js";
|
|
8
|
+
import { hasReplayProtection } from "./idempotency.js";
|
|
9
|
+
import { getPath, isRecord } from "./util.js";
|
|
10
|
+
/** Headers that carry a request id, in lookup order. */
|
|
11
|
+
const REQUEST_ID_HEADERS = ["x-request-id", "request-id", "x-correlation-id", "x-amzn-requestid", "cf-ray"];
|
|
12
|
+
export function requestIdOf(headers) {
|
|
13
|
+
for (const name of REQUEST_ID_HEADERS) {
|
|
14
|
+
const value = headers[name];
|
|
15
|
+
if (value)
|
|
16
|
+
return value.slice(0, 200);
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
/** The media type without parameters, lowercased; "" when absent. */
|
|
21
|
+
export function mediaTypeOf(headers) {
|
|
22
|
+
return (headers["content-type"] ?? "").split(";")[0]?.trim().toLowerCase() ?? "";
|
|
23
|
+
}
|
|
24
|
+
export function isJsonMedia(media) {
|
|
25
|
+
return media === "application/json" || media.endsWith("+json") || media === "text/json";
|
|
26
|
+
}
|
|
27
|
+
const JSONL_MEDIA = new Set(["application/jsonl", "application/x-jsonl", "application/ndjson", "application/x-ndjson"]);
|
|
28
|
+
/** JSON Lines media types: one JSON value per line. */
|
|
29
|
+
export function isJsonlMedia(media) {
|
|
30
|
+
return JSONL_MEDIA.has(media);
|
|
31
|
+
}
|
|
32
|
+
/** The response descriptor matching a status: exact, then `NXX`, then `default`. */
|
|
33
|
+
export function matchResponse(responses, status) {
|
|
34
|
+
const list = Array.isArray(responses) ? responses.filter(isRecord) : [];
|
|
35
|
+
return (list.find((r) => r.status === status) ??
|
|
36
|
+
list.find((r) => typeof r.status === "string" && r.status === `${Math.floor(status / 100)}XX`) ??
|
|
37
|
+
list.find((r) => r.status === "default"));
|
|
38
|
+
}
|
|
39
|
+
export function decodeBody(bytes, headers, declared) {
|
|
40
|
+
const announced = mediaTypeOf(headers);
|
|
41
|
+
const media = announced || (declared ?? "").toLowerCase();
|
|
42
|
+
const text = () => new TextDecoder().decode(bytes);
|
|
43
|
+
// An empty body is no body, unless the server says it is JSON Lines: then it is no lines.
|
|
44
|
+
if (bytes.byteLength === 0 && !isJsonlMedia(announced))
|
|
45
|
+
return { value: undefined, json: false, invalidJson: false, empty: true };
|
|
46
|
+
if (isJsonlMedia(announced || media.split(";")[0].trim())) {
|
|
47
|
+
const raw = text();
|
|
48
|
+
const values = [];
|
|
49
|
+
const lines = raw.split("\n");
|
|
50
|
+
for (const [index, line] of lines.entries()) {
|
|
51
|
+
if (line.trim() === "")
|
|
52
|
+
continue;
|
|
53
|
+
try {
|
|
54
|
+
values.push(JSON.parse(line));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return { value: raw, json: false, invalidJson: true, empty: false, jsonl: true, badLine: index + 1 };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { value: values, json: false, invalidJson: false, empty: false, jsonl: true };
|
|
61
|
+
}
|
|
62
|
+
if (isJsonMedia(media)) {
|
|
63
|
+
const raw = text();
|
|
64
|
+
try {
|
|
65
|
+
return { value: JSON.parse(raw), json: true, invalidJson: false, empty: false };
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return { value: raw, json: false, invalidJson: true, empty: false };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (media === "" || media.startsWith("text/") || media === "application/problem+xml" || media.endsWith("+xml") || media === "application/xml") {
|
|
72
|
+
const raw = text();
|
|
73
|
+
if (media === "" && /^\s*[[{]/.test(raw)) {
|
|
74
|
+
try {
|
|
75
|
+
return { value: JSON.parse(raw), json: true, invalidJson: false, empty: false };
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// not JSON after all; keep the text
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { value: raw, json: false, invalidJson: false, empty: false };
|
|
82
|
+
}
|
|
83
|
+
return { value: bytes, json: false, invalidJson: false, empty: false };
|
|
84
|
+
}
|
|
85
|
+
const MESSAGE_FIELDS = ["message", "error.message", "detail", "error_description", "title", "error"];
|
|
86
|
+
function serverMessage(body) {
|
|
87
|
+
for (const field of MESSAGE_FIELDS) {
|
|
88
|
+
const value = getPath(body, field);
|
|
89
|
+
if (typeof value === "string" && value.trim() !== "")
|
|
90
|
+
return value.trim().slice(0, 200);
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
function errorCode(op, body) {
|
|
95
|
+
const field = op.errorCodeField;
|
|
96
|
+
if (!field)
|
|
97
|
+
return null;
|
|
98
|
+
const value = getPath(body, field);
|
|
99
|
+
if (typeof value === "string" && value !== "")
|
|
100
|
+
return value.slice(0, 200);
|
|
101
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
102
|
+
return String(value);
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
function remediationEntry(table, code) {
|
|
106
|
+
if (code === null || !isRecord(table))
|
|
107
|
+
return undefined;
|
|
108
|
+
const entry = Object.prototype.hasOwnProperty.call(table, code) ? table[code] : undefined;
|
|
109
|
+
return isRecord(entry) ? entry : undefined;
|
|
110
|
+
}
|
|
111
|
+
/** Whether the verification hook's arguments can be built without the
|
|
112
|
+
* call's response: after a lost or ambiguous answer there is no success
|
|
113
|
+
* body, so a hook whose args reference `$response` cannot be called. */
|
|
114
|
+
export function verifyCallable(verify) {
|
|
115
|
+
const refersToResponse = (node, depth) => {
|
|
116
|
+
if (depth > 32)
|
|
117
|
+
return true;
|
|
118
|
+
if (typeof node === "string")
|
|
119
|
+
return node === "$response" || node.startsWith("$response.");
|
|
120
|
+
if (Array.isArray(node))
|
|
121
|
+
return node.some((item) => refersToResponse(item, depth + 1));
|
|
122
|
+
if (isRecord(node))
|
|
123
|
+
return Object.values(node).some((item) => refersToResponse(item, depth + 1));
|
|
124
|
+
return false;
|
|
125
|
+
};
|
|
126
|
+
return isRecord(verify) && !refersToResponse(verify.args, 0);
|
|
127
|
+
}
|
|
128
|
+
/** What resolves an unknown outcome: the verification hook when it can be
|
|
129
|
+
* called without the lost response, else repeating the call under its
|
|
130
|
+
* replay protection (the same key, or the identical body), which answers
|
|
131
|
+
* the original result instead of applying the effect twice. Null when
|
|
132
|
+
* nothing can resolve it safely. */
|
|
133
|
+
function nextActionHint(ctx) {
|
|
134
|
+
const verify = ctx.op.agent.verify;
|
|
135
|
+
if (verify && typeof verify.operation === "string" && verifyCallable(verify)) {
|
|
136
|
+
const keys = isRecord(verify.args) ? Object.keys(verify.args) : [];
|
|
137
|
+
const withArgs = keys.length > 0 ? ` with ${keys.join(", ")}` : "";
|
|
138
|
+
return `Call ${verify.operation}${withArgs} to check whether ${ctx.op.id} took effect before doing anything else.`;
|
|
139
|
+
}
|
|
140
|
+
if (ctx.op.agent.idempotency.policy === "content_identity") {
|
|
141
|
+
return `Call ${ctx.op.id} again with the identical body; the body is its own identity, so the server answers the original result instead of applying it twice.`;
|
|
142
|
+
}
|
|
143
|
+
if (ctx.key !== null) {
|
|
144
|
+
return `Call ${ctx.op.id} again with the same ${ctx.keyHeader} value and identical arguments; the server answers the original result instead of applying it twice.`;
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
/** The change an operation makes, in words, for remediation text: its
|
|
149
|
+
* summary when it has one (`the change "Rotate an endpoint's signing
|
|
150
|
+
* secret"`). */
|
|
151
|
+
export function changeOf(op) {
|
|
152
|
+
const summary = typeof op.summary === "string" ? op.summary.trim().replace(/\.$/, "") : "";
|
|
153
|
+
return summary === "" ? `the change ${op.id} makes` : `the change ${JSON.stringify(summary)}`;
|
|
154
|
+
}
|
|
155
|
+
/** OUTCOME_UNKNOWN for a mutation whose effect cannot be known. Without
|
|
156
|
+
* replay protection (no key, no identity body) a repeat can apply the
|
|
157
|
+
* effect twice, so the envelope never offers one: `retryable` is
|
|
158
|
+
* `after_remediation` (check the state first), not `same_key_only`, and
|
|
159
|
+
* the remediation and `next_action` say what to check before repeating:
|
|
160
|
+
* `ctx.check` when the client found a read that shows it, else the
|
|
161
|
+
* operation's own change in words. */
|
|
162
|
+
export function outcomeUnknown(ctx, cause, fields = {}) {
|
|
163
|
+
const op = ctx.op;
|
|
164
|
+
let rule;
|
|
165
|
+
let hint = null;
|
|
166
|
+
if (op.agent.idempotency.policy === "content_identity") {
|
|
167
|
+
rule = "If you retry, resend the identical bytes only; the body is its own identity.";
|
|
168
|
+
}
|
|
169
|
+
else if (ctx.key !== null) {
|
|
170
|
+
rule = `If you retry, reuse the SAME ${ctx.keyHeader} value; a new key can apply the effect twice.`;
|
|
171
|
+
}
|
|
172
|
+
else if (ctx.check) {
|
|
173
|
+
// What to verify, specific to the operation: its verification hook, or
|
|
174
|
+
// a read of the resource it changes.
|
|
175
|
+
const { call, shows } = ctx.check;
|
|
176
|
+
rule = `This operation has no idempotency key, so repeating it can apply the effect twice. Before calling ${op.id} again, call ${call} and check ${shows}: if it does, ${op.id} took effect and must not be repeated; call it again only if it did not.`;
|
|
177
|
+
hint = `Call ${call} and check ${shows}; call ${op.id} again only if it did not take effect.`;
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
const change = changeOf(op);
|
|
181
|
+
rule = `This operation has no idempotency key and no registered way to verify it, so ${op.id} may have taken effect and a repeat can apply the effect twice: do not retry it blindly. Check the outcome by other means if you can (look for ${change}); if you cannot, ask whoever owns the task before calling it again.`;
|
|
182
|
+
hint = `Do not retry ${op.id} blindly: it may have taken effect. Check for ${change} if you can; call ${op.id} again only if it is not there, or after the task owner accepts that it may apply twice.`;
|
|
183
|
+
}
|
|
184
|
+
return diagnostic(op.id, "OUTCOME_UNKNOWN", {
|
|
185
|
+
remediation: `${cause} The server may or may not have applied ${op.id}. ${rule}`,
|
|
186
|
+
retryable: hasReplayProtection(op, ctx.key) ? "same_key_only" : "after_remediation",
|
|
187
|
+
next_action: fields.next_action ?? hint ?? nextActionHint(ctx),
|
|
188
|
+
http_status: fields.http_status ?? null,
|
|
189
|
+
code: fields.code ?? null,
|
|
190
|
+
request_id: fields.request_id ?? null,
|
|
191
|
+
retry_after_ms: fields.retry_after_ms ?? null,
|
|
192
|
+
trace: { attempts: ctx.attempts },
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/** Categories that say the request was not applied, or that a repeat is
|
|
196
|
+
* pointless: a manifest entry with one of them (or with `retryable`
|
|
197
|
+
* `never` / `after_remediation`) is a definite answer even for a status
|
|
198
|
+
* that is otherwise ambiguous. */
|
|
199
|
+
const DEFINITE_CATEGORIES = new Set([
|
|
200
|
+
"VALIDATION_FAILED",
|
|
201
|
+
"MALFORMED_REQUEST",
|
|
202
|
+
"REQUEST_TOO_LARGE",
|
|
203
|
+
"AUTH_FAILED",
|
|
204
|
+
"NOT_FOUND",
|
|
205
|
+
"CONFLICT",
|
|
206
|
+
"PRECONDITION_FAILED",
|
|
207
|
+
"RATE_LIMITED",
|
|
208
|
+
"GATE_DISABLED",
|
|
209
|
+
]);
|
|
210
|
+
function definite(entry) {
|
|
211
|
+
if (!entry)
|
|
212
|
+
return false;
|
|
213
|
+
if (entry.retryable === "never" || entry.retryable === "after_remediation")
|
|
214
|
+
return true;
|
|
215
|
+
return typeof entry.category === "string" && DEFINITE_CATEGORIES.has(entry.category);
|
|
216
|
+
}
|
|
217
|
+
export function isMutation(op) {
|
|
218
|
+
return op.agent.safety !== "read_only";
|
|
219
|
+
}
|
|
220
|
+
/** The classification of an HTTP error status. */
|
|
221
|
+
export function classifyError(ctx, status, headers, decoded, retryAfterMs) {
|
|
222
|
+
const { api, op } = ctx;
|
|
223
|
+
const requestId = requestIdOf(headers);
|
|
224
|
+
const base = { http_status: status, request_id: requestId, retry_after_ms: retryAfterMs, trace: { attempts: ctx.attempts } };
|
|
225
|
+
const code = decoded.json ? errorCode(op, decoded.value) : null;
|
|
226
|
+
// A disabled gate answers its status without the API's error document
|
|
227
|
+
// (ZROtext: the route is not mounted, so axum's bare 404). An answer
|
|
228
|
+
// with an error code comes from the mounted route, so it is classified
|
|
229
|
+
// by its code like any other error (an unknown id is NOT_FOUND).
|
|
230
|
+
if (op.status.kind === "gated" && status === op.status.disabledStatus && code === null) {
|
|
231
|
+
const gates = isRecord(api.gates) ? api.gates : {};
|
|
232
|
+
const text = typeof gates[op.status.envVar] === "string" ? gates[op.status.envVar] : null;
|
|
233
|
+
return diagnostic(op.id, "GATE_DISABLED", {
|
|
234
|
+
...base,
|
|
235
|
+
code,
|
|
236
|
+
remediation: text ??
|
|
237
|
+
`This deployment disables ${op.id} because ${op.status.envVar} is off (HTTP ${status}). It is a deployment setting, not a missing resource; do not retry.`,
|
|
238
|
+
retryable: "never",
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
const entry = remediationEntry(op.agent.remediation, code) ?? remediationEntry(api.errorCodes, code);
|
|
242
|
+
const media = decoded.empty ? "none" : mediaTypeOf(headers) || "none";
|
|
243
|
+
const nonJson = decoded.json
|
|
244
|
+
? undefined
|
|
245
|
+
: (Array.isArray(api.nonJson) ? api.nonJson : []).find((n) => isRecord(n) && n.status === status && (n.media === media || (n.media === "none" && decoded.empty)));
|
|
246
|
+
const declared = matchResponse(op.responses, status);
|
|
247
|
+
// On a mutation, a status listed as ambiguous, a response declared
|
|
248
|
+
// ambiguous, and any 5xx leave the outcome unknown (a gateway's 502/504
|
|
249
|
+
// or an intermediary's 500 says nothing about whether the origin
|
|
250
|
+
// committed), unless the manifest's entry for the answer's code or
|
|
251
|
+
// non-JSON shape says it is a definite rejection (billing_pending,
|
|
252
|
+
// retryable: never).
|
|
253
|
+
const ambiguous = (Array.isArray(api.ambiguousStatuses) && api.ambiguousStatuses.includes(status)) ||
|
|
254
|
+
declared?.kind === "ambiguous" ||
|
|
255
|
+
(status >= 500 && status <= 599);
|
|
256
|
+
if (ambiguous && isMutation(op) && !definite(entry ?? (isRecord(nonJson) ? nonJson : undefined))) {
|
|
257
|
+
// The manifest's text for this answer still explains it.
|
|
258
|
+
const said = entry && typeof entry.text === "string" ? entry.text : nonJson && typeof nonJson.text === "string" ? nonJson.text : null;
|
|
259
|
+
return outcomeUnknown(ctx, `HTTP ${status} leaves the outcome of this call unknown.${said ? ` ${said}` : ""}`, {
|
|
260
|
+
...base,
|
|
261
|
+
code,
|
|
262
|
+
next_action: typeof entry?.next_action === "string" ? entry.next_action : null,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
let category;
|
|
266
|
+
let retryable;
|
|
267
|
+
let text = null;
|
|
268
|
+
let nextAction = null;
|
|
269
|
+
if (entry) {
|
|
270
|
+
category = isCategory(entry.category) ? entry.category : categoryForStatus(status, decoded.json);
|
|
271
|
+
retryable = isRetryable(entry.retryable) ? entry.retryable : undefined;
|
|
272
|
+
text = typeof entry.text === "string" ? entry.text : null;
|
|
273
|
+
nextAction = typeof entry.next_action === "string" ? entry.next_action : null;
|
|
274
|
+
}
|
|
275
|
+
else if (nonJson && isCategory(nonJson.category)) {
|
|
276
|
+
category = nonJson.category;
|
|
277
|
+
retryable = isRetryable(nonJson.retryable) ? nonJson.retryable : undefined;
|
|
278
|
+
text = typeof nonJson.text === "string" ? nonJson.text : null;
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
category = categoryForStatus(status, decoded.json);
|
|
282
|
+
}
|
|
283
|
+
if (category === "OUTCOME_UNKNOWN" && !isMutation(op)) {
|
|
284
|
+
// A read has no effect whose outcome could be unknown: the answer only
|
|
285
|
+
// says the service did not respond in time.
|
|
286
|
+
category = "UPSTREAM_UNAVAILABLE";
|
|
287
|
+
if (retryable === undefined || retryable === "same_key_only")
|
|
288
|
+
retryable = "after_delay";
|
|
289
|
+
}
|
|
290
|
+
if (text === null) {
|
|
291
|
+
const said = decoded.json ? serverMessage(decoded.value) : null;
|
|
292
|
+
text = `${GENERIC_REMEDIATION[category]}${said ? ` Server message: ${JSON.stringify(said)}` : ""}`;
|
|
293
|
+
}
|
|
294
|
+
return diagnostic(op.id, category, {
|
|
295
|
+
...base,
|
|
296
|
+
code,
|
|
297
|
+
remediation: text,
|
|
298
|
+
...(retryable ? { retryable } : {}),
|
|
299
|
+
next_action: nextAction,
|
|
300
|
+
});
|
|
301
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { OAuthFlow } from "./oauth.js";
|
|
2
|
+
import { EventStream } from "./streams.js";
|
|
3
|
+
import type { ApiDescriptor, CallOptions, ClientCoreApi, ClientCoreExtensions, ClientOptions, MacroDescriptor, OperationDescriptor, Page, Predicate, PreviewResult, Result } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* The runtime core used by generated clients. One instance per client:
|
|
6
|
+
* it owns the idempotency store, the confirmation-token key, the OAuth2
|
|
7
|
+
* token cache and the registry of operations resolvable by id.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ClientCore implements ClientCoreApi, ClientCoreExtensions {
|
|
10
|
+
#private;
|
|
11
|
+
readonly api: ApiDescriptor;
|
|
12
|
+
readonly options: ClientOptions;
|
|
13
|
+
constructor(api: ApiDescriptor, options?: ClientOptions);
|
|
14
|
+
/** Make operations resolvable by id for verification hooks, endpoint
|
|
15
|
+
* previews and macro steps. Invalid entries are ignored. */
|
|
16
|
+
register(...ops: OperationDescriptor[]): void;
|
|
17
|
+
/** A registered operation by id. */
|
|
18
|
+
operation(id: string): OperationDescriptor | undefined;
|
|
19
|
+
/** Validate, authenticate, apply idempotency and confirmation rules,
|
|
20
|
+
* send with retries and classify the response. Never throws. */
|
|
21
|
+
call<T>(op: OperationDescriptor, args: Record<string, unknown>, opts?: CallOptions): Promise<Result<T>>;
|
|
22
|
+
/** Run the operation's preview mode: local rendering (no network), a
|
|
23
|
+
* dry-run header, or a preview endpoint. Mutating operations get a
|
|
24
|
+
* confirmation token bound to these exact arguments for five minutes. */
|
|
25
|
+
preview(op: OperationDescriptor, args: Record<string, unknown>, opts?: CallOptions): Promise<Result<PreviewResult>>;
|
|
26
|
+
/** Iterate pages (cursor, offset, page number or `Link` header); stops
|
|
27
|
+
* after the last page or after yielding the first error. */
|
|
28
|
+
pages<T>(op: OperationDescriptor, args: Record<string, unknown>, opts?: CallOptions): AsyncIterable<Result<Page<T>>>;
|
|
29
|
+
/** Iterate the events of the operation's event stream: one item per
|
|
30
|
+
* server-sent event, decoded from JSON, then the end of the stream or a
|
|
31
|
+
* final failure item. Never throws. */
|
|
32
|
+
stream<T>(op: OperationDescriptor, args: Record<string, unknown>, opts?: CallOptions): EventStream<T>;
|
|
33
|
+
/** Call a read operation until `until` holds on its body or the budget
|
|
34
|
+
* runs out (`timedOut: true` with the last result). */
|
|
35
|
+
poll<T>(op: OperationDescriptor, args: Record<string, unknown>, until: Predicate, intervalMs: number, budgetMs: number, opts?: CallOptions): Promise<Result<T> & {
|
|
36
|
+
timedOut?: boolean;
|
|
37
|
+
}>;
|
|
38
|
+
/** Run a compiled macro: steps in order, each step's success body bound
|
|
39
|
+
* to its `as` name; returns the evaluated output, or the first failing
|
|
40
|
+
* step's envelope (its remediation names the steps already completed). */
|
|
41
|
+
runMacro<T>(macro: MacroDescriptor, input: Record<string, unknown>, opts?: CallOptions): Promise<Result<T>>;
|
|
42
|
+
/** Preview a macro: no request is sent. Validates and renders the first
|
|
43
|
+
* step, lists the effects of every step, and returns a confirmation token
|
|
44
|
+
* bound to the macro and this exact input unless the macro is read-only. */
|
|
45
|
+
previewMacro(macro: MacroDescriptor, input: Record<string, unknown>, opts?: CallOptions): Promise<Result<PreviewResult>>;
|
|
46
|
+
/** The OAuth2 authorization-code helpers of the scheme `scheme`: PKCE, the
|
|
47
|
+
* authorization URL, the code exchange and refresh. */
|
|
48
|
+
oauth(scheme: string): OAuthFlow;
|
|
49
|
+
}
|