@introspection-sdk/introspection-node 0.5.1 → 0.5.2
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/.turbo/turbo-build.log +1 -1
- package/dist/http.d.ts +6 -19
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +14 -139
- package/dist/http.js.map +1 -1
- package/dist/pagination.d.ts +4 -35
- package/dist/pagination.d.ts.map +1 -1
- package/dist/pagination.js +4 -40
- package/dist/pagination.js.map +1 -1
- package/dist/streaming.d.ts +4 -5
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +4 -64
- package/dist/streaming.js.map +1 -1
- package/package.json +14 -13
- package/src/http.ts +15 -145
- package/src/pagination.ts +8 -67
- package/src/streaming.ts +4 -55
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
> @introspection-sdk/introspection-node@0.5.
|
|
2
|
+
> @introspection-sdk/introspection-node@0.5.2 build /home/runner/work/introspection-js-sdk/introspection-js-sdk/packages/introspection-node
|
|
3
3
|
> tsc
|
|
4
4
|
|
package/dist/http.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BaseHttpClient } from "@introspection-sdk/http";
|
|
1
2
|
export interface ResolvedApiConfig {
|
|
2
3
|
/**
|
|
3
4
|
* Base URL the client will prepend to every request path. For the
|
|
@@ -11,26 +12,12 @@ export interface ResolvedApiConfig {
|
|
|
11
12
|
fetch?: typeof fetch;
|
|
12
13
|
}
|
|
13
14
|
/**
|
|
14
|
-
*
|
|
15
|
-
* each DP-bound Runner.
|
|
16
|
-
*
|
|
15
|
+
* Bearer-token HTTP client used by both the CP-bound IntrospectionClient
|
|
16
|
+
* and each DP-bound Runner. The request/response machinery lives in the
|
|
17
|
+
* shared {@link BaseHttpClient}; this only pins the auth strategy —
|
|
18
|
+
* `Authorization: Bearer <token>` on every request, no cookie credentials.
|
|
17
19
|
*/
|
|
18
|
-
export declare class HttpClient {
|
|
19
|
-
private readonly cfg;
|
|
20
|
-
private readonly fetchImpl;
|
|
20
|
+
export declare class HttpClient extends BaseHttpClient {
|
|
21
21
|
constructor(cfg: ResolvedApiConfig);
|
|
22
|
-
private headers;
|
|
23
|
-
request<T>(opts: {
|
|
24
|
-
method: string;
|
|
25
|
-
path: string;
|
|
26
|
-
query?: Record<string, unknown>;
|
|
27
|
-
body?: unknown;
|
|
28
|
-
headers?: Record<string, string>;
|
|
29
|
-
expect?: "json" | "empty" | "bytes" | "stream";
|
|
30
|
-
}): Promise<T>;
|
|
31
|
-
stream(opts: {
|
|
32
|
-
path: string;
|
|
33
|
-
query?: Record<string, unknown>;
|
|
34
|
-
}): Promise<Response>;
|
|
35
22
|
}
|
|
36
23
|
//# sourceMappingURL=http.d.ts.map
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEzD,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED;;;;;GAKG;AACH,qBAAa,UAAW,SAAQ,cAAc;gBAChC,GAAG,EAAE,iBAAiB;CAUnC"}
|
package/dist/http.js
CHANGED
|
@@ -1,145 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
function joinUrl(base, path) {
|
|
3
|
-
const b = base.replace(/\/+$/, "");
|
|
4
|
-
const p = path.startsWith("/") ? path : `/${path}`;
|
|
5
|
-
return `${b}${p}`;
|
|
6
|
-
}
|
|
7
|
-
function buildQuery(params) {
|
|
8
|
-
if (!params)
|
|
9
|
-
return "";
|
|
10
|
-
const sp = new URLSearchParams();
|
|
11
|
-
for (const [k, v] of Object.entries(params)) {
|
|
12
|
-
if (v === undefined || v === null)
|
|
13
|
-
continue;
|
|
14
|
-
if (Array.isArray(v)) {
|
|
15
|
-
for (const item of v)
|
|
16
|
-
sp.append(k, String(item));
|
|
17
|
-
}
|
|
18
|
-
else {
|
|
19
|
-
sp.set(k, String(v));
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
const q = sp.toString();
|
|
23
|
-
return q ? `?${q}` : "";
|
|
24
|
-
}
|
|
1
|
+
import { BaseHttpClient } from "@introspection-sdk/http";
|
|
25
2
|
/**
|
|
26
|
-
*
|
|
27
|
-
* each DP-bound Runner.
|
|
28
|
-
*
|
|
3
|
+
* Bearer-token HTTP client used by both the CP-bound IntrospectionClient
|
|
4
|
+
* and each DP-bound Runner. The request/response machinery lives in the
|
|
5
|
+
* shared {@link BaseHttpClient}; this only pins the auth strategy —
|
|
6
|
+
* `Authorization: Bearer <token>` on every request, no cookie credentials.
|
|
29
7
|
*/
|
|
30
|
-
export class HttpClient {
|
|
31
|
-
cfg;
|
|
32
|
-
fetchImpl;
|
|
8
|
+
export class HttpClient extends BaseHttpClient {
|
|
33
9
|
constructor(cfg) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
Authorization: `Bearer ${this.cfg.token}`,
|
|
43
|
-
...(this.cfg.additionalHeaders ?? {}),
|
|
44
|
-
...(extra ?? {}),
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
async request(opts) {
|
|
48
|
-
const url = joinUrl(this.cfg.apiUrl, opts.path) + buildQuery(opts.query);
|
|
49
|
-
let body;
|
|
50
|
-
const headers = this.headers(opts.headers);
|
|
51
|
-
if (opts.body instanceof FormData) {
|
|
52
|
-
body = opts.body;
|
|
53
|
-
// let fetch set the multipart boundary
|
|
54
|
-
}
|
|
55
|
-
else if (opts.body !== undefined) {
|
|
56
|
-
headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
|
|
57
|
-
body = JSON.stringify(opts.body);
|
|
58
|
-
}
|
|
59
|
-
let res;
|
|
60
|
-
try {
|
|
61
|
-
res = await this.fetchImpl(url, {
|
|
62
|
-
method: opts.method,
|
|
63
|
-
headers,
|
|
64
|
-
body,
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
throw new NetworkError({
|
|
69
|
-
message: err instanceof Error ? err.message : "network request failed",
|
|
70
|
-
code: null,
|
|
71
|
-
requestId: null,
|
|
72
|
-
body: err,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
if (!res.ok)
|
|
76
|
-
throw await toApiError(res);
|
|
77
|
-
const expect = opts.expect ?? "json";
|
|
78
|
-
if (expect === "empty")
|
|
79
|
-
return undefined;
|
|
80
|
-
if (expect === "bytes")
|
|
81
|
-
return new Uint8Array(await res.arrayBuffer());
|
|
82
|
-
if (expect === "stream")
|
|
83
|
-
return res.body;
|
|
84
|
-
return (await res.json());
|
|
85
|
-
}
|
|
86
|
-
async stream(opts) {
|
|
87
|
-
const url = joinUrl(this.cfg.apiUrl, opts.path) + buildQuery(opts.query);
|
|
88
|
-
let res;
|
|
89
|
-
try {
|
|
90
|
-
res = await this.fetchImpl(url, {
|
|
91
|
-
method: "GET",
|
|
92
|
-
headers: this.headers({ Accept: "text/event-stream" }),
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
catch (err) {
|
|
96
|
-
throw new NetworkError({
|
|
97
|
-
message: err instanceof Error ? err.message : "network request failed",
|
|
98
|
-
code: null,
|
|
99
|
-
requestId: null,
|
|
100
|
-
body: err,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
if (!res.ok)
|
|
104
|
-
throw await toApiError(res);
|
|
105
|
-
return res;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
async function toApiError(res) {
|
|
109
|
-
let body = undefined;
|
|
110
|
-
let message = `HTTP ${res.status}`;
|
|
111
|
-
let code = null;
|
|
112
|
-
const ct = res.headers.get("content-type") ?? "";
|
|
113
|
-
if (ct.includes("json")) {
|
|
114
|
-
body = await res.json().catch(() => undefined);
|
|
115
|
-
if (body && typeof body === "object") {
|
|
116
|
-
const obj = body;
|
|
117
|
-
if (typeof obj.detail === "string")
|
|
118
|
-
message = obj.detail;
|
|
119
|
-
if (typeof obj.code === "string")
|
|
120
|
-
code = obj.code;
|
|
121
|
-
if (typeof obj.message === "string" && message === `HTTP ${res.status}`) {
|
|
122
|
-
message = obj.message;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
body = await res.text().catch(() => undefined);
|
|
128
|
-
}
|
|
129
|
-
const retryAfterHeader = res.headers.get("retry-after");
|
|
130
|
-
let retryAfter = null;
|
|
131
|
-
if (retryAfterHeader) {
|
|
132
|
-
const n = Number(retryAfterHeader);
|
|
133
|
-
if (Number.isFinite(n))
|
|
134
|
-
retryAfter = n;
|
|
10
|
+
super({
|
|
11
|
+
apiUrl: cfg.apiUrl,
|
|
12
|
+
additionalHeaders: cfg.additionalHeaders,
|
|
13
|
+
fetch: cfg.fetch,
|
|
14
|
+
transport: {
|
|
15
|
+
authHeaders: () => ({ Authorization: `Bearer ${cfg.token}` }),
|
|
16
|
+
},
|
|
17
|
+
});
|
|
135
18
|
}
|
|
136
|
-
return apiErrorFromResponse({
|
|
137
|
-
status: res.status,
|
|
138
|
-
message,
|
|
139
|
-
code,
|
|
140
|
-
requestId: res.headers.get("x-request-id"),
|
|
141
|
-
body,
|
|
142
|
-
retryAfter,
|
|
143
|
-
});
|
|
144
19
|
}
|
|
145
20
|
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAezD;;;;;GAKG;AACH,MAAM,OAAO,UAAW,SAAQ,cAAc;IAC5C,YAAY,GAAsB;QAChC,KAAK,CAAC;YACJ,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;YACxC,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,SAAS,EAAE;gBACT,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC;aAC9D;SACF,CAAC,CAAC;IACL,CAAC;CACF"}
|
package/dist/pagination.d.ts
CHANGED
|
@@ -1,38 +1,7 @@
|
|
|
1
|
-
import { Paginated } from "@introspection-sdk/types";
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Cursor pagination now lives in the shared `@introspection-sdk/http`
|
|
3
|
+
* core so the browser and Node SDKs share one `Paginator`. Re-exported
|
|
4
|
+
* here to keep the existing `./pagination.js` import path stable.
|
|
6
5
|
*/
|
|
7
|
-
export
|
|
8
|
-
/** Fetch the page at `cursor` (`undefined` = the first page). */
|
|
9
|
-
fetch(cursor: string | undefined): Promise<TPage>;
|
|
10
|
-
/** Items contained in `page`, in iteration order. */
|
|
11
|
-
items(page: TPage): T[];
|
|
12
|
-
/** Cursor for the page after `page`, or `undefined` when exhausted. */
|
|
13
|
-
next(page: TPage): string | undefined;
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* A lazy, auto-paging collection.
|
|
17
|
-
*
|
|
18
|
-
* - `await listing` resolves to the FIRST page, preserving the wire
|
|
19
|
-
* envelope's metadata (counts, `has_more`, …).
|
|
20
|
-
* - `for await (const item of listing)` streams every item across all
|
|
21
|
-
* pages, fetching each page only as the iterator reaches it; stop
|
|
22
|
-
* early to stop fetching.
|
|
23
|
-
*/
|
|
24
|
-
export declare class Paginator<T, TPage = Paginated<T>> implements AsyncIterable<T>, PromiseLike<TPage> {
|
|
25
|
-
private readonly source;
|
|
26
|
-
private readonly start?;
|
|
27
|
-
constructor(source: PageSource<T, TPage>, start?: string | undefined);
|
|
28
|
-
/** Thenable: resolves to the first page (no further pages fetched). */
|
|
29
|
-
then<R1 = TPage, R2 = never>(onfulfilled?: ((page: TPage) => R1 | PromiseLike<R1>) | null, onrejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | null): PromiseLike<R1 | R2>;
|
|
30
|
-
[Symbol.asyncIterator](): AsyncIterator<T>;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Build a {@link Paginator} over the standard Introspection cursor
|
|
34
|
-
* envelope ({@link Paginated}): items live in `records` and the next
|
|
35
|
-
* page is reached via the opaque `next` token.
|
|
36
|
-
*/
|
|
37
|
-
export declare function cursorPaginate<T>(fetch: (next: string | undefined) => Promise<Paginated<T>>, start?: string): Paginator<T, Paginated<T>>;
|
|
6
|
+
export { Paginator, cursorPaginate, type PageSource, } from "@introspection-sdk/http";
|
|
38
7
|
//# sourceMappingURL=pagination.d.ts.map
|
package/dist/pagination.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,SAAS,EACT,cAAc,EACd,KAAK,UAAU,GAChB,MAAM,yBAAyB,CAAC"}
|
package/dist/pagination.js
CHANGED
|
@@ -1,43 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* envelope's metadata (counts, `has_more`, …).
|
|
6
|
-
* - `for await (const item of listing)` streams every item across all
|
|
7
|
-
* pages, fetching each page only as the iterator reaches it; stop
|
|
8
|
-
* early to stop fetching.
|
|
2
|
+
* Cursor pagination now lives in the shared `@introspection-sdk/http`
|
|
3
|
+
* core so the browser and Node SDKs share one `Paginator`. Re-exported
|
|
4
|
+
* here to keep the existing `./pagination.js` import path stable.
|
|
9
5
|
*/
|
|
10
|
-
export
|
|
11
|
-
source;
|
|
12
|
-
start;
|
|
13
|
-
constructor(source, start) {
|
|
14
|
-
this.source = source;
|
|
15
|
-
this.start = start;
|
|
16
|
-
}
|
|
17
|
-
/** Thenable: resolves to the first page (no further pages fetched). */
|
|
18
|
-
then(onfulfilled, onrejected) {
|
|
19
|
-
return this.source.fetch(this.start).then(onfulfilled, onrejected);
|
|
20
|
-
}
|
|
21
|
-
async *[Symbol.asyncIterator]() {
|
|
22
|
-
let cursor = this.start;
|
|
23
|
-
do {
|
|
24
|
-
const page = await this.source.fetch(cursor);
|
|
25
|
-
for (const item of this.source.items(page))
|
|
26
|
-
yield item;
|
|
27
|
-
cursor = this.source.next(page);
|
|
28
|
-
} while (cursor !== undefined);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Build a {@link Paginator} over the standard Introspection cursor
|
|
33
|
-
* envelope ({@link Paginated}): items live in `records` and the next
|
|
34
|
-
* page is reached via the opaque `next` token.
|
|
35
|
-
*/
|
|
36
|
-
export function cursorPaginate(fetch, start) {
|
|
37
|
-
return new Paginator({
|
|
38
|
-
fetch,
|
|
39
|
-
items: (page) => page.records,
|
|
40
|
-
next: (page) => page.next ?? undefined,
|
|
41
|
-
}, start);
|
|
42
|
-
}
|
|
6
|
+
export { Paginator, cursorPaginate, } from "@introspection-sdk/http";
|
|
43
7
|
//# sourceMappingURL=pagination.js.map
|
package/dist/pagination.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pagination.js","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,SAAS,EACT,cAAc,GAEf,MAAM,yBAAyB,CAAC"}
|
package/dist/streaming.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type { SseEvent } from "@introspection-sdk/types";
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* SSE parsing now lives in the shared `@introspection-sdk/http` core so
|
|
3
|
+
* the browser and Node SDKs stay byte-for-byte identical. Re-exported
|
|
4
|
+
* here to keep the existing `./streaming.js` import path stable.
|
|
6
5
|
*/
|
|
7
|
-
export
|
|
6
|
+
export { parseSse } from "@introspection-sdk/http";
|
|
8
7
|
//# sourceMappingURL=streaming.d.ts.map
|
package/dist/streaming.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/streaming.js
CHANGED
|
@@ -1,67 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* SSE parsing now lives in the shared `@introspection-sdk/http` core so
|
|
3
|
+
* the browser and Node SDKs stay byte-for-byte identical. Re-exported
|
|
4
|
+
* here to keep the existing `./streaming.js` import path stable.
|
|
5
5
|
*/
|
|
6
|
-
export
|
|
7
|
-
if (!res.body)
|
|
8
|
-
return;
|
|
9
|
-
const reader = res.body.getReader();
|
|
10
|
-
const decoder = new TextDecoder();
|
|
11
|
-
let buf = "";
|
|
12
|
-
let event = "message";
|
|
13
|
-
let data = [];
|
|
14
|
-
let id;
|
|
15
|
-
let retry;
|
|
16
|
-
const flush = () => {
|
|
17
|
-
if (data.length === 0 && event === "message" && id === undefined)
|
|
18
|
-
return null;
|
|
19
|
-
const ev = { event, data: data.join("\n") };
|
|
20
|
-
if (id !== undefined)
|
|
21
|
-
ev.id = id;
|
|
22
|
-
if (retry !== undefined)
|
|
23
|
-
ev.retry = retry;
|
|
24
|
-
event = "message";
|
|
25
|
-
data = [];
|
|
26
|
-
id = undefined;
|
|
27
|
-
retry = undefined;
|
|
28
|
-
return ev;
|
|
29
|
-
};
|
|
30
|
-
while (true) {
|
|
31
|
-
const { value, done } = await reader.read();
|
|
32
|
-
if (done)
|
|
33
|
-
break;
|
|
34
|
-
buf += decoder.decode(value, { stream: true });
|
|
35
|
-
let nl;
|
|
36
|
-
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
37
|
-
const line = buf.slice(0, nl).replace(/\r$/, "");
|
|
38
|
-
buf = buf.slice(nl + 1);
|
|
39
|
-
if (line === "") {
|
|
40
|
-
const ev = flush();
|
|
41
|
-
if (ev)
|
|
42
|
-
yield ev;
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
if (line.startsWith(":"))
|
|
46
|
-
continue;
|
|
47
|
-
const colon = line.indexOf(":");
|
|
48
|
-
const field = colon < 0 ? line : line.slice(0, colon);
|
|
49
|
-
const valueRaw = colon < 0 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
50
|
-
if (field === "event")
|
|
51
|
-
event = valueRaw;
|
|
52
|
-
else if (field === "data")
|
|
53
|
-
data.push(valueRaw);
|
|
54
|
-
else if (field === "id")
|
|
55
|
-
id = valueRaw;
|
|
56
|
-
else if (field === "retry") {
|
|
57
|
-
const n = Number(valueRaw);
|
|
58
|
-
if (Number.isFinite(n))
|
|
59
|
-
retry = n;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const ev = flush();
|
|
64
|
-
if (ev)
|
|
65
|
-
yield ev;
|
|
66
|
-
}
|
|
6
|
+
export { parseSse } from "@introspection-sdk/http";
|
|
67
7
|
//# sourceMappingURL=streaming.js.map
|
package/dist/streaming.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streaming.js","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"streaming.js","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@introspection-sdk/introspection-node",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Introspection observability SDK for Node.js",
|
|
6
6
|
"engines": {
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@openai/agents": "^0.11.6",
|
|
36
|
-
"@opentelemetry/api-logs": "^0.
|
|
37
|
-
"@opentelemetry/context-async-hooks": "^2.
|
|
38
|
-
"@opentelemetry/core": "^2.
|
|
39
|
-
"@opentelemetry/exporter-logs-otlp-proto": "^0.
|
|
40
|
-
"@opentelemetry/exporter-trace-otlp-proto": "^0.
|
|
41
|
-
"@opentelemetry/resources": "^2.
|
|
42
|
-
"@opentelemetry/sdk-logs": "^0.
|
|
43
|
-
"@opentelemetry/sdk-trace-base": "^2.
|
|
44
|
-
"@opentelemetry/sdk-trace-node": "^2.
|
|
36
|
+
"@opentelemetry/api-logs": "^0.219.0",
|
|
37
|
+
"@opentelemetry/context-async-hooks": "^2.8.0",
|
|
38
|
+
"@opentelemetry/core": "^2.8.0",
|
|
39
|
+
"@opentelemetry/exporter-logs-otlp-proto": "^0.219.0",
|
|
40
|
+
"@opentelemetry/exporter-trace-otlp-proto": "^0.219.0",
|
|
41
|
+
"@opentelemetry/resources": "^2.8.0",
|
|
42
|
+
"@opentelemetry/sdk-logs": "^0.219.0",
|
|
43
|
+
"@opentelemetry/sdk-trace-base": "^2.8.0",
|
|
44
|
+
"@opentelemetry/sdk-trace-node": "^2.8.0",
|
|
45
45
|
"@opentelemetry/semantic-conventions": "^1.41.1",
|
|
46
46
|
"@types/node": "^22.19.20",
|
|
47
47
|
"eslint": "^9.39.4",
|
|
@@ -139,9 +139,10 @@
|
|
|
139
139
|
"@opentelemetry/api": "^1.9.1",
|
|
140
140
|
"https-proxy-agent": "^7.0.6",
|
|
141
141
|
"proxy-from-env": "^2.1.0",
|
|
142
|
-
"@introspection-sdk/types": "0.5.
|
|
143
|
-
"@introspection-sdk/
|
|
144
|
-
"@introspection-sdk/introspection-
|
|
142
|
+
"@introspection-sdk/types": "0.5.2",
|
|
143
|
+
"@introspection-sdk/http": "0.5.2",
|
|
144
|
+
"@introspection-sdk/introspection-pi": "0.5.2",
|
|
145
|
+
"@introspection-sdk/introspection-proxy": "0.5.2"
|
|
145
146
|
},
|
|
146
147
|
"scripts": {
|
|
147
148
|
"lint": "eslint . --max-warnings 0",
|
package/src/http.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
IntrospectionAPIError,
|
|
3
|
-
NetworkError,
|
|
4
|
-
apiErrorFromResponse,
|
|
5
|
-
} from "@introspection-sdk/types";
|
|
1
|
+
import { BaseHttpClient } from "@introspection-sdk/http";
|
|
6
2
|
|
|
7
3
|
export interface ResolvedApiConfig {
|
|
8
4
|
/**
|
|
@@ -17,147 +13,21 @@ export interface ResolvedApiConfig {
|
|
|
17
13
|
fetch?: typeof fetch;
|
|
18
14
|
}
|
|
19
15
|
|
|
20
|
-
function joinUrl(base: string, path: string): string {
|
|
21
|
-
const b = base.replace(/\/+$/, "");
|
|
22
|
-
const p = path.startsWith("/") ? path : `/${path}`;
|
|
23
|
-
return `${b}${p}`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function buildQuery(params?: Record<string, unknown>): string {
|
|
27
|
-
if (!params) return "";
|
|
28
|
-
const sp = new URLSearchParams();
|
|
29
|
-
for (const [k, v] of Object.entries(params)) {
|
|
30
|
-
if (v === undefined || v === null) continue;
|
|
31
|
-
if (Array.isArray(v)) {
|
|
32
|
-
for (const item of v) sp.append(k, String(item));
|
|
33
|
-
} else {
|
|
34
|
-
sp.set(k, String(v));
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const q = sp.toString();
|
|
38
|
-
return q ? `?${q}` : "";
|
|
39
|
-
}
|
|
40
|
-
|
|
41
16
|
/**
|
|
42
|
-
*
|
|
43
|
-
* each DP-bound Runner.
|
|
44
|
-
*
|
|
17
|
+
* Bearer-token HTTP client used by both the CP-bound IntrospectionClient
|
|
18
|
+
* and each DP-bound Runner. The request/response machinery lives in the
|
|
19
|
+
* shared {@link BaseHttpClient}; this only pins the auth strategy —
|
|
20
|
+
* `Authorization: Bearer <token>` on every request, no cookie credentials.
|
|
45
21
|
*/
|
|
46
|
-
export class HttpClient {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private headers(extra?: Record<string, string>): Record<string, string> {
|
|
59
|
-
return {
|
|
60
|
-
Authorization: `Bearer ${this.cfg.token}`,
|
|
61
|
-
...(this.cfg.additionalHeaders ?? {}),
|
|
62
|
-
...(extra ?? {}),
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async request<T>(opts: {
|
|
67
|
-
method: string;
|
|
68
|
-
path: string;
|
|
69
|
-
query?: Record<string, unknown>;
|
|
70
|
-
body?: unknown;
|
|
71
|
-
headers?: Record<string, string>;
|
|
72
|
-
expect?: "json" | "empty" | "bytes" | "stream";
|
|
73
|
-
}): Promise<T> {
|
|
74
|
-
const url = joinUrl(this.cfg.apiUrl, opts.path) + buildQuery(opts.query);
|
|
75
|
-
let body: BodyInit | undefined;
|
|
76
|
-
const headers = this.headers(opts.headers);
|
|
77
|
-
if (opts.body instanceof FormData) {
|
|
78
|
-
body = opts.body;
|
|
79
|
-
// let fetch set the multipart boundary
|
|
80
|
-
} else if (opts.body !== undefined) {
|
|
81
|
-
headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
|
|
82
|
-
body = JSON.stringify(opts.body);
|
|
83
|
-
}
|
|
84
|
-
let res: Response;
|
|
85
|
-
try {
|
|
86
|
-
res = await this.fetchImpl(url, {
|
|
87
|
-
method: opts.method,
|
|
88
|
-
headers,
|
|
89
|
-
body,
|
|
90
|
-
});
|
|
91
|
-
} catch (err) {
|
|
92
|
-
throw new NetworkError({
|
|
93
|
-
message: err instanceof Error ? err.message : "network request failed",
|
|
94
|
-
code: null,
|
|
95
|
-
requestId: null,
|
|
96
|
-
body: err,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
if (!res.ok) throw await toApiError(res);
|
|
100
|
-
const expect = opts.expect ?? "json";
|
|
101
|
-
if (expect === "empty") return undefined as T;
|
|
102
|
-
if (expect === "bytes") return new Uint8Array(await res.arrayBuffer()) as T;
|
|
103
|
-
if (expect === "stream") return res.body as T;
|
|
104
|
-
return (await res.json()) as T;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async stream(opts: {
|
|
108
|
-
path: string;
|
|
109
|
-
query?: Record<string, unknown>;
|
|
110
|
-
}): Promise<Response> {
|
|
111
|
-
const url = joinUrl(this.cfg.apiUrl, opts.path) + buildQuery(opts.query);
|
|
112
|
-
let res: Response;
|
|
113
|
-
try {
|
|
114
|
-
res = await this.fetchImpl(url, {
|
|
115
|
-
method: "GET",
|
|
116
|
-
headers: this.headers({ Accept: "text/event-stream" }),
|
|
117
|
-
});
|
|
118
|
-
} catch (err) {
|
|
119
|
-
throw new NetworkError({
|
|
120
|
-
message: err instanceof Error ? err.message : "network request failed",
|
|
121
|
-
code: null,
|
|
122
|
-
requestId: null,
|
|
123
|
-
body: err,
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
if (!res.ok) throw await toApiError(res);
|
|
127
|
-
return res;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async function toApiError(res: Response): Promise<IntrospectionAPIError> {
|
|
132
|
-
let body: unknown = undefined;
|
|
133
|
-
let message = `HTTP ${res.status}`;
|
|
134
|
-
let code: string | null = null;
|
|
135
|
-
const ct = res.headers.get("content-type") ?? "";
|
|
136
|
-
if (ct.includes("json")) {
|
|
137
|
-
body = await res.json().catch(() => undefined);
|
|
138
|
-
if (body && typeof body === "object") {
|
|
139
|
-
const obj = body as Record<string, unknown>;
|
|
140
|
-
if (typeof obj.detail === "string") message = obj.detail;
|
|
141
|
-
if (typeof obj.code === "string") code = obj.code;
|
|
142
|
-
if (typeof obj.message === "string" && message === `HTTP ${res.status}`) {
|
|
143
|
-
message = obj.message;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
body = await res.text().catch(() => undefined);
|
|
148
|
-
}
|
|
149
|
-
const retryAfterHeader = res.headers.get("retry-after");
|
|
150
|
-
let retryAfter: number | null = null;
|
|
151
|
-
if (retryAfterHeader) {
|
|
152
|
-
const n = Number(retryAfterHeader);
|
|
153
|
-
if (Number.isFinite(n)) retryAfter = n;
|
|
22
|
+
export class HttpClient extends BaseHttpClient {
|
|
23
|
+
constructor(cfg: ResolvedApiConfig) {
|
|
24
|
+
super({
|
|
25
|
+
apiUrl: cfg.apiUrl,
|
|
26
|
+
additionalHeaders: cfg.additionalHeaders,
|
|
27
|
+
fetch: cfg.fetch,
|
|
28
|
+
transport: {
|
|
29
|
+
authHeaders: () => ({ Authorization: `Bearer ${cfg.token}` }),
|
|
30
|
+
},
|
|
31
|
+
});
|
|
154
32
|
}
|
|
155
|
-
return apiErrorFromResponse({
|
|
156
|
-
status: res.status,
|
|
157
|
-
message,
|
|
158
|
-
code,
|
|
159
|
-
requestId: res.headers.get("x-request-id"),
|
|
160
|
-
body,
|
|
161
|
-
retryAfter,
|
|
162
|
-
});
|
|
163
33
|
}
|
package/src/pagination.ts
CHANGED
|
@@ -1,69 +1,10 @@
|
|
|
1
|
-
import { Paginated } from "@introspection-sdk/types";
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Cursor pagination now lives in the shared `@introspection-sdk/http`
|
|
3
|
+
* core so the browser and Node SDKs share one `Paginator`. Re-exported
|
|
4
|
+
* here to keep the existing `./pagination.js` import path stable.
|
|
7
5
|
*/
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
/** Cursor for the page after `page`, or `undefined` when exhausted. */
|
|
14
|
-
next(page: TPage): string | undefined;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* A lazy, auto-paging collection.
|
|
19
|
-
*
|
|
20
|
-
* - `await listing` resolves to the FIRST page, preserving the wire
|
|
21
|
-
* envelope's metadata (counts, `has_more`, …).
|
|
22
|
-
* - `for await (const item of listing)` streams every item across all
|
|
23
|
-
* pages, fetching each page only as the iterator reaches it; stop
|
|
24
|
-
* early to stop fetching.
|
|
25
|
-
*/
|
|
26
|
-
export class Paginator<T, TPage = Paginated<T>>
|
|
27
|
-
implements AsyncIterable<T>, PromiseLike<TPage>
|
|
28
|
-
{
|
|
29
|
-
constructor(
|
|
30
|
-
private readonly source: PageSource<T, TPage>,
|
|
31
|
-
private readonly start?: string,
|
|
32
|
-
) {}
|
|
33
|
-
|
|
34
|
-
/** Thenable: resolves to the first page (no further pages fetched). */
|
|
35
|
-
then<R1 = TPage, R2 = never>(
|
|
36
|
-
onfulfilled?: ((page: TPage) => R1 | PromiseLike<R1>) | null,
|
|
37
|
-
onrejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | null,
|
|
38
|
-
): PromiseLike<R1 | R2> {
|
|
39
|
-
return this.source.fetch(this.start).then(onfulfilled, onrejected);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async *[Symbol.asyncIterator](): AsyncIterator<T> {
|
|
43
|
-
let cursor = this.start;
|
|
44
|
-
do {
|
|
45
|
-
const page = await this.source.fetch(cursor);
|
|
46
|
-
for (const item of this.source.items(page)) yield item;
|
|
47
|
-
cursor = this.source.next(page);
|
|
48
|
-
} while (cursor !== undefined);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Build a {@link Paginator} over the standard Introspection cursor
|
|
54
|
-
* envelope ({@link Paginated}): items live in `records` and the next
|
|
55
|
-
* page is reached via the opaque `next` token.
|
|
56
|
-
*/
|
|
57
|
-
export function cursorPaginate<T>(
|
|
58
|
-
fetch: (next: string | undefined) => Promise<Paginated<T>>,
|
|
59
|
-
start?: string,
|
|
60
|
-
): Paginator<T, Paginated<T>> {
|
|
61
|
-
return new Paginator(
|
|
62
|
-
{
|
|
63
|
-
fetch,
|
|
64
|
-
items: (page) => page.records,
|
|
65
|
-
next: (page) => page.next ?? undefined,
|
|
66
|
-
},
|
|
67
|
-
start,
|
|
68
|
-
);
|
|
69
|
-
}
|
|
6
|
+
export {
|
|
7
|
+
Paginator,
|
|
8
|
+
cursorPaginate,
|
|
9
|
+
type PageSource,
|
|
10
|
+
} from "@introspection-sdk/http";
|
package/src/streaming.ts
CHANGED
|
@@ -1,57 +1,6 @@
|
|
|
1
|
-
import type { SseEvent } from "@introspection-sdk/types";
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* SSE parsing now lives in the shared `@introspection-sdk/http` core so
|
|
3
|
+
* the browser and Node SDKs stay byte-for-byte identical. Re-exported
|
|
4
|
+
* here to keep the existing `./streaming.js` import path stable.
|
|
7
5
|
*/
|
|
8
|
-
export
|
|
9
|
-
if (!res.body) return;
|
|
10
|
-
const reader = res.body.getReader();
|
|
11
|
-
const decoder = new TextDecoder();
|
|
12
|
-
let buf = "";
|
|
13
|
-
let event = "message";
|
|
14
|
-
let data: string[] = [];
|
|
15
|
-
let id: string | undefined;
|
|
16
|
-
let retry: number | undefined;
|
|
17
|
-
const flush = (): SseEvent | null => {
|
|
18
|
-
if (data.length === 0 && event === "message" && id === undefined)
|
|
19
|
-
return null;
|
|
20
|
-
const ev: SseEvent = { event, data: data.join("\n") };
|
|
21
|
-
if (id !== undefined) ev.id = id;
|
|
22
|
-
if (retry !== undefined) ev.retry = retry;
|
|
23
|
-
event = "message";
|
|
24
|
-
data = [];
|
|
25
|
-
id = undefined;
|
|
26
|
-
retry = undefined;
|
|
27
|
-
return ev;
|
|
28
|
-
};
|
|
29
|
-
while (true) {
|
|
30
|
-
const { value, done } = await reader.read();
|
|
31
|
-
if (done) break;
|
|
32
|
-
buf += decoder.decode(value, { stream: true });
|
|
33
|
-
let nl: number;
|
|
34
|
-
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
35
|
-
const line = buf.slice(0, nl).replace(/\r$/, "");
|
|
36
|
-
buf = buf.slice(nl + 1);
|
|
37
|
-
if (line === "") {
|
|
38
|
-
const ev = flush();
|
|
39
|
-
if (ev) yield ev;
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (line.startsWith(":")) continue;
|
|
43
|
-
const colon = line.indexOf(":");
|
|
44
|
-
const field = colon < 0 ? line : line.slice(0, colon);
|
|
45
|
-
const valueRaw = colon < 0 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
46
|
-
if (field === "event") event = valueRaw;
|
|
47
|
-
else if (field === "data") data.push(valueRaw);
|
|
48
|
-
else if (field === "id") id = valueRaw;
|
|
49
|
-
else if (field === "retry") {
|
|
50
|
-
const n = Number(valueRaw);
|
|
51
|
-
if (Number.isFinite(n)) retry = n;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
const ev = flush();
|
|
56
|
-
if (ev) yield ev;
|
|
57
|
-
}
|
|
6
|
+
export { parseSse } from "@introspection-sdk/http";
|