contree-client 0.2.1-dev0 → 0.2.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/lib/client.d.ts CHANGED
@@ -34,13 +34,13 @@ export interface ContreeClientOptions {
34
34
  }
35
35
 
36
36
  export declare class ContreeClient {
37
- token: string;
37
+ token: string | null;
38
38
  baseUrl: string;
39
39
  project: string | null;
40
40
  timeout: number | null;
41
41
  retry: RetryPolicy | null;
42
42
  identity: string | null;
43
- constructor(token: string, options?: ContreeClientOptions);
43
+ constructor(token: string | null, options?: ContreeClientOptions);
44
44
  static fromProfile(
45
45
  profile?: string | Profile | null,
46
46
  options?: ContreeClientOptions & { configPath?: string | null },
@@ -215,10 +215,10 @@ export declare class ContreeClient {
215
215
  inspectImageList(imageUuid: string, path: string): Promise<DirectoryList>;
216
216
  inspectImageGrep(
217
217
  imageUuid: string,
218
- pattern: string,
218
+ pattern: string | readonly string[],
219
219
  options?: {
220
- path?: string | null;
221
- glob?: string | null;
220
+ path?: string | readonly string[] | null;
221
+ glob?: string | readonly string[] | null;
222
222
  max_count?: number | null;
223
223
  max_total?: number | null;
224
224
  case?: "sensitive" | "insensitive" | "smart" | null;
package/lib/client.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  } from "./errors.js";
9
9
  import { AUTH_TYPE_IAM, ProfileError, resolveProfile } from "./profiles.js";
10
10
  import {
11
+ EVENTS_UNAVAILABLE_STATUSES,
11
12
  IS_NODE,
12
13
  SSEParser,
13
14
  TIGHT_LOOP_FLOOR,
@@ -25,7 +26,11 @@ import {
25
26
  sleep,
26
27
  } from "./runtime.js";
27
28
  import { DEFAULT_BASE_URL } from "./specInfo.js";
28
- import { OperationEvent, isTerminalStatus } from "./models.js";
29
+ import {
30
+ EventDataCompletion,
31
+ OperationEvent,
32
+ isTerminalStatus,
33
+ } from "./models.js";
29
34
  import * as operations from "./operations.js";
30
35
 
31
36
  /** Contree API client on top of the platform fetch.
@@ -34,6 +39,10 @@ import * as operations from "./operations.js";
34
39
  * unlike the Python adapters there is exactly one transport. Pass a
35
40
  * custom `fetch` implementation (undici dispatcher wrappers, MSW,
36
41
  * ...) via the constructor options to customize it.
42
+ *
43
+ * `token` may be null, like `project`: the client then sends no
44
+ * `Authorization` header, which is all the endpoints that need no
45
+ * authentication want.
37
46
  */
38
47
  export class ContreeClient {
39
48
  constructor(
@@ -54,7 +63,7 @@ export class ContreeClient {
54
63
  `unsupported baseUrl scheme ${parsed.protocol} in ${baseUrl}: use http:// or https://`,
55
64
  );
56
65
  }
57
- this.token = token;
66
+ this.token = token ?? null;
58
67
  this.baseUrl = baseUrl.replace(/\/+$/, "");
59
68
  this.project = project;
60
69
  this.timeout = timeout;
@@ -107,8 +116,11 @@ export class ContreeClient {
107
116
  }
108
117
 
109
118
  buildHeaders(spec) {
110
- const headers = { Authorization: `Bearer ${this.token}` };
111
- if (this.project !== null) {
119
+ const headers = {};
120
+ if (this.token) {
121
+ headers["Authorization"] = `Bearer ${this.token}`;
122
+ }
123
+ if (this.project) {
112
124
  headers["Project"] = this.project;
113
125
  }
114
126
  if (spec.contentType) {
@@ -409,8 +421,13 @@ export class ContreeClient {
409
421
 
410
422
  /** Stream operation events with transparent reconnection: network
411
423
  * drops, in-band SSE error frames and retryable API statuses
412
- * (410/425/5xx) reconnect from the last received event id; other
413
- * API errors propagate. Ends after the `completion` event. */
424
+ * (410/425/5xx) reconnect from the last received event id. A status
425
+ * meaning the events endpoint itself doesn't exist for this
426
+ * operation/server (400/404/405/406) stops reconnecting and instead
427
+ * polls getOperationStatus() until the operation is terminal, then
428
+ * yields a synthesized `completion` event built from that status
429
+ * (there is no real event log to relay). Other API errors
430
+ * propagate. Ends after the `completion` event. */
414
431
  async *followOperationEvents(
415
432
  operationId,
416
433
  { last_event_id = null, spid = null, since = null, timeout = null } = {},
@@ -450,6 +467,64 @@ export class ContreeClient {
450
467
  lastId = error.lastEventId;
451
468
  }
452
469
  } else if (error instanceof ContreeAPIError) {
470
+ if (EVENTS_UNAVAILABLE_STATUSES.has(error.status)) {
471
+ // the endpoint is gone, not just failing: reconnecting the
472
+ // same request will never work, but the operation may
473
+ // still finish - stop touching /events and poll status
474
+ // instead for the rest of this wait
475
+ for (;;) {
476
+ checkDeadline();
477
+ let response;
478
+ try {
479
+ response = await this.getOperationStatus(operationId);
480
+ } catch (pollError) {
481
+ if (
482
+ !(pollError instanceof ContreeError) &&
483
+ !this._transportRetryable(pollError)
484
+ ) {
485
+ throw pollError;
486
+ }
487
+ response = null;
488
+ }
489
+ if (
490
+ response !== null &&
491
+ response.status !== undefined &&
492
+ isTerminalStatus(response.status)
493
+ ) {
494
+ // no event log to relay, but a caller of
495
+ // followOperationEvents must still observe a terminal
496
+ // completion rather than nothing at all
497
+ lastId = lastId === null ? 0 : lastId + 1;
498
+ yield new OperationEvent({
499
+ id: lastId,
500
+ ts: new Date(),
501
+ type: "completion",
502
+ data: new EventDataCompletion({
503
+ status: response.status,
504
+ duration_ms:
505
+ typeof response.duration === "number"
506
+ ? Math.round(response.duration * 1000)
507
+ : 0,
508
+ result_image_uuid: response.result_image_uuid,
509
+ error: response.error,
510
+ image_size_bytes:
511
+ typeof response.image_size === "number"
512
+ ? response.image_size
513
+ : undefined,
514
+ }),
515
+ });
516
+ return;
517
+ }
518
+ let pollDelay = delays.next().value;
519
+ if (deadline !== null) {
520
+ pollDelay = Math.min(
521
+ pollDelay,
522
+ Math.max(0, deadline - monotonic()),
523
+ );
524
+ }
525
+ await sleep(pollDelay);
526
+ }
527
+ }
453
528
  const retryable =
454
529
  error.status === 410 ||
455
530
  error.status === 425 ||
package/lib/models.js CHANGED
@@ -585,7 +585,7 @@ export class StreamRepr {
585
585
  }
586
586
  }
587
587
 
588
- /** Stdin payload. Unlike output streams it is never truncated; */
588
+ /** Stdin payload. Delivery to the child is always complete */
589
589
  export class ClosableStreamRepr {
590
590
  constructor(fields = {}) {
591
591
  this.value = fields.value;
@@ -246,10 +246,10 @@ export declare function parseInspectImageList(
246
246
 
247
247
  export declare function buildInspectImageGrep(
248
248
  imageUuid: string,
249
- pattern: string,
249
+ pattern: string | readonly string[],
250
250
  options?: {
251
- path?: string | null;
252
- glob?: string | null;
251
+ path?: string | readonly string[] | null;
252
+ glob?: string | readonly string[] | null;
253
253
  max_count?: number | null;
254
254
  max_total?: number | null;
255
255
  case?: "sensitive" | "insensitive" | "smart" | null;
package/lib/runtime.d.ts CHANGED
@@ -21,7 +21,7 @@ export type RequestBody =
21
21
  export interface RequestSpec {
22
22
  method: string;
23
23
  path: string;
24
- query?: Record<string, string>;
24
+ query?: Record<string, string | readonly string[]>;
25
25
  headers?: Record<string, string>;
26
26
  body?: RequestBody;
27
27
  contentType?: string | null;
@@ -58,7 +58,9 @@ export declare function sleep(seconds: number): Promise<void>;
58
58
  export declare function monotonic(): number;
59
59
  export declare function isUuid(ref: string): boolean;
60
60
  export declare function quotePath(value: unknown): string;
61
- export declare function encodeQuery(query: Record<string, string>): string;
61
+ export declare function encodeQuery(
62
+ query: Record<string, string | readonly string[]>,
63
+ ): string;
62
64
  export declare function formatTimeParam(value: string | number | Date): string;
63
65
  export declare function parseDatetime(value: string): Date;
64
66
  export declare function bytesToText(bytes: Uint8Array): string;
package/lib/runtime.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
 
15
15
  export const CHUNK_SIZE = 65536;
16
16
 
17
- export const PACKAGE_VERSION = "0.2.1-dev0";
17
+ export const PACKAGE_VERSION = "0.2.2";
18
18
  export const UA_PRODUCT = `contree-client-js/${PACKAGE_VERSION}`;
19
19
 
20
20
  const NODE_VERSION =
@@ -38,6 +38,17 @@ export const RETRY_DELAYS = Object.freeze([0.1, 0.2, 0.5, 1.0, 2.0, 5.0]);
38
38
  // returning immediate empty streams does not spin the client
39
39
  export const TIGHT_LOOP_FLOOR = 0.5;
40
40
 
41
+ // the events route itself doesn't exist for this operation/server
42
+ // (malformed request an older backend rejects, or a reverse proxy
43
+ // that never forwards it) rather than merely being down: reconnecting
44
+ // will never succeed, but the operation itself may still complete -
45
+ // degrade to polling instead of failing the whole wait. 401/403 are
46
+ // deliberately excluded: an auth/permission failure here likely means
47
+ // the whole client is broken, not just this route, so it still throws
48
+ export const EVENTS_UNAVAILABLE_STATUSES = Object.freeze(
49
+ new Set([400, 404, 405, 406]),
50
+ );
51
+
41
52
  /** An endless ladder of backoff delays: the ladder is walked once and
42
53
  * then the tail delay repeats forever. */
43
54
  export function* retryDelays(delays = RETRY_DELAYS) {
@@ -140,9 +151,11 @@ export function quotePath(value) {
140
151
 
141
152
  export function encodeQuery(query) {
142
153
  return Object.entries(query)
143
- .map(
144
- ([key, value]) =>
145
- `${encodeURIComponent(key)}=${encodeURIComponent(value).replaceAll("%2F", "/")}`,
154
+ .flatMap(([key, value]) =>
155
+ (Array.isArray(value) ? value : [value]).map(
156
+ (item) =>
157
+ `${encodeURIComponent(key)}=${encodeURIComponent(item).replaceAll("%2F", "/")}`,
158
+ ),
146
159
  )
147
160
  .join("&");
148
161
  }
package/lib/specInfo.js CHANGED
@@ -5,4 +5,4 @@ export const DEFAULT_BASE_URL = "https://api.tokenfactory.nebius.com/sandboxes";
5
5
  // sha256 of the exact OpenAPI document this package was built
6
6
  // from - the build input provenance
7
7
  export const SPEC_SHA256 =
8
- "2bfd239341e6f61ccba1aafb39cef7b476d56f424156860a204f089d7d79b7bf";
8
+ "23d748a2918b1124b3072d54d17d8eb43aa00d0b969bc1fa718f34ac884ee887";
package/lib/testing.d.ts CHANGED
@@ -17,7 +17,7 @@ export declare class ContreeClient extends GeneratedClient {
17
17
  mocks: Map<string, Outcome[]>;
18
18
  calls: Call[];
19
19
  constructedWith: {
20
- token: string;
20
+ token: string | null;
21
21
  baseUrl: string;
22
22
  project: string | null;
23
23
  timeout: number | null;
@@ -25,7 +25,7 @@ export declare class ContreeClient extends GeneratedClient {
25
25
  identity: string | null;
26
26
  };
27
27
  constructor(
28
- token?: string,
28
+ token?: string | null,
29
29
  options?: ConstructorParameters<typeof GeneratedClient>[1],
30
30
  );
31
31
  mock(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contree-client",
3
- "version": "0.2.1-dev0",
3
+ "version": "0.2.2",
4
4
  "description": "JavaScript client for the Contree API, generated from the OpenAPI spec",
5
5
  "homepage": "https://contree.dev/",
6
6
  "repository": {