@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.
Files changed (44) hide show
  1. package/README.md +27 -0
  2. package/dist/esm/__generated__/api/v1.d.ts +582 -11
  3. package/dist/esm/__generated__/api/v1.d.ts.map +1 -1
  4. package/dist/esm/authFetch.d.ts +30 -0
  5. package/dist/esm/authFetch.d.ts.map +1 -0
  6. package/dist/esm/authFetch.js +49 -0
  7. package/dist/esm/authFetch.js.map +1 -0
  8. package/dist/esm/errors.d.ts +1 -0
  9. package/dist/esm/errors.d.ts.map +1 -1
  10. package/dist/esm/errors.js +2 -0
  11. package/dist/esm/errors.js.map +1 -1
  12. package/dist/esm/index.d.ts +1 -0
  13. package/dist/esm/index.d.ts.map +1 -1
  14. package/dist/esm/index.js +1 -0
  15. package/dist/esm/index.js.map +1 -1
  16. package/dist/esm/spans/logSpans.d.ts.map +1 -1
  17. package/dist/esm/spans/logSpans.js +32 -9
  18. package/dist/esm/spans/logSpans.js.map +1 -1
  19. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  20. package/dist/src/__generated__/api/v1.d.ts +582 -11
  21. package/dist/src/__generated__/api/v1.d.ts.map +1 -1
  22. package/dist/src/authFetch.d.ts +30 -0
  23. package/dist/src/authFetch.d.ts.map +1 -0
  24. package/dist/src/authFetch.js +52 -0
  25. package/dist/src/authFetch.js.map +1 -0
  26. package/dist/src/errors.d.ts +1 -0
  27. package/dist/src/errors.d.ts.map +1 -1
  28. package/dist/src/errors.js +1 -0
  29. package/dist/src/errors.js.map +1 -1
  30. package/dist/src/index.d.ts +1 -0
  31. package/dist/src/index.d.ts.map +1 -1
  32. package/dist/src/index.js +1 -0
  33. package/dist/src/index.js.map +1 -1
  34. package/dist/src/spans/logSpans.d.ts.map +1 -1
  35. package/dist/src/spans/logSpans.js +32 -9
  36. package/dist/src/spans/logSpans.js.map +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/docs/overview.mdx +1 -0
  39. package/package.json +5 -4
  40. package/src/__generated__/api/v1.ts +582 -11
  41. package/src/authFetch.ts +87 -0
  42. package/src/errors.ts +2 -0
  43. package/src/index.ts +1 -0
  44. package/src/spans/logSpans.ts +27 -4
@@ -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
@@ -1,2 +1,3 @@
1
1
  export * from "./client";
2
+ export * from "./authFetch";
2
3
  export * from "./errors";
@@ -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
- const { data, error } = await client.POST(
307
- "/v1/projects/{project_identifier}/spans",
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);