@arizeai/phoenix-client 6.13.0 → 6.14.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/README.md +27 -0
- package/dist/esm/__generated__/api/v1.d.ts +582 -11
- package/dist/esm/__generated__/api/v1.d.ts.map +1 -1
- package/dist/esm/authFetch.d.ts +30 -0
- package/dist/esm/authFetch.d.ts.map +1 -0
- package/dist/esm/authFetch.js +49 -0
- package/dist/esm/authFetch.js.map +1 -0
- package/dist/esm/errors.d.ts +1 -0
- package/dist/esm/errors.d.ts.map +1 -1
- package/dist/esm/errors.js +2 -0
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/spans/logSpans.d.ts.map +1 -1
- package/dist/esm/spans/logSpans.js +32 -9
- package/dist/esm/spans/logSpans.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/src/__generated__/api/v1.d.ts +582 -11
- package/dist/src/__generated__/api/v1.d.ts.map +1 -1
- package/dist/src/authFetch.d.ts +30 -0
- package/dist/src/authFetch.d.ts.map +1 -0
- package/dist/src/authFetch.js +52 -0
- package/dist/src/authFetch.js.map +1 -0
- package/dist/src/errors.d.ts +1 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +1 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/spans/logSpans.d.ts.map +1 -1
- package/dist/src/spans/logSpans.js +32 -9
- package/dist/src/spans/logSpans.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/overview.mdx +1 -0
- package/package.json +5 -4
- package/src/__generated__/api/v1.ts +582 -11
- package/src/authFetch.ts +87 -0
- package/src/errors.ts +2 -0
- package/src/index.ts +1 -0
- package/src/spans/logSpans.ts +27 -4
package/src/authFetch.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export interface AuthFetchOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Return an access token for a request.
|
|
4
|
+
*
|
|
5
|
+
* When `forceRefresh` is true, the previous token was rejected with a 401
|
|
6
|
+
* and the provider should refresh it before returning. Providers own token
|
|
7
|
+
* storage and refresh-token rotation.
|
|
8
|
+
*/
|
|
9
|
+
getAccessToken: (options: {
|
|
10
|
+
forceRefresh: boolean;
|
|
11
|
+
}) => string | Promise<string>;
|
|
12
|
+
/** Fetch implementation used to send requests. Defaults to global fetch. */
|
|
13
|
+
fetch?: typeof fetch;
|
|
14
|
+
/** Called when the request is still unauthorized after one refresh. */
|
|
15
|
+
onUnauthorized?: (response: Response) => void | Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create a fetch implementation that supplies a bearer token and refreshes it
|
|
20
|
+
* after a 401 response.
|
|
21
|
+
*
|
|
22
|
+
* Refresh calls are coalesced so concurrent unauthorized requests rotate a
|
|
23
|
+
* refresh token only once. Each request is retried at most once.
|
|
24
|
+
*
|
|
25
|
+
* @param options - Authentication behavior for the fetch implementation.
|
|
26
|
+
* @param options.getAccessToken - Returns the current token or refreshes it.
|
|
27
|
+
* @param options.fetch - Fetch implementation used to send requests.
|
|
28
|
+
* @param options.onUnauthorized - Handles a second unauthorized response.
|
|
29
|
+
*/
|
|
30
|
+
export function createAuthFetch({
|
|
31
|
+
getAccessToken,
|
|
32
|
+
fetch: fetchImpl = fetch,
|
|
33
|
+
onUnauthorized,
|
|
34
|
+
}: AuthFetchOptions): typeof fetch {
|
|
35
|
+
let refreshPromise: Promise<string> | undefined;
|
|
36
|
+
|
|
37
|
+
const refreshAccessToken = (): Promise<string> => {
|
|
38
|
+
if (!refreshPromise) {
|
|
39
|
+
refreshPromise = Promise.resolve(
|
|
40
|
+
getAccessToken({ forceRefresh: true })
|
|
41
|
+
).finally(() => {
|
|
42
|
+
refreshPromise = undefined;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return refreshPromise;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return async function authFetch(
|
|
49
|
+
input: Parameters<typeof fetch>[0],
|
|
50
|
+
init?: RequestInit
|
|
51
|
+
) {
|
|
52
|
+
const request = new Request(input, init);
|
|
53
|
+
const retryRequest = request.clone();
|
|
54
|
+
const accessToken = await getAccessToken({ forceRefresh: false });
|
|
55
|
+
const response = await fetchImpl(addBearerToken({ request, accessToken }));
|
|
56
|
+
if (response.status !== 401) {
|
|
57
|
+
return response;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Another request may already have refreshed and persisted the token while
|
|
61
|
+
// this request was in flight. Reuse it instead of rotating again.
|
|
62
|
+
const currentAccessToken = await getAccessToken({ forceRefresh: false });
|
|
63
|
+
const retryAccessToken =
|
|
64
|
+
currentAccessToken === accessToken
|
|
65
|
+
? await refreshAccessToken()
|
|
66
|
+
: currentAccessToken;
|
|
67
|
+
const retryResponse = await fetchImpl(
|
|
68
|
+
addBearerToken({ request: retryRequest, accessToken: retryAccessToken })
|
|
69
|
+
);
|
|
70
|
+
if (retryResponse.status === 401) {
|
|
71
|
+
await onUnauthorized?.(retryResponse);
|
|
72
|
+
}
|
|
73
|
+
return retryResponse;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function addBearerToken({
|
|
78
|
+
request,
|
|
79
|
+
accessToken,
|
|
80
|
+
}: {
|
|
81
|
+
request: Request;
|
|
82
|
+
accessToken: string;
|
|
83
|
+
}): Request {
|
|
84
|
+
const headers = new Headers(request.headers);
|
|
85
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
86
|
+
return new Request(request, { headers });
|
|
87
|
+
}
|
package/src/errors.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* for instance, is how a caller learns that auth is enabled on the deployment.
|
|
7
7
|
*/
|
|
8
8
|
export class HttpError extends Error {
|
|
9
|
+
readonly response: Response;
|
|
9
10
|
readonly status: number;
|
|
10
11
|
readonly statusText: string;
|
|
11
12
|
readonly url: string;
|
|
@@ -13,6 +14,7 @@ export class HttpError extends Error {
|
|
|
13
14
|
constructor(response: Response) {
|
|
14
15
|
super(`${response.url}: ${response.status} ${response.statusText}`);
|
|
15
16
|
this.name = "HttpError";
|
|
17
|
+
this.response = response;
|
|
16
18
|
this.status = response.status;
|
|
17
19
|
this.statusText = response.statusText;
|
|
18
20
|
this.url = response.url;
|
package/src/index.ts
CHANGED
package/src/spans/logSpans.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { paths } from "../__generated__/api/v1";
|
|
2
2
|
import { createClient } from "../client";
|
|
3
|
+
import { HttpError } from "../errors";
|
|
3
4
|
import type { ClientFn } from "../types/core";
|
|
4
5
|
import type { ProjectIdentifier } from "../types/projects";
|
|
5
6
|
import { resolveProjectIdentifier } from "../types/projects";
|
|
@@ -264,6 +265,21 @@ function parseLogSpansError(error: unknown, spans: Span[]): Error {
|
|
|
264
265
|
return new Error(`Failed to log spans: ${formatApiError(error)}`);
|
|
265
266
|
}
|
|
266
267
|
|
|
268
|
+
async function parseLogSpansHttpError(
|
|
269
|
+
error: HttpError,
|
|
270
|
+
spans: Span[]
|
|
271
|
+
): Promise<Error> {
|
|
272
|
+
try {
|
|
273
|
+
const contentType = error.response.headers.get("content-type");
|
|
274
|
+
const payload: unknown = contentType?.includes("application/json")
|
|
275
|
+
? await error.response.json()
|
|
276
|
+
: await error.response.text();
|
|
277
|
+
return parseLogSpansError(payload, spans);
|
|
278
|
+
} catch {
|
|
279
|
+
return parseLogSpansError(error, spans);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
267
283
|
/**
|
|
268
284
|
* Log spans to a project using Phoenix's simplified span structure.
|
|
269
285
|
*
|
|
@@ -303,9 +319,9 @@ export async function logSpans({
|
|
|
303
319
|
const client = _client ?? createClient();
|
|
304
320
|
const projectIdentifier = resolveProjectIdentifier(project);
|
|
305
321
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
{
|
|
322
|
+
let response: Awaited<ReturnType<typeof client.POST>>;
|
|
323
|
+
try {
|
|
324
|
+
response = await client.POST("/v1/projects/{project_identifier}/spans", {
|
|
309
325
|
params: {
|
|
310
326
|
path: {
|
|
311
327
|
project_identifier: projectIdentifier,
|
|
@@ -314,8 +330,15 @@ export async function logSpans({
|
|
|
314
330
|
body: {
|
|
315
331
|
data: spans,
|
|
316
332
|
},
|
|
333
|
+
});
|
|
334
|
+
} catch (error) {
|
|
335
|
+
if (error instanceof HttpError) {
|
|
336
|
+
throw await parseLogSpansHttpError(error, spans);
|
|
317
337
|
}
|
|
318
|
-
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const { data, error } = response;
|
|
319
342
|
|
|
320
343
|
if (error) {
|
|
321
344
|
throw parseLogSpansError(error, spans);
|