@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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @introspection-sdk/introspection-node@0.5.1 build /home/runner/work/introspection-js-sdk/introspection-js-sdk/packages/introspection-node
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
- * Thin HTTP wrapper used by both the CP-bound IntrospectionClient and
15
- * each DP-bound Runner. Has no opinion on which one it is — the caller
16
- * picks the base URL and the token.
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
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAMA,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;AAuBD;;;;GAIG;AACH,qBAAa,UAAU;IAGT,OAAO,CAAC,QAAQ,CAAC,GAAG;IAFhC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;gBAEZ,GAAG,EAAE,iBAAiB;IASnD,OAAO,CAAC,OAAO;IAQT,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAChC,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;KAChD,GAAG,OAAO,CAAC,CAAC,CAAC;IAkCR,MAAM,CAAC,IAAI,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACjC,GAAG,OAAO,CAAC,QAAQ,CAAC;CAmBtB"}
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 { NetworkError, apiErrorFromResponse, } from "@introspection-sdk/types";
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
- * Thin HTTP wrapper used by both the CP-bound IntrospectionClient and
27
- * each DP-bound Runner. Has no opinion on which one it is — the caller
28
- * picks the base URL and the token.
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
- this.cfg = cfg;
35
- this.fetchImpl = cfg.fetch ?? globalThis.fetch;
36
- if (!this.fetchImpl) {
37
- throw new Error("global fetch is unavailable; pass `advanced.fetch` or run on Node 18+");
38
- }
39
- }
40
- headers(extra) {
41
- return {
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":"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"}
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"}
@@ -1,38 +1,7 @@
1
- import { Paginated } from "@introspection-sdk/types";
2
1
  /**
3
- * Adapts a wire pagination protocol for {@link Paginator}: how to fetch
4
- * a page at a given cursor, pull the items out of it, and find the
5
- * cursor for the next page (`undefined` once the list is exhausted).
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 interface PageSource<T, TPage> {
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
@@ -1 +1 @@
1
- {"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAErD;;;;GAIG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC,EAAE,KAAK;IAClC,iEAAiE;IACjE,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAClD,qDAAqD;IACrD,KAAK,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC;IACxB,uEAAuE;IACvE,IAAI,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;CACvC;AAED;;;;;;;;GAQG;AACH,qBAAa,SAAS,CAAC,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,CAC5C,YAAW,aAAa,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,KAAK,CAAC;IAG7C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;gBADN,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,EAC5B,KAAK,CAAC,EAAE,MAAM,YAAA;IAGjC,uEAAuE;IACvE,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,EAAE,GAAG,KAAK,EACzB,WAAW,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,KAAK,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAC5D,UAAU,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,KAAK,EAAE,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,GAC9D,WAAW,CAAC,EAAE,GAAG,EAAE,CAAC;IAIhB,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC;CAQlD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAC1D,KAAK,CAAC,EAAE,MAAM,GACb,SAAS,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAS5B"}
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"}
@@ -1,43 +1,7 @@
1
1
  /**
2
- * A lazy, auto-paging collection.
3
- *
4
- * - `await listing` resolves to the FIRST page, preserving the wire
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 class Paginator {
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
@@ -1 +1 @@
1
- {"version":3,"file":"pagination.js","sourceRoot":"","sources":["../src/pagination.ts"],"names":[],"mappings":"AAgBA;;;;;;;;GAQG;AACH,MAAM,OAAO,SAAS;IAID;IACA;IAFnB,YACmB,MAA4B,EAC5B,KAAc;QADd,WAAM,GAAN,MAAM,CAAsB;QAC5B,UAAK,GAAL,KAAK,CAAS;IAC9B,CAAC;IAEJ,uEAAuE;IACvE,IAAI,CACF,WAA4D,EAC5D,UAA+D;QAE/D,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC;QAC3B,IAAI,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,GAAG,CAAC;YACF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC7C,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,MAAM,IAAI,CAAC;YACvD,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC,QAAQ,MAAM,KAAK,SAAS,EAAE;IACjC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,KAA0D,EAC1D,KAAc;IAEd,OAAO,IAAI,SAAS,CAClB;QACE,KAAK;QACL,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO;QAC7B,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,SAAS;KACvC,EACD,KAAK,CACN,CAAC;AACJ,CAAC"}
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"}
@@ -1,8 +1,7 @@
1
- import type { SseEvent } from "@introspection-sdk/types";
2
1
  /**
3
- * Parse a `text/event-stream` Response into an async-iterable of SSE
4
- * events. Minimal parser: handles `event:`, `data:` (multi-line),
5
- * `id:`, `retry:`; ignores comments and unknown fields.
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 declare function parseSse(res: Response): AsyncIterable<SseEvent>;
6
+ export { parseSse } from "@introspection-sdk/http";
8
7
  //# sourceMappingURL=streaming.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD;;;;GAIG;AACH,wBAAuB,QAAQ,CAAC,GAAG,EAAE,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,CAiDtE"}
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
- * Parse a `text/event-stream` Response into an async-iterable of SSE
3
- * events. Minimal parser: handles `event:`, `data:` (multi-line),
4
- * `id:`, `retry:`; ignores comments and unknown fields.
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 async function* parseSse(res) {
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
@@ -1 +1 @@
1
- {"version":3,"file":"streaming.js","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"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"}
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.1",
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.218.0",
37
- "@opentelemetry/context-async-hooks": "^2.7.1",
38
- "@opentelemetry/core": "^2.7.1",
39
- "@opentelemetry/exporter-logs-otlp-proto": "^0.218.0",
40
- "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
41
- "@opentelemetry/resources": "^2.7.1",
42
- "@opentelemetry/sdk-logs": "^0.218.0",
43
- "@opentelemetry/sdk-trace-base": "^2.7.1",
44
- "@opentelemetry/sdk-trace-node": "^2.7.1",
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.1",
143
- "@introspection-sdk/introspection-pi": "0.5.1",
144
- "@introspection-sdk/introspection-proxy": "0.5.1"
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
- * Thin HTTP wrapper used by both the CP-bound IntrospectionClient and
43
- * each DP-bound Runner. Has no opinion on which one it is — the caller
44
- * picks the base URL and the token.
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
- private readonly fetchImpl: typeof fetch;
48
-
49
- constructor(private readonly cfg: ResolvedApiConfig) {
50
- this.fetchImpl = cfg.fetch ?? globalThis.fetch;
51
- if (!this.fetchImpl) {
52
- throw new Error(
53
- "global fetch is unavailable; pass `advanced.fetch` or run on Node 18+",
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
- * Adapts a wire pagination protocol for {@link Paginator}: how to fetch
5
- * a page at a given cursor, pull the items out of it, and find the
6
- * cursor for the next page (`undefined` once the list is exhausted).
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 interface PageSource<T, TPage> {
9
- /** Fetch the page at `cursor` (`undefined` = the first page). */
10
- fetch(cursor: string | undefined): Promise<TPage>;
11
- /** Items contained in `page`, in iteration order. */
12
- items(page: TPage): T[];
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
- * Parse a `text/event-stream` Response into an async-iterable of SSE
5
- * events. Minimal parser: handles `event:`, `data:` (multi-line),
6
- * `id:`, `retry:`; ignores comments and unknown fields.
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 async function* parseSse(res: Response): AsyncIterable<SseEvent> {
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";