@attestry/sdk 0.6.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/LICENSE +190 -0
- package/README.md +1269 -0
- package/dist/client.d.ts +58 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +74 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +43 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +41 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/lines-parser.d.ts +50 -0
- package/dist/lines-parser.d.ts.map +1 -0
- package/dist/lines-parser.js +211 -0
- package/dist/lines-parser.js.map +1 -0
- package/dist/ndjson-parser.d.ts +57 -0
- package/dist/ndjson-parser.d.ts.map +1 -0
- package/dist/ndjson-parser.js +245 -0
- package/dist/ndjson-parser.js.map +1 -0
- package/dist/resources/abac-policies.d.ts +1034 -0
- package/dist/resources/abac-policies.d.ts.map +1 -0
- package/dist/resources/abac-policies.js +1519 -0
- package/dist/resources/abac-policies.js.map +1 -0
- package/dist/resources/audit-log.d.ts +588 -0
- package/dist/resources/audit-log.d.ts.map +1 -0
- package/dist/resources/audit-log.js +629 -0
- package/dist/resources/audit-log.js.map +1 -0
- package/dist/resources/batch.d.ts +845 -0
- package/dist/resources/batch.d.ts.map +1 -0
- package/dist/resources/batch.js +1074 -0
- package/dist/resources/batch.js.map +1 -0
- package/dist/resources/chat.d.ts +151 -0
- package/dist/resources/chat.d.ts.map +1 -0
- package/dist/resources/chat.js +124 -0
- package/dist/resources/chat.js.map +1 -0
- package/dist/resources/check.d.ts +348 -0
- package/dist/resources/check.d.ts.map +1 -0
- package/dist/resources/check.js +543 -0
- package/dist/resources/check.js.map +1 -0
- package/dist/resources/compliance-check.d.ts +330 -0
- package/dist/resources/compliance-check.d.ts.map +1 -0
- package/dist/resources/compliance-check.js +402 -0
- package/dist/resources/compliance-check.js.map +1 -0
- package/dist/resources/decisions.d.ts +1208 -0
- package/dist/resources/decisions.d.ts.map +1 -0
- package/dist/resources/decisions.js +1362 -0
- package/dist/resources/decisions.js.map +1 -0
- package/dist/resources/evidence-pack.d.ts +1080 -0
- package/dist/resources/evidence-pack.d.ts.map +1 -0
- package/dist/resources/evidence-pack.js +1789 -0
- package/dist/resources/evidence-pack.js.map +1 -0
- package/dist/resources/gate.d.ts +613 -0
- package/dist/resources/gate.d.ts.map +1 -0
- package/dist/resources/gate.js +737 -0
- package/dist/resources/gate.js.map +1 -0
- package/dist/resources/incidents.d.ts +136 -0
- package/dist/resources/incidents.d.ts.map +1 -0
- package/dist/resources/incidents.js +229 -0
- package/dist/resources/incidents.js.map +1 -0
- package/dist/resources/regulatory-changes.d.ts +307 -0
- package/dist/resources/regulatory-changes.d.ts.map +1 -0
- package/dist/resources/regulatory-changes.js +365 -0
- package/dist/resources/regulatory-changes.js.map +1 -0
- package/dist/resources/safe-input-read.d.ts +21 -0
- package/dist/resources/safe-input-read.d.ts.map +1 -0
- package/dist/resources/safe-input-read.js +57 -0
- package/dist/resources/safe-input-read.js.map +1 -0
- package/dist/resources/ship-gate.d.ts +475 -0
- package/dist/resources/ship-gate.d.ts.map +1 -0
- package/dist/resources/ship-gate.js +727 -0
- package/dist/resources/ship-gate.js.map +1 -0
- package/dist/resources/vision.d.ts +540 -0
- package/dist/resources/vision.d.ts.map +1 -0
- package/dist/resources/vision.js +1036 -0
- package/dist/resources/vision.js.map +1 -0
- package/dist/retry.d.ts +103 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +224 -0
- package/dist/retry.js.map +1 -0
- package/dist/sse-parser.d.ts +64 -0
- package/dist/sse-parser.d.ts.map +1 -0
- package/dist/sse-parser.js +271 -0
- package/dist/sse-parser.js.map +1 -0
- package/dist/transport.d.ts +142 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +455 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// ─── Server-Sent Events parser ──────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Generic SSE frame parser used by streaming resources (today: decisions.stream;
|
|
4
|
+
// future: any other text/event-stream endpoint). Reads from a
|
|
5
|
+
// `ReadableStreamDefaultReader<Uint8Array>` and yields parsed frames.
|
|
6
|
+
//
|
|
7
|
+
// Spec: https://html.spec.whatwg.org/multipage/server-sent-events.html
|
|
8
|
+
// Subset implemented:
|
|
9
|
+
// - `id:`, `event:`, `data:` field lines (the three the kernel emits today)
|
|
10
|
+
// - Multi-`data:` line concatenation (joined with `\n` per spec)
|
|
11
|
+
// - Comment lines (`:` prefix) — silently dropped (heartbeat handling)
|
|
12
|
+
// - Frames separated by blank line (`\n\n`, `\r\n\r\n`, or `\r\r`)
|
|
13
|
+
// - UTF-8 decoded with `{stream: true}` so multibyte chars split across
|
|
14
|
+
// reads recombine correctly
|
|
15
|
+
// Subset deliberately NOT implemented:
|
|
16
|
+
// - `retry:` field (would map to a reconnect-delay hint; we don't
|
|
17
|
+
// auto-reconnect today — caller manages reconnection by passing the
|
|
18
|
+
// last seen event id back as `lastEventId`)
|
|
19
|
+
// - `BOM` stripping at the very first byte (the kernel's text/event-stream
|
|
20
|
+
// never emits a BOM; defensive at the parser level would be over-spec)
|
|
21
|
+
//
|
|
22
|
+
// The parser is INTENTIONALLY lax about the optional space after the colon:
|
|
23
|
+
// `data:foo` and `data: foo` both yield `foo`. The W3C spec (§ 9.2.6) says
|
|
24
|
+
// "If value starts with a U+0020 SPACE character, remove it." — implemented.
|
|
25
|
+
import { AttestryError } from "./errors.js";
|
|
26
|
+
/**
|
|
27
|
+
* Maximum buffer size before the parser bails. Defense against a server
|
|
28
|
+
* that emits an unbounded frame (no `\n\n` boundary) — without a cap, the
|
|
29
|
+
* parser would buffer until the consumer's process runs out of memory.
|
|
30
|
+
* 1 MiB is generous: the kernel's frame payload is ~500 bytes today, so
|
|
31
|
+
* 1 MiB is ~2000× headroom. A real-world SSE event larger than this is
|
|
32
|
+
* either a server bug or a hostile peer.
|
|
33
|
+
*
|
|
34
|
+
* Configurable via `parseSSE`'s `maxBufferBytes` option for callers
|
|
35
|
+
* with legitimate large-frame use cases (none today). Hostile-review H3.
|
|
36
|
+
*/
|
|
37
|
+
const DEFAULT_MAX_BUFFER_BYTES = 1024 * 1024;
|
|
38
|
+
/**
|
|
39
|
+
* Async generator: reads from a stream of UTF-8-encoded SSE bytes and
|
|
40
|
+
* yields each complete frame. Ends when the underlying reader signals
|
|
41
|
+
* `done`. Defensive: a caller that breaks early out of the for-await
|
|
42
|
+
* loop leaves the reader open — call `reader.cancel()` from your finally
|
|
43
|
+
* block (or use the `parseSSEResponse` wrapper below which handles it).
|
|
44
|
+
*
|
|
45
|
+
* @throws AttestryError if a frame line is malformed past recovery (no
|
|
46
|
+
* such case in the kernel's emitter today; future-proofing); OR
|
|
47
|
+
* if the buffer exceeds `maxBufferBytes` (defense against
|
|
48
|
+
* unbounded-frame DoS — hostile-review H3).
|
|
49
|
+
*/
|
|
50
|
+
export async function* parseSSE(reader, options = {}) {
|
|
51
|
+
const maxBufferBytes = options.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;
|
|
52
|
+
const decoder = new TextDecoder("utf-8");
|
|
53
|
+
let buffer = "";
|
|
54
|
+
let bomStripped = false;
|
|
55
|
+
while (true) {
|
|
56
|
+
const { value, done } = await reader.read();
|
|
57
|
+
if (done) {
|
|
58
|
+
// Flush any final bytes (no-op if value was always complete).
|
|
59
|
+
buffer += decoder.decode();
|
|
60
|
+
// A final unterminated frame is dropped per SSE spec § 9.2.4
|
|
61
|
+
// ("If the end of the file is reached, then dispatch the event and
|
|
62
|
+
// proceed."). Be permissive and emit the trailing block if it has
|
|
63
|
+
// content — an HTTP/1.1 connection-close mid-frame is the most
|
|
64
|
+
// common failure mode and dropping the partial event silently is
|
|
65
|
+
// worse than emitting an under-defined one.
|
|
66
|
+
if (buffer.length > 0) {
|
|
67
|
+
const frame = parseFrameBlock(buffer);
|
|
68
|
+
if (frame !== null)
|
|
69
|
+
yield frame;
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (value === undefined)
|
|
74
|
+
continue;
|
|
75
|
+
// `{stream: true}` carries multi-byte boundaries across chunks.
|
|
76
|
+
buffer += decoder.decode(value, { stream: true });
|
|
77
|
+
// Strip a leading BOM (U+FEFF) if present. Kernel doesn't emit BOM
|
|
78
|
+
// today, but a misconfigured proxy / character-set transcoder
|
|
79
|
+
// could prepend one. Without this strip, the first frame's first
|
|
80
|
+
// field would be e.g. `"id"` instead of `"id"` and silently
|
|
81
|
+
// drop. Hostile-review H4. One-shot — only at the very first
|
|
82
|
+
// decoded codepoint of the stream.
|
|
83
|
+
//
|
|
84
|
+
// Hostile-review fix (parallel to ndjson-parser): `bomStripped` is
|
|
85
|
+
// only flipped once we've actually decoded at least one character.
|
|
86
|
+
// Otherwise an empty / partial-multibyte first read would set the
|
|
87
|
+
// flag prematurely; if the BOM bytes arrived in a later read, the
|
|
88
|
+
// strip-attempt would be skipped. Defense-in-depth — String.prototype.trim
|
|
89
|
+
// and TextDecoder("utf-8")'s default stream-start BOM handling
|
|
90
|
+
// mask the consumer-visible bug today, but a future TextDecoder
|
|
91
|
+
// change (e.g. `ignoreBOM: true`) would unmask it.
|
|
92
|
+
if (!bomStripped && buffer.length > 0) {
|
|
93
|
+
bomStripped = true;
|
|
94
|
+
// Defense-in-depth dead path: same rationale as
|
|
95
|
+
// ndjson-parser.ts — TextDecoder strips stream-start BOM
|
|
96
|
+
// internally, so the slice below is unreachable in Node 18+.
|
|
97
|
+
// Marked for v8 coverage to make the intentional defense visible;
|
|
98
|
+
// a future `{ignoreBOM: true}` decoder OR a buffer path that
|
|
99
|
+
// bypasses TextDecoder would unmask it.
|
|
100
|
+
/* v8 ignore next 3 */
|
|
101
|
+
if (buffer.charCodeAt(0) === 0xfeff) {
|
|
102
|
+
buffer = buffer.slice(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Defense in depth: if the buffer grew past the cap WITHOUT
|
|
106
|
+
// a frame boundary, abort rather than spin until OOM. Hostile-
|
|
107
|
+
// review H3.
|
|
108
|
+
if (buffer.length > maxBufferBytes) {
|
|
109
|
+
throw new AttestryError(`SSE frame exceeded maximum buffer size (${maxBufferBytes} bytes) — server emitted an unbounded frame or omitted the boundary delimiter`);
|
|
110
|
+
}
|
|
111
|
+
// Find frame boundaries. Spec allows `\r\n\r\n`, `\n\n`, or `\r\r`
|
|
112
|
+
// as the separator. Scan with a regex that accepts any.
|
|
113
|
+
while (true) {
|
|
114
|
+
const match = FRAME_BOUNDARY.exec(buffer);
|
|
115
|
+
if (match === null)
|
|
116
|
+
break;
|
|
117
|
+
const block = buffer.slice(0, match.index);
|
|
118
|
+
buffer = buffer.slice(match.index + match[0].length);
|
|
119
|
+
if (block.length === 0)
|
|
120
|
+
continue; // back-to-back blank lines
|
|
121
|
+
const frame = parseFrameBlock(block);
|
|
122
|
+
if (frame !== null)
|
|
123
|
+
yield frame;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Convenience wrapper around `parseSSE` that accepts a `Response`,
|
|
129
|
+
* derives the reader, and ALWAYS calls `reader.cancel()` in a `finally`
|
|
130
|
+
* block — including when a caller breaks out of the for-await loop
|
|
131
|
+
* early. Without this, an early `break` leaks the underlying connection.
|
|
132
|
+
*
|
|
133
|
+
* Resources should call this rather than `parseSSE` directly.
|
|
134
|
+
*/
|
|
135
|
+
export async function* parseSSEResponse(response, options = {}) {
|
|
136
|
+
if (response.body === null) {
|
|
137
|
+
// 204 No Content / empty body — iterator ends with zero frames.
|
|
138
|
+
// Consumer's for-await loop runs zero times. (Throwing here would
|
|
139
|
+
// be over-strict; the server may legitimately close before any data.)
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const reader = response.body.getReader();
|
|
143
|
+
try {
|
|
144
|
+
yield* parseSSE(reader, options);
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
// Release the lock + signal cancellation upstream. Wrapped in
|
|
148
|
+
// try/catch because cancel() on an already-closed reader rejects.
|
|
149
|
+
try {
|
|
150
|
+
await reader.cancel();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Already canceled / closed — nothing to do.
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// ─── Internals ──────────────────────────────────────────────────────────────
|
|
158
|
+
/**
|
|
159
|
+
* One block of SSE text (delimited by blank line) → a parsed frame, or
|
|
160
|
+
* `null` if the block was nothing but comments / whitespace.
|
|
161
|
+
*
|
|
162
|
+
* Per spec:
|
|
163
|
+
* - Lines beginning with `:` are comments, ignored.
|
|
164
|
+
* - Otherwise split on first `:` into `field` + `value`.
|
|
165
|
+
* - If `value` starts with U+0020 SPACE, remove ONE leading space.
|
|
166
|
+
* - Multiple `data:` lines accumulate, joined by `\n`.
|
|
167
|
+
* - Field names other than `id`, `event`, `data`, `retry` are ignored.
|
|
168
|
+
*/
|
|
169
|
+
function parseFrameBlock(block) {
|
|
170
|
+
let id;
|
|
171
|
+
let eventType;
|
|
172
|
+
let dataLines = null;
|
|
173
|
+
let sawNonComment = false;
|
|
174
|
+
// Split on any of `\r\n`, `\n`, or `\r` per spec. Use a character
|
|
175
|
+
// class regex so we don't hit the trailing-empty-string edge case
|
|
176
|
+
// that String.split with a multi-char separator can produce.
|
|
177
|
+
for (const line of block.split(/\r\n|\n|\r/)) {
|
|
178
|
+
if (line.length === 0)
|
|
179
|
+
continue;
|
|
180
|
+
if (line[0] === ":")
|
|
181
|
+
continue; // comment
|
|
182
|
+
sawNonComment = true;
|
|
183
|
+
const colonIdx = line.indexOf(":");
|
|
184
|
+
let field;
|
|
185
|
+
let value;
|
|
186
|
+
if (colonIdx === -1) {
|
|
187
|
+
// Per spec: "If line contains no U+003A COLON character, set field to
|
|
188
|
+
// line and value to the empty string." — we don't recognize any
|
|
189
|
+
// value-less fields today, so silently drop these.
|
|
190
|
+
field = line;
|
|
191
|
+
value = "";
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
field = line.slice(0, colonIdx);
|
|
195
|
+
value = line.slice(colonIdx + 1);
|
|
196
|
+
if (value.length > 0 && value[0] === " ")
|
|
197
|
+
value = value.slice(1);
|
|
198
|
+
}
|
|
199
|
+
switch (field) {
|
|
200
|
+
case "id":
|
|
201
|
+
// Per spec: "If the field value does not contain U+0000 NULL,
|
|
202
|
+
// then set the last event ID buffer to the field value."
|
|
203
|
+
if (!value.includes(""))
|
|
204
|
+
id = value;
|
|
205
|
+
break;
|
|
206
|
+
case "event":
|
|
207
|
+
eventType = value;
|
|
208
|
+
break;
|
|
209
|
+
case "data":
|
|
210
|
+
if (dataLines === null)
|
|
211
|
+
dataLines = [];
|
|
212
|
+
dataLines.push(value);
|
|
213
|
+
break;
|
|
214
|
+
case "retry":
|
|
215
|
+
// Reconnect-delay hint. Not implemented — see file header.
|
|
216
|
+
break;
|
|
217
|
+
default:
|
|
218
|
+
// Unknown field — silently ignored per spec. Don't throw; future
|
|
219
|
+
// server-side fields shouldn't break consumers.
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (!sawNonComment)
|
|
224
|
+
return null;
|
|
225
|
+
// Defense in depth: if EVERY non-comment line was an unrecognized
|
|
226
|
+
// field, we still produced no data. Emitting an empty-data event is
|
|
227
|
+
// confusing — drop. Consumer never has to handle a frame with no
|
|
228
|
+
// content. (SSE spec strictly says emit anyway, but our wire shape
|
|
229
|
+
// always carries data.)
|
|
230
|
+
if (dataLines === null) {
|
|
231
|
+
if (id === undefined && eventType === undefined)
|
|
232
|
+
return null;
|
|
233
|
+
// id-only or event-only is unusual but valid — the kernel doesn't
|
|
234
|
+
// emit these today, but defensively pass them through with empty data
|
|
235
|
+
// rather than swallowing the metadata entirely.
|
|
236
|
+
return { id, event: eventType, data: "" };
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
id,
|
|
240
|
+
event: eventType,
|
|
241
|
+
data: dataLines.join("\n"),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Frame-boundary regex — matches `\r\n\r\n`, `\n\n`, or `\r\r` per spec.
|
|
246
|
+
* Exported only for tests.
|
|
247
|
+
*/
|
|
248
|
+
const FRAME_BOUNDARY = /\r\n\r\n|\n\n|\r\r/g;
|
|
249
|
+
/**
|
|
250
|
+
* Parse a `data:` payload as JSON. Throws `AttestryError` (with the
|
|
251
|
+
* underlying error as `cause`) if the payload isn't valid JSON. Resources
|
|
252
|
+
* call this when the wire shape is documented as JSON-encoded.
|
|
253
|
+
*/
|
|
254
|
+
export function parseSSEData(data) {
|
|
255
|
+
try {
|
|
256
|
+
return JSON.parse(data);
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
throw new AttestryError(`SSE frame data was not valid JSON: ${
|
|
260
|
+
// JSON.parse always throws SyntaxError (an Error subclass), so
|
|
261
|
+
// the String(err) branch is unreachable. Defense-in-depth.
|
|
262
|
+
/* v8 ignore next */
|
|
263
|
+
err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Test-only handles for direct pinning of internals.
|
|
267
|
+
export const __test__ = {
|
|
268
|
+
FRAME_BOUNDARY,
|
|
269
|
+
parseFrameBlock,
|
|
270
|
+
};
|
|
271
|
+
//# sourceMappingURL=sse-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse-parser.js","sourceRoot":"","sources":["../src/sse-parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { type RetryOptions } from "./retry.js";
|
|
2
|
+
import type { AttestryClientOptions, FetchLike, RequestOptions } from "./types.js";
|
|
3
|
+
interface ResolvedClientConfig {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
fetchImpl: FetchLike;
|
|
7
|
+
timeoutMs: number;
|
|
8
|
+
retry: Partial<RetryOptions> | undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validates options at construction time. Surfaces every misconfiguration
|
|
12
|
+
* as `AttestryError` BEFORE any request is made — fail-fast beats failing
|
|
13
|
+
* deep in the resource methods. Used by `AttestryClient` constructor.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveClientConfig(options: AttestryClientOptions): ResolvedClientConfig;
|
|
16
|
+
/**
|
|
17
|
+
* Internal — exported for tests. Composes a URL safely from baseUrl + path.
|
|
18
|
+
* Caller-supplied `path` may or may not start with `/`; either way works.
|
|
19
|
+
*/
|
|
20
|
+
export declare function composeUrl(baseUrl: string, path: string): string;
|
|
21
|
+
interface InternalRequestArgs {
|
|
22
|
+
config: ResolvedClientConfig;
|
|
23
|
+
method: "GET" | "POST" | "PATCH" | "DELETE";
|
|
24
|
+
path: string;
|
|
25
|
+
body?: unknown;
|
|
26
|
+
query?: Record<string, string | number | boolean | undefined | null>;
|
|
27
|
+
options?: RequestOptions;
|
|
28
|
+
/**
|
|
29
|
+
* P3 hardening: expected response Content-Type for the 2xx success
|
|
30
|
+
* path. Defaults to `"application/json"`. The transport's success
|
|
31
|
+
* path checks the response's Content-Type header against this value
|
|
32
|
+
* (MIME-type prefix only, parameter-tolerant for `; charset=utf-8`)
|
|
33
|
+
* and throws `AttestryAPIError` on mismatch — fail-fast against a
|
|
34
|
+
* misbehaving proxy / load balancer that returns 200 OK with an
|
|
35
|
+
* HTML error page or text/plain body. Pre-P3 the SDK silently
|
|
36
|
+
* resolved with `null` (or whatever JSON.parse returned) for
|
|
37
|
+
* wrong-content-type responses; consumers crashed downstream with
|
|
38
|
+
* a confusing TypeError. Now consumers receive a clear
|
|
39
|
+
* AttestryAPIError naming the expected and actual content-type.
|
|
40
|
+
*
|
|
41
|
+
* Skipped for 204 No Content and 304 Not Modified (no body to
|
|
42
|
+
* validate). Error path (non-2xx) is unchanged — the existing
|
|
43
|
+
* error-body parser still falls back to raw text for non-JSON
|
|
44
|
+
* error bodies (see hostile-review H1 pin in transport.test.ts).
|
|
45
|
+
*
|
|
46
|
+
* Symmetric with `streamRequestOnce`'s `expectedContentType` param
|
|
47
|
+
* (which defaults to `text/event-stream` for SSE). Both use the
|
|
48
|
+
* same MIME-type-extraction algorithm to defend against superset /
|
|
49
|
+
* parameter-injection / structured-suffix attacks.
|
|
50
|
+
*/
|
|
51
|
+
expectedContentType?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Public request runner. Wraps the single-attempt `requestOnce` with the
|
|
55
|
+
* retry loop. Resources should NOT import this directly — they go
|
|
56
|
+
* through `AttestryClient._request`.
|
|
57
|
+
*
|
|
58
|
+
* Retry behavior: on 429 only (today). The retry loop honors the caller's
|
|
59
|
+
* abort signal — `signal.abort()` during the backoff sleep rejects with
|
|
60
|
+
* `AttestryError("request aborted by caller")` rather than completing
|
|
61
|
+
* the wait.
|
|
62
|
+
*/
|
|
63
|
+
export declare function request<T>(args: InternalRequestArgs): Promise<T>;
|
|
64
|
+
/**
|
|
65
|
+
* Internal single-attempt request runner. Exposed under `__test__` for
|
|
66
|
+
* unit pinning.
|
|
67
|
+
*/
|
|
68
|
+
export declare function requestOnce<T>(args: InternalRequestArgs): Promise<T>;
|
|
69
|
+
/**
|
|
70
|
+
* Streaming-response request — used for any long-lived streaming endpoint
|
|
71
|
+
* (SSE, NDJSON). Mirrors `request` for header / signal / URL composition
|
|
72
|
+
* but:
|
|
73
|
+
*
|
|
74
|
+
* - Hardcodes `method: "GET"` (streaming endpoints today are read-only).
|
|
75
|
+
* - Sends `Accept: <expectedContentType>` (default `text/event-stream`
|
|
76
|
+
* for SSE backward-compat; NDJSON callers pass
|
|
77
|
+
* `application/x-ndjson`).
|
|
78
|
+
* - Does NOT arm an internal timeout — streams are long-lived; the
|
|
79
|
+
* 30s default would kill them after half a minute. Caller controls
|
|
80
|
+
* duration via `options.signal`. (`decisions.export` runs up to
|
|
81
|
+
* 5 minutes server-side.)
|
|
82
|
+
* - Returns the un-consumed `Response` so the resource can read
|
|
83
|
+
* `response.body` as a stream of bytes (for an SSE / NDJSON parser).
|
|
84
|
+
* - On non-2xx, drains the body and throws `AttestryAPIError` exactly
|
|
85
|
+
* like the JSON path so consumers see the same error shape.
|
|
86
|
+
* - On a 2xx with the WRONG content-type, throws with a clear message —
|
|
87
|
+
* defensive against a misconfigured proxy returning `text/html` (LB
|
|
88
|
+
* error page) or `text/plain`. The kernel always sets the right
|
|
89
|
+
* content-type for each route, so this is a fail-fast guard. The
|
|
90
|
+
* `expectedContentType` parameter (default `text/event-stream`)
|
|
91
|
+
* drives both the `Accept:` request header AND this guard, so SSE
|
|
92
|
+
* callers reject NDJSON responses and vice versa — a single source
|
|
93
|
+
* of truth.
|
|
94
|
+
*
|
|
95
|
+
* Retry semantics: the INITIAL fetch goes through the retry wrapper
|
|
96
|
+
* (same 429 logic as `request<T>`). Once the response is open,
|
|
97
|
+
* mid-iteration errors do NOT retry — events would be lost or
|
|
98
|
+
* duplicated without per-event idempotency. Caller manages reconnection
|
|
99
|
+
* by passing the last seen cursor back (e.g., SSE `lastEventId`,
|
|
100
|
+
* NDJSON: re-issue with adjusted filters).
|
|
101
|
+
*/
|
|
102
|
+
export declare function streamRequest(args: {
|
|
103
|
+
config: ResolvedClientConfig;
|
|
104
|
+
path: string;
|
|
105
|
+
query?: Record<string, string | number | boolean | undefined | null>;
|
|
106
|
+
headers?: Record<string, string>;
|
|
107
|
+
options?: RequestOptions;
|
|
108
|
+
/**
|
|
109
|
+
* Substring (case-insensitive) the response's `Content-Type` header
|
|
110
|
+
* must contain to pass the fail-fast guard. ALSO drives the request's
|
|
111
|
+
* `Accept:` header. Defaults to `"text/event-stream"` for SSE
|
|
112
|
+
* backward-compat. NDJSON callers (`decisions.export`) pass
|
|
113
|
+
* `"application/x-ndjson"`. Single source of truth — same value
|
|
114
|
+
* everywhere keeps SSE callers from accidentally accepting NDJSON
|
|
115
|
+
* responses and vice versa.
|
|
116
|
+
*/
|
|
117
|
+
expectedContentType?: string;
|
|
118
|
+
}): Promise<Response>;
|
|
119
|
+
declare function encodeQuery(q: Record<string, string | number | boolean | undefined | null> | undefined): string;
|
|
120
|
+
/**
|
|
121
|
+
* Reads the response body and tries to parse as JSON. Returns BOTH the
|
|
122
|
+
* parsed value (or null on parse failure / empty body) AND the raw text.
|
|
123
|
+
* Callers on the success path use only `parsed`; callers on the error
|
|
124
|
+
* path fall back to `raw` when `parsed` is null so non-JSON error bodies
|
|
125
|
+
* (LB 502 HTML, plain-text proxy errors) are surfaced as
|
|
126
|
+
* `AttestryAPIError.details` rather than silently lost. (Hostile-review
|
|
127
|
+
* H1.)
|
|
128
|
+
*/
|
|
129
|
+
declare function readBody(response: Response): Promise<{
|
|
130
|
+
parsed: unknown;
|
|
131
|
+
raw: string;
|
|
132
|
+
}>;
|
|
133
|
+
declare function extractMessage(detail: unknown): string | null;
|
|
134
|
+
export declare const __test__: {
|
|
135
|
+
DEFAULT_BASE_URL: string;
|
|
136
|
+
DEFAULT_TIMEOUT_MS: number;
|
|
137
|
+
encodeQuery: typeof encodeQuery;
|
|
138
|
+
readBody: typeof readBody;
|
|
139
|
+
extractMessage: typeof extractMessage;
|
|
140
|
+
};
|
|
141
|
+
export {};
|
|
142
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AA6BA,OAAO,EAML,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,qBAAqB,EACrB,SAAS,EACT,cAAc,EACf,MAAM,YAAY,CAAC;AAKpB,UAAU,oBAAoB;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,qBAAqB,GAC7B,oBAAoB,CAwCtB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhE;AAED,UAAU,mBAAmB;IAC3B,MAAM,EAAE,oBAAoB,CAAC;IAC7B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC;IACrE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,CAsBtE;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,CAAC,CAAC,CA8I1E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC,CAAC;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB;;;;;;;;OAQG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyBpB;AA+HD,iBAAS,WAAW,CAClB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC,GAAG,SAAS,GAC1E,MAAM,CAQR;AAED;;;;;;;;GAQG;AACH,iBAAe,QAAQ,CACrB,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAS3C;AAED,iBAAS,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CActD;AAcD,eAAO,MAAM,QAAQ;;;;;;CAMpB,CAAC"}
|