dataiku-sdk 3.1.0 → 3.3.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 +253 -38
- package/dist/build-metadata.json +1 -1
- package/dist/packages/types/src/index.d.ts +102 -0
- package/dist/packages/types/src/index.js +76 -0
- package/dist/src/auth.js +1 -1
- package/dist/src/cli/coerce.js +11 -2
- package/dist/src/cli/commands/analysis.js +4 -3
- package/dist/src/cli/commands/api-service.js +5 -3
- package/dist/src/cli/commands/app.js +61 -18
- package/dist/src/cli/commands/bundle.js +3 -2
- package/dist/src/cli/commands/code-env.js +23 -16
- package/dist/src/cli/commands/connection.d.ts +9 -0
- package/dist/src/cli/commands/connection.js +267 -1
- package/dist/src/cli/commands/dashboard.js +7 -6
- package/dist/src/cli/commands/data-collection.d.ts +2 -0
- package/dist/src/cli/commands/data-collection.js +164 -0
- package/dist/src/cli/commands/data-quality.js +9 -8
- package/dist/src/cli/commands/dataset.js +170 -40
- package/dist/src/cli/commands/doctor.js +4 -2
- package/dist/src/cli/commands/flow-zone.js +9 -8
- package/dist/src/cli/commands/folder.js +9 -8
- package/dist/src/cli/commands/future.js +4 -3
- package/dist/src/cli/commands/group.d.ts +2 -0
- package/dist/src/cli/commands/group.js +109 -0
- package/dist/src/cli/commands/index.js +151 -78
- package/dist/src/cli/commands/insight.js +8 -7
- package/dist/src/cli/commands/job.js +4 -3
- package/dist/src/cli/commands/knowledge-bank.d.ts +2 -0
- package/dist/src/cli/commands/knowledge-bank.js +80 -0
- package/dist/src/cli/commands/llm.d.ts +2 -0
- package/dist/src/cli/commands/llm.js +93 -0
- package/dist/src/cli/commands/macro.d.ts +2 -0
- package/dist/src/cli/commands/macro.js +221 -0
- package/dist/src/cli/commands/meaning.js +5 -4
- package/dist/src/cli/commands/ml-task.js +21 -8
- package/dist/src/cli/commands/model-evaluation-store.js +4 -3
- package/dist/src/cli/commands/notebook.js +17 -12
- package/dist/src/cli/commands/plugin.d.ts +2 -0
- package/dist/src/cli/commands/plugin.js +799 -0
- package/dist/src/cli/commands/project-folder.d.ts +2 -0
- package/dist/src/cli/commands/project-folder.js +197 -0
- package/dist/src/cli/commands/project-git.js +14 -54
- package/dist/src/cli/commands/project-library.js +7 -6
- package/dist/src/cli/commands/project.js +55 -8
- package/dist/src/cli/commands/recipe.js +50 -14
- package/dist/src/cli/commands/saved-model.d.ts +5 -0
- package/dist/src/cli/commands/saved-model.js +392 -4
- package/dist/src/cli/commands/scenario.js +147 -9
- package/dist/src/cli/commands/sql.js +3 -2
- package/dist/src/cli/commands/statistics.js +7 -6
- package/dist/src/cli/commands/user.d.ts +8 -0
- package/dist/src/cli/commands/user.js +296 -0
- package/dist/src/cli/commands/variable.js +2 -1
- package/dist/src/cli/commands/webapp.js +2 -1
- package/dist/src/cli/commands/wiki.js +7 -6
- package/dist/src/cli/contract.d.ts +2 -3
- package/dist/src/cli/contract.js +1167 -163
- package/dist/src/cli/flags.d.ts +4 -0
- package/dist/src/cli/flags.js +50 -2
- package/dist/src/cli/helpers/app-successor.d.ts +19 -4
- package/dist/src/cli/helpers/app-successor.js +90 -30
- package/dist/src/cli/helpers/cleanup.js +3 -1
- package/dist/src/cli/helpers/flow-zone.js +1 -2
- package/dist/src/cli/output.d.ts +1 -1
- package/dist/src/cli/output.js +23 -10
- package/dist/src/cli/usage.d.ts +1 -0
- package/dist/src/cli/usage.js +47 -1
- package/dist/src/cli.js +225 -95
- package/dist/src/client.d.ts +86 -6
- package/dist/src/client.js +263 -50
- package/dist/src/errors.d.ts +3 -0
- package/dist/src/errors.js +4 -0
- package/dist/src/index.d.ts +16 -7
- package/dist/src/index.js +11 -2
- package/dist/src/resources/analyses.js +1 -8
- package/dist/src/resources/applications.d.ts +8 -6
- package/dist/src/resources/applications.js +21 -21
- package/dist/src/resources/base.d.ts +4 -0
- package/dist/src/resources/base.js +15 -0
- package/dist/src/resources/bundles.d.ts +9 -4
- package/dist/src/resources/bundles.js +10 -5
- package/dist/src/resources/connections.d.ts +78 -1
- package/dist/src/resources/connections.js +132 -1
- package/dist/src/resources/data-collections.d.ts +118 -0
- package/dist/src/resources/data-collections.js +49 -0
- package/dist/src/resources/datasets.d.ts +69 -0
- package/dist/src/resources/datasets.js +99 -1
- package/dist/src/resources/folders.js +2 -1
- package/dist/src/resources/futures.d.ts +4 -1
- package/dist/src/resources/futures.js +38 -6
- package/dist/src/resources/groups.d.ts +47 -0
- package/dist/src/resources/groups.js +56 -0
- package/dist/src/resources/jobs.d.ts +8 -13
- package/dist/src/resources/jobs.js +41 -16
- package/dist/src/resources/knowledge-banks.d.ts +58 -0
- package/dist/src/resources/knowledge-banks.js +61 -0
- package/dist/src/resources/llms.d.ts +92 -0
- package/dist/src/resources/llms.js +62 -0
- package/dist/src/resources/macros.d.ts +121 -0
- package/dist/src/resources/macros.js +231 -0
- package/dist/src/resources/ml-tasks.d.ts +37 -2
- package/dist/src/resources/ml-tasks.js +100 -20
- package/dist/src/resources/model-evaluation-stores.js +1 -8
- package/dist/src/resources/notebooks.d.ts +43 -7
- package/dist/src/resources/notebooks.js +53 -13
- package/dist/src/resources/plugins.d.ts +265 -0
- package/dist/src/resources/plugins.js +458 -0
- package/dist/src/resources/project-folders.d.ts +71 -0
- package/dist/src/resources/project-folders.js +57 -0
- package/dist/src/resources/project-git.d.ts +6 -0
- package/dist/src/resources/project-git.js +69 -42
- package/dist/src/resources/project-library.d.ts +0 -1
- package/dist/src/resources/project-library.js +1 -17
- package/dist/src/resources/projects.d.ts +19 -3
- package/dist/src/resources/projects.js +30 -18
- package/dist/src/resources/recipes.d.ts +15 -1
- package/dist/src/resources/recipes.js +73 -6
- package/dist/src/resources/saved-models.d.ts +134 -1
- package/dist/src/resources/saved-models.js +214 -6
- package/dist/src/resources/scenarios.d.ts +105 -0
- package/dist/src/resources/scenarios.js +232 -106
- package/dist/src/resources/sql.js +5 -1
- package/dist/src/resources/users.d.ts +129 -0
- package/dist/src/resources/users.js +128 -0
- package/dist/src/resources/webapps.d.ts +15 -2
- package/dist/src/resources/webapps.js +18 -4
- package/dist/src/schemas.d.ts +2 -2
- package/dist/src/schemas.js +1 -1
- package/dist/src/skill.d.ts +13 -4
- package/dist/src/skill.js +75 -24
- package/dist/src/utils/deep-merge.d.ts +1 -1
- package/dist/src/utils/polling.d.ts +20 -0
- package/dist/src/utils/polling.js +27 -0
- package/dist/src/utils/response-file.d.ts +2 -0
- package/dist/src/utils/response-file.js +34 -0
- package/dist/src/utils/secret-sanitize.d.ts +54 -0
- package/dist/src/utils/secret-sanitize.js +101 -0
- package/package.json +8 -5
- package/packages/types/dist/index.d.ts +102 -0
- package/packages/types/dist/index.js +76 -0
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +47 -225
- package/skills/dataiku-dss/references/app-releases.md +28 -0
- package/skills/dataiku-dss/references/authentication.md +39 -0
- package/skills/dataiku-dss/references/coding.md +8 -0
- package/skills/dataiku-dss/references/discovery.md +30 -0
- package/skills/dataiku-dss/references/flow-maps.md +12 -0
- package/skills/dataiku-dss/references/mutations.md +33 -0
- package/skills/dataiku-dss/references/troubleshooting.md +29 -0
package/dist/src/client.d.ts
CHANGED
|
@@ -8,19 +8,26 @@ import { CodeEnvsResource } from "./resources/code-envs.js";
|
|
|
8
8
|
import { ConnectionsResource } from "./resources/connections.js";
|
|
9
9
|
import { ContinuousActivitiesResource } from "./resources/continuous-activities.js";
|
|
10
10
|
import { DashboardsResource } from "./resources/dashboards.js";
|
|
11
|
+
import { DataCollectionsResource } from "./resources/data-collections.js";
|
|
11
12
|
import { DataQualityResource } from "./resources/data-quality.js";
|
|
12
13
|
import { DatasetsResource } from "./resources/datasets.js";
|
|
13
14
|
import { DiscussionsResource } from "./resources/discussions.js";
|
|
14
15
|
import { FlowZonesResource } from "./resources/flow-zones.js";
|
|
15
16
|
import { FoldersResource } from "./resources/folders.js";
|
|
16
17
|
import { FuturesResource } from "./resources/futures.js";
|
|
18
|
+
import { GroupsResource } from "./resources/groups.js";
|
|
17
19
|
import { InsightsResource } from "./resources/insights.js";
|
|
18
20
|
import { JobsResource } from "./resources/jobs.js";
|
|
21
|
+
import { KnowledgeBanksResource } from "./resources/knowledge-banks.js";
|
|
22
|
+
import { LlmsResource } from "./resources/llms.js";
|
|
23
|
+
import { MacrosResource } from "./resources/macros.js";
|
|
19
24
|
import { MeaningsResource } from "./resources/meanings.js";
|
|
20
25
|
import { MetricsResource } from "./resources/metrics.js";
|
|
21
26
|
import { MlTasksResource } from "./resources/ml-tasks.js";
|
|
22
27
|
import { ModelEvaluationStoresResource } from "./resources/model-evaluation-stores.js";
|
|
23
28
|
import { NotebooksResource } from "./resources/notebooks.js";
|
|
29
|
+
import { PluginsResource } from "./resources/plugins.js";
|
|
30
|
+
import { ProjectFoldersResource } from "./resources/project-folders.js";
|
|
24
31
|
import { ProjectGitResource } from "./resources/project-git.js";
|
|
25
32
|
import { ProjectLibraryResource } from "./resources/project-library.js";
|
|
26
33
|
import { ProjectsResource } from "./resources/projects.js";
|
|
@@ -30,10 +37,43 @@ import { ScenariosResource } from "./resources/scenarios.js";
|
|
|
30
37
|
import { SqlResource } from "./resources/sql.js";
|
|
31
38
|
import { StatisticsResource } from "./resources/statistics.js";
|
|
32
39
|
import { StreamingEndpointsResource } from "./resources/streaming-endpoints.js";
|
|
40
|
+
import { UsersResource } from "./resources/users.js";
|
|
33
41
|
import { VariablesResource } from "./resources/variables.js";
|
|
34
42
|
import { WebappsResource } from "./resources/webapps.js";
|
|
35
43
|
import { WikiResource } from "./resources/wiki.js";
|
|
36
44
|
import { WorkspacesResource } from "./resources/workspaces.js";
|
|
45
|
+
/** One part of a {@link DataikuClient.uploadForm} multipart/form-data body. */
|
|
46
|
+
export interface UploadFormPart {
|
|
47
|
+
/** Form field name. */
|
|
48
|
+
name: string;
|
|
49
|
+
/** Text field value. Mutually exclusive with `blob`/`fileName` file parts. */
|
|
50
|
+
value?: string;
|
|
51
|
+
/** File content. Omit (or null) together with `fileName` for a zero-byte file part. */
|
|
52
|
+
blob?: Blob | null;
|
|
53
|
+
/** Filename for file parts. */
|
|
54
|
+
fileName?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Multipart part modes for {@link DataikuClient.uploadForm}:
|
|
58
|
+
* - `value`: a form text field;
|
|
59
|
+
* - neither `blob` nor `fileName`: a filename-less, zero-length field part —
|
|
60
|
+
* the official Python client's files={"file": (None, None)} shape required
|
|
61
|
+
* by the managed-folder MLflow import (a Blob would add `filename=`, even
|
|
62
|
+
* when empty);
|
|
63
|
+
* - `blob` (optionally with `fileName`): a file part.
|
|
64
|
+
*/
|
|
65
|
+
/** Per-call overrides for {@link DataikuClient.get}. */
|
|
66
|
+
export interface DataikuGetOptions {
|
|
67
|
+
/**
|
|
68
|
+
* TOTAL duration budget in milliseconds for this call, measured from call
|
|
69
|
+
* start: it bounds every retry attempt's fetch, the backoff sleeps between
|
|
70
|
+
* attempts, and the response body read. When the budget is exhausted the
|
|
71
|
+
* request is aborted and a timeout DataikuError is thrown. The client-level
|
|
72
|
+
* requestTimeoutMs still caps each attempt and buffered body read; this
|
|
73
|
+
* budget may shorten those limits but never extends them.
|
|
74
|
+
*/
|
|
75
|
+
timeoutMs?: number;
|
|
76
|
+
}
|
|
37
77
|
export interface DataikuClientTraceEvent {
|
|
38
78
|
type: "trace";
|
|
39
79
|
phase: "request" | "response" | "error";
|
|
@@ -67,14 +107,14 @@ export interface DataikuClientConfig {
|
|
|
67
107
|
apiKey: string;
|
|
68
108
|
/** Default project key — used when a resource method omits projectKey */
|
|
69
109
|
projectKey?: string;
|
|
70
|
-
/**
|
|
110
|
+
/** Request/header timeout and buffered-body budget; idle timeout per raw stream read (default 30 000 ms). */
|
|
71
111
|
requestTimeoutMs?: number;
|
|
72
112
|
/** Max retry attempts for idempotent requests (default 4, capped at 10) */
|
|
73
113
|
retryMaxAttempts?: number;
|
|
74
114
|
/**
|
|
75
|
-
* Maximum bytes
|
|
76
|
-
*
|
|
77
|
-
*
|
|
115
|
+
* Maximum bytes retained from a single response body by text/JSON consumers
|
|
116
|
+
* (default 50 MiB). Oversized successful responses are rejected. Error
|
|
117
|
+
* responses retain a bounded prefix with DataikuError.bodyTruncated=true.
|
|
78
118
|
*/
|
|
79
119
|
maxResponseBodyBytes?: number;
|
|
80
120
|
/** Emit HTTP request/response trace events. Defaults to JSONL on stderr when verbose is true. */
|
|
@@ -139,6 +179,14 @@ export declare class DataikuClient {
|
|
|
139
179
|
private mlTasksResource?;
|
|
140
180
|
private savedModelsResource?;
|
|
141
181
|
private modelEvaluationStoresResource?;
|
|
182
|
+
private projectFoldersResource?;
|
|
183
|
+
private dataCollectionsResource?;
|
|
184
|
+
private llmsResource?;
|
|
185
|
+
private knowledgeBanksResource?;
|
|
186
|
+
private macrosResource?;
|
|
187
|
+
private pluginsResource?;
|
|
188
|
+
private usersResource?;
|
|
189
|
+
private groupsResource?;
|
|
142
190
|
get projects(): ProjectsResource;
|
|
143
191
|
get datasets(): DatasetsResource;
|
|
144
192
|
get dashboards(): DashboardsResource;
|
|
@@ -175,6 +223,14 @@ export declare class DataikuClient {
|
|
|
175
223
|
get mlTasks(): MlTasksResource;
|
|
176
224
|
get savedModels(): SavedModelsResource;
|
|
177
225
|
get modelEvaluationStores(): ModelEvaluationStoresResource;
|
|
226
|
+
get projectFolders(): ProjectFoldersResource;
|
|
227
|
+
get dataCollections(): DataCollectionsResource;
|
|
228
|
+
get llms(): LlmsResource;
|
|
229
|
+
get knowledgeBanks(): KnowledgeBanksResource;
|
|
230
|
+
get macros(): MacrosResource;
|
|
231
|
+
get plugins(): PluginsResource;
|
|
232
|
+
get users(): UsersResource;
|
|
233
|
+
get groups(): GroupsResource;
|
|
178
234
|
constructor(config?: DataikuClientConfig);
|
|
179
235
|
/**
|
|
180
236
|
* Canonical request base URL: the exact prefix every HTTP verb concatenates
|
|
@@ -189,7 +245,7 @@ export declare class DataikuClient {
|
|
|
189
245
|
*/
|
|
190
246
|
getMaxResponseBodyBytes(): number;
|
|
191
247
|
resolveProjectKey(paramValue?: string): string;
|
|
192
|
-
get<T = unknown>(path: string): Promise<T>;
|
|
248
|
+
get<T = unknown>(path: string, options?: DataikuGetOptions): Promise<T>;
|
|
193
249
|
/**
|
|
194
250
|
* GET returning the parsed JSON body plus selected response metadata
|
|
195
251
|
* (documented DSS version headers, server date, request id). Authorization
|
|
@@ -239,14 +295,38 @@ export declare class DataikuClient {
|
|
|
239
295
|
* {@link getText} (byte cap `maxResponseBodyBytes`, request deadline).
|
|
240
296
|
*/
|
|
241
297
|
postText(path: string, body?: unknown): Promise<string>;
|
|
298
|
+
/** Send raw file contents through the shared authenticated transport. */
|
|
299
|
+
postRawBody(path: string, body: string | Uint8Array): Promise<void>;
|
|
300
|
+
/** Git endpoints require Basic API-key authentication, unlike the bearer API. */
|
|
301
|
+
requestGit(method: string, path: string, body?: unknown, deadlineAt?: number): Promise<Response>;
|
|
242
302
|
postStream(path: string, body?: unknown): Promise<Response>;
|
|
243
303
|
put<T = unknown>(path: string, body: unknown): Promise<T>;
|
|
244
304
|
del(path: string): Promise<void>;
|
|
245
305
|
putVoid(path: string, body: unknown): Promise<void>;
|
|
306
|
+
/**
|
|
307
|
+
* PUT without a request body. A few DSS endpoints (bundle export) reject
|
|
308
|
+
* an empty JSON object body with 400 validation, and the official Python
|
|
309
|
+
* client sends no body at all for them.
|
|
310
|
+
*/
|
|
311
|
+
putVoidNoBody(path: string): Promise<void>;
|
|
312
|
+
/**
|
|
313
|
+
* POST a multipart/form-data body built from explicit parts, with optional
|
|
314
|
+
* query parameters appended to the path, returning the parsed JSON body.
|
|
315
|
+
*
|
|
316
|
+
* A part with `value` becomes a form text field; a part with `blob`
|
|
317
|
+
* (or a `fileName` with no blob) becomes a file part — a `fileName` without
|
|
318
|
+
* blob yields a zero-byte file part, which some DSS import endpoints
|
|
319
|
+
* (e.g. saved-model MLflow version import) require for metadata-only parts.
|
|
320
|
+
* The `Content-Type` header is left to the runtime so the multipart
|
|
321
|
+
* boundary is set correctly.
|
|
322
|
+
*/
|
|
323
|
+
uploadForm<T = unknown>(path: string, parts: UploadFormPart[], query?: URLSearchParams): Promise<T>;
|
|
246
324
|
private uploadResponse;
|
|
247
325
|
upload(path: string, filePath: string, fileName?: string): Promise<void>;
|
|
248
326
|
uploadJson<T>(path: string, filePath: string, fileName?: string): Promise<T>;
|
|
249
327
|
stream(path: string): Promise<Response>;
|
|
328
|
+
/** Bound each pending read, not total transfer time; preserve large downloads and backpressure. */
|
|
329
|
+
private withBodyDeadline;
|
|
250
330
|
private getHeaders;
|
|
251
331
|
private getAnyHeaders;
|
|
252
332
|
private responseMeta;
|
|
@@ -269,7 +349,7 @@ export declare class DataikuClient {
|
|
|
269
349
|
private requestIdFromHeaders;
|
|
270
350
|
private parseJsonResponse;
|
|
271
351
|
/**
|
|
272
|
-
* Read at most `maxBytes` of a
|
|
352
|
+
* Read at most `maxBytes` of a response body as UTF-8 text.
|
|
273
353
|
*
|
|
274
354
|
* The body is consumed with the request deadline still active (measured
|
|
275
355
|
* from the first read): a stalled body throws a DataikuError instead of
|
package/dist/src/client.js
CHANGED
|
@@ -13,19 +13,26 @@ import { CodeEnvsResource, } from "./resources/code-envs.js";
|
|
|
13
13
|
import { ConnectionsResource, } from "./resources/connections.js";
|
|
14
14
|
import { ContinuousActivitiesResource, } from "./resources/continuous-activities.js";
|
|
15
15
|
import { DashboardsResource, } from "./resources/dashboards.js";
|
|
16
|
+
import { DataCollectionsResource, } from "./resources/data-collections.js";
|
|
16
17
|
import { DataQualityResource, } from "./resources/data-quality.js";
|
|
17
18
|
import { DatasetsResource, } from "./resources/datasets.js";
|
|
18
19
|
import { DiscussionsResource, } from "./resources/discussions.js";
|
|
19
20
|
import { FlowZonesResource, } from "./resources/flow-zones.js";
|
|
20
21
|
import { FoldersResource, } from "./resources/folders.js";
|
|
21
22
|
import { FuturesResource, } from "./resources/futures.js";
|
|
23
|
+
import { GroupsResource, } from "./resources/groups.js";
|
|
22
24
|
import { InsightsResource, } from "./resources/insights.js";
|
|
23
25
|
import { JobsResource, } from "./resources/jobs.js";
|
|
26
|
+
import { KnowledgeBanksResource, } from "./resources/knowledge-banks.js";
|
|
27
|
+
import { LlmsResource, } from "./resources/llms.js";
|
|
28
|
+
import { MacrosResource, } from "./resources/macros.js";
|
|
24
29
|
import { MeaningsResource, } from "./resources/meanings.js";
|
|
25
30
|
import { MetricsResource, } from "./resources/metrics.js";
|
|
26
31
|
import { MlTasksResource, } from "./resources/ml-tasks.js";
|
|
27
32
|
import { ModelEvaluationStoresResource, } from "./resources/model-evaluation-stores.js";
|
|
28
33
|
import { NotebooksResource, } from "./resources/notebooks.js";
|
|
34
|
+
import { PluginsResource, } from "./resources/plugins.js";
|
|
35
|
+
import { ProjectFoldersResource, } from "./resources/project-folders.js";
|
|
29
36
|
import { ProjectGitResource, } from "./resources/project-git.js";
|
|
30
37
|
import { ProjectLibraryResource, } from "./resources/project-library.js";
|
|
31
38
|
import { ProjectsResource, } from "./resources/projects.js";
|
|
@@ -35,6 +42,7 @@ import { ScenariosResource, } from "./resources/scenarios.js";
|
|
|
35
42
|
import { SqlResource, } from "./resources/sql.js";
|
|
36
43
|
import { StatisticsResource, } from "./resources/statistics.js";
|
|
37
44
|
import { StreamingEndpointsResource, } from "./resources/streaming-endpoints.js";
|
|
45
|
+
import { UsersResource, } from "./resources/users.js";
|
|
38
46
|
import { VariablesResource, } from "./resources/variables.js";
|
|
39
47
|
import { WebappsResource, } from "./resources/webapps.js";
|
|
40
48
|
import { WikiResource, } from "./resources/wiki.js";
|
|
@@ -76,15 +84,6 @@ function isTransientError(status, body) {
|
|
|
76
84
|
function shouldRetryMethod(method) {
|
|
77
85
|
return method.toUpperCase() === "GET";
|
|
78
86
|
}
|
|
79
|
-
function concatBytes(chunks, total) {
|
|
80
|
-
const out = new Uint8Array(total);
|
|
81
|
-
let offset = 0;
|
|
82
|
-
for (const chunk of chunks) {
|
|
83
|
-
out.set(chunk, offset);
|
|
84
|
-
offset += chunk.byteLength;
|
|
85
|
-
}
|
|
86
|
-
return out;
|
|
87
|
-
}
|
|
88
87
|
/**
|
|
89
88
|
* Drop a trailing incomplete UTF-8 multibyte sequence so the bytes end on a
|
|
90
89
|
* character boundary. Guarantees the decoded string re-encodes to no more bytes
|
|
@@ -133,7 +132,7 @@ function buildBodyReadTimeoutError(timeoutMs) {
|
|
|
133
132
|
* Read the next chunk of a response body under a hard deadline.
|
|
134
133
|
* When `remainingMs` elapses, the stream is cancelled and the caller's
|
|
135
134
|
* promise rejects with a DataikuError, so a stalled body cannot hang the
|
|
136
|
-
*
|
|
135
|
+
* consumers after headers already arrived.
|
|
137
136
|
*/
|
|
138
137
|
async function readChunkWithDeadline(reader, remainingMs, timeoutMs) {
|
|
139
138
|
const { promise, resolve, reject, } = Promise.withResolvers();
|
|
@@ -182,6 +181,20 @@ function buildFetchTlsOptions(config) {
|
|
|
182
181
|
}
|
|
183
182
|
return tls;
|
|
184
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Resolve the total-budget request deadline timestamp for a per-call GET
|
|
186
|
+
* budget: `Date.now() + timeoutMs`. Undefined when no budget is supplied, in
|
|
187
|
+
* which case the client-level per-attempt timeout applies unchanged.
|
|
188
|
+
*/
|
|
189
|
+
function resolveGetDeadlineAt(options) {
|
|
190
|
+
const timeoutMs = options?.timeoutMs;
|
|
191
|
+
if (timeoutMs === undefined)
|
|
192
|
+
return undefined;
|
|
193
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1) {
|
|
194
|
+
throw new ClientValidationError("timeoutMs must be a positive integer.");
|
|
195
|
+
}
|
|
196
|
+
return Date.now() + timeoutMs;
|
|
197
|
+
}
|
|
185
198
|
/**
|
|
186
199
|
* True when the URL embeds userinfo (`https://user:password@host`). Embedded
|
|
187
200
|
* credentials are rejected up front so they can never reach the canonical base
|
|
@@ -249,6 +262,14 @@ export class DataikuClient {
|
|
|
249
262
|
mlTasksResource;
|
|
250
263
|
savedModelsResource;
|
|
251
264
|
modelEvaluationStoresResource;
|
|
265
|
+
projectFoldersResource;
|
|
266
|
+
dataCollectionsResource;
|
|
267
|
+
llmsResource;
|
|
268
|
+
knowledgeBanksResource;
|
|
269
|
+
macrosResource;
|
|
270
|
+
pluginsResource;
|
|
271
|
+
usersResource;
|
|
272
|
+
groupsResource;
|
|
252
273
|
get projects() {
|
|
253
274
|
return (this.projectsResource ??= new ProjectsResource(this));
|
|
254
275
|
}
|
|
@@ -357,6 +378,30 @@ export class DataikuClient {
|
|
|
357
378
|
get modelEvaluationStores() {
|
|
358
379
|
return (this.modelEvaluationStoresResource ??= new ModelEvaluationStoresResource(this));
|
|
359
380
|
}
|
|
381
|
+
get projectFolders() {
|
|
382
|
+
return (this.projectFoldersResource ??= new ProjectFoldersResource(this));
|
|
383
|
+
}
|
|
384
|
+
get dataCollections() {
|
|
385
|
+
return (this.dataCollectionsResource ??= new DataCollectionsResource(this));
|
|
386
|
+
}
|
|
387
|
+
get llms() {
|
|
388
|
+
return (this.llmsResource ??= new LlmsResource(this));
|
|
389
|
+
}
|
|
390
|
+
get knowledgeBanks() {
|
|
391
|
+
return (this.knowledgeBanksResource ??= new KnowledgeBanksResource(this));
|
|
392
|
+
}
|
|
393
|
+
get macros() {
|
|
394
|
+
return (this.macrosResource ??= new MacrosResource(this));
|
|
395
|
+
}
|
|
396
|
+
get plugins() {
|
|
397
|
+
return (this.pluginsResource ??= new PluginsResource(this));
|
|
398
|
+
}
|
|
399
|
+
get users() {
|
|
400
|
+
return (this.usersResource ??= new UsersResource(this));
|
|
401
|
+
}
|
|
402
|
+
get groups() {
|
|
403
|
+
return (this.groupsResource ??= new GroupsResource(this));
|
|
404
|
+
}
|
|
360
405
|
constructor(config) {
|
|
361
406
|
const envUrl = process.env["DATAIKU_URL"]?.trim();
|
|
362
407
|
const envApiKey = process.env["DATAIKU_API_KEY"]?.trim();
|
|
@@ -408,12 +453,10 @@ export class DataikuClient {
|
|
|
408
453
|
throw new Error("projectKey is required — pass it as a parameter or set projectKey in DataikuClientConfig");
|
|
409
454
|
}
|
|
410
455
|
/* ---- public: HTTP verbs ---- */
|
|
411
|
-
async get(path) {
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
});
|
|
416
|
-
return this.parseJsonResponse(res);
|
|
456
|
+
async get(path, options) {
|
|
457
|
+
const deadlineAt = resolveGetDeadlineAt(options);
|
|
458
|
+
const res = await this.fetchWithRetry(`${this.baseUrl}${path}`, { method: "GET", headers: this.getHeaders(), }, undefined, deadlineAt);
|
|
459
|
+
return this.parseJsonResponse(res, deadlineAt);
|
|
417
460
|
}
|
|
418
461
|
/**
|
|
419
462
|
* GET returning the parsed JSON body plus selected response metadata
|
|
@@ -505,12 +548,39 @@ export class DataikuClient {
|
|
|
505
548
|
throw this.buildResponseTooLargeError(res, this.maxResponseBodyBytes);
|
|
506
549
|
return text;
|
|
507
550
|
}
|
|
508
|
-
|
|
551
|
+
/** Send raw file contents through the shared authenticated transport. */
|
|
552
|
+
async postRawBody(path, body) {
|
|
553
|
+
const payload = typeof body === "string"
|
|
554
|
+
? body
|
|
555
|
+
: body.buffer instanceof ArrayBuffer
|
|
556
|
+
? new Uint8Array(body.buffer, body.byteOffset, body.byteLength)
|
|
557
|
+
: new Uint8Array(body);
|
|
558
|
+
const response = await this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
559
|
+
method: "POST",
|
|
560
|
+
headers: this.getAnyHeaders(),
|
|
561
|
+
body: payload,
|
|
562
|
+
});
|
|
563
|
+
await response.text();
|
|
564
|
+
}
|
|
565
|
+
/** Git endpoints require Basic API-key authentication, unlike the bearer API. */
|
|
566
|
+
async requestGit(method, path, body, deadlineAt) {
|
|
509
567
|
return this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
568
|
+
method,
|
|
569
|
+
headers: {
|
|
570
|
+
Authorization: `Basic ${Buffer.from(`${this.apiKey}:`, "utf8").toString("base64")}`,
|
|
571
|
+
Accept: "application/json",
|
|
572
|
+
"Content-Type": "application/json",
|
|
573
|
+
},
|
|
574
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
575
|
+
}, undefined, deadlineAt);
|
|
576
|
+
}
|
|
577
|
+
async postStream(path, body) {
|
|
578
|
+
const res = await this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
510
579
|
method: "POST",
|
|
511
580
|
headers: { ...this.getAnyHeaders(), "Content-Type": "application/json", },
|
|
512
581
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
513
582
|
});
|
|
583
|
+
return this.withBodyDeadline(res);
|
|
514
584
|
}
|
|
515
585
|
async put(path, body) {
|
|
516
586
|
const res = await this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
@@ -533,6 +603,54 @@ export class DataikuClient {
|
|
|
533
603
|
body: JSON.stringify(body),
|
|
534
604
|
});
|
|
535
605
|
}
|
|
606
|
+
/**
|
|
607
|
+
* PUT without a request body. A few DSS endpoints (bundle export) reject
|
|
608
|
+
* an empty JSON object body with 400 validation, and the official Python
|
|
609
|
+
* client sends no body at all for them.
|
|
610
|
+
*/
|
|
611
|
+
async putVoidNoBody(path) {
|
|
612
|
+
await this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
613
|
+
method: "PUT",
|
|
614
|
+
headers: this.getHeaders(),
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* POST a multipart/form-data body built from explicit parts, with optional
|
|
619
|
+
* query parameters appended to the path, returning the parsed JSON body.
|
|
620
|
+
*
|
|
621
|
+
* A part with `value` becomes a form text field; a part with `blob`
|
|
622
|
+
* (or a `fileName` with no blob) becomes a file part — a `fileName` without
|
|
623
|
+
* blob yields a zero-byte file part, which some DSS import endpoints
|
|
624
|
+
* (e.g. saved-model MLflow version import) require for metadata-only parts.
|
|
625
|
+
* The `Content-Type` header is left to the runtime so the multipart
|
|
626
|
+
* boundary is set correctly.
|
|
627
|
+
*/
|
|
628
|
+
async uploadForm(path, parts, query) {
|
|
629
|
+
const formData = new FormData();
|
|
630
|
+
for (const part of parts) {
|
|
631
|
+
if (part.value !== undefined) {
|
|
632
|
+
formData.append(part.name, part.value);
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
if ((part.blob ?? null) === null && (part.fileName ?? null) === null) {
|
|
636
|
+
// Filename-less, zero-length field part — the shape the official
|
|
637
|
+
// Python client produces with files={"file": (None, None)} and
|
|
638
|
+
// the one DSS's managed-folder MLflow import requires (a Blob
|
|
639
|
+
// would always add a `filename=` parameter, even when empty).
|
|
640
|
+
formData.append(part.name, "");
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
const blob = part.blob ?? new Blob([]);
|
|
644
|
+
formData.append(part.name, blob, part.fileName);
|
|
645
|
+
}
|
|
646
|
+
const suffix = query && [...query.keys(),].length > 0 ? `?${query.toString()}` : "";
|
|
647
|
+
const res = await this.fetchWithRetry(`${this.baseUrl}${path}${suffix}`, {
|
|
648
|
+
method: "POST",
|
|
649
|
+
headers: { Authorization: `Bearer ${this.apiKey}`, },
|
|
650
|
+
body: formData,
|
|
651
|
+
});
|
|
652
|
+
return this.parseJsonResponse(res);
|
|
653
|
+
}
|
|
536
654
|
async uploadResponse(path, filePath, fileName) {
|
|
537
655
|
const fileBlob = Bun.file(filePath);
|
|
538
656
|
const formData = new FormData();
|
|
@@ -551,10 +669,51 @@ export class DataikuClient {
|
|
|
551
669
|
return this.parseJsonResponse(response);
|
|
552
670
|
}
|
|
553
671
|
async stream(path) {
|
|
554
|
-
|
|
672
|
+
const res = await this.fetchWithRetry(`${this.baseUrl}${path}`, {
|
|
555
673
|
method: "GET",
|
|
556
674
|
headers: this.getAnyHeaders(),
|
|
557
675
|
});
|
|
676
|
+
return this.withBodyDeadline(res);
|
|
677
|
+
}
|
|
678
|
+
/** Bound each pending read, not total transfer time; preserve large downloads and backpressure. */
|
|
679
|
+
withBodyDeadline(res) {
|
|
680
|
+
if (!res.body)
|
|
681
|
+
return res;
|
|
682
|
+
const reader = res.body.getReader();
|
|
683
|
+
const timeoutMs = this.requestTimeoutMs;
|
|
684
|
+
const body = new ReadableStream({
|
|
685
|
+
async pull(controller) {
|
|
686
|
+
try {
|
|
687
|
+
const { done, value, } = await readChunkWithDeadline(reader, timeoutMs, timeoutMs);
|
|
688
|
+
if (done) {
|
|
689
|
+
controller.close();
|
|
690
|
+
reader.releaseLock();
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
controller.enqueue(value);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
catch (error) {
|
|
697
|
+
controller.error(error);
|
|
698
|
+
void reader.cancel(error).catch(() => { });
|
|
699
|
+
}
|
|
700
|
+
},
|
|
701
|
+
cancel(reason) {
|
|
702
|
+
void reader.cancel(reason).catch(() => { });
|
|
703
|
+
},
|
|
704
|
+
});
|
|
705
|
+
const response = new Response(body, {
|
|
706
|
+
status: res.status,
|
|
707
|
+
statusText: res.statusText,
|
|
708
|
+
headers: res.headers,
|
|
709
|
+
});
|
|
710
|
+
// ResponseInit has no fields for these fetch response properties.
|
|
711
|
+
Object.defineProperties(response, {
|
|
712
|
+
url: { value: res.url, },
|
|
713
|
+
redirected: { value: res.redirected, },
|
|
714
|
+
type: { value: res.type, },
|
|
715
|
+
});
|
|
716
|
+
return response;
|
|
558
717
|
}
|
|
559
718
|
/* ---- private: headers ---- */
|
|
560
719
|
getHeaders() {
|
|
@@ -625,8 +784,8 @@ export class DataikuClient {
|
|
|
625
784
|
}
|
|
626
785
|
return undefined;
|
|
627
786
|
}
|
|
628
|
-
async parseJsonResponse(res) {
|
|
629
|
-
const { text, truncated, } = await this.readBoundedBodyText(res, this.maxResponseBodyBytes);
|
|
787
|
+
async parseJsonResponse(res, deadlineAt) {
|
|
788
|
+
const { text, truncated, } = await this.readBoundedBodyText(res, this.maxResponseBodyBytes, deadlineAt);
|
|
630
789
|
if (truncated)
|
|
631
790
|
throw this.buildResponseTooLargeError(res, this.maxResponseBodyBytes);
|
|
632
791
|
// SAFETY: Empty 2xx responses from DSS are surfaced to callers as undefined
|
|
@@ -644,7 +803,7 @@ export class DataikuClient {
|
|
|
644
803
|
}
|
|
645
804
|
/* ---- private: bounded response body reading ---- */
|
|
646
805
|
/**
|
|
647
|
-
* Read at most `maxBytes` of a
|
|
806
|
+
* Read at most `maxBytes` of a response body as UTF-8 text.
|
|
648
807
|
*
|
|
649
808
|
* The body is consumed with the request deadline still active (measured
|
|
650
809
|
* from the first read): a stalled body throws a DataikuError instead of
|
|
@@ -653,39 +812,71 @@ export class DataikuClient {
|
|
|
653
812
|
* text is trimmed to a UTF-8 character boundary so it never decodes beyond
|
|
654
813
|
* the byte cap.
|
|
655
814
|
*/
|
|
656
|
-
async readBoundedBodyText(res, maxBytes) {
|
|
815
|
+
async readBoundedBodyText(res, maxBytes, deadlineAt) {
|
|
657
816
|
const limit = Math.max(0, Math.floor(maxBytes));
|
|
658
817
|
if (!res.body)
|
|
659
818
|
return { text: "", truncated: false, };
|
|
819
|
+
// Total-budget mode: the read shares the call's absolute deadline, so
|
|
820
|
+
// the budget already spent on attempts/backoff is not re-credited here.
|
|
821
|
+
const bodyTimeoutMs = deadlineAt === undefined
|
|
822
|
+
? this.requestTimeoutMs
|
|
823
|
+
: Math.min(this.requestTimeoutMs, Math.max(1, deadlineAt - Date.now()));
|
|
660
824
|
const reader = res.body.getReader();
|
|
661
|
-
const
|
|
825
|
+
const decoder = new TextDecoder();
|
|
826
|
+
const parts = [];
|
|
827
|
+
// Keep the final UTF-8 sequence undecoded until truncation is known.
|
|
828
|
+
// Four bytes also cover malformed sequences handled by trimToUtf8Boundary.
|
|
829
|
+
const tail = new Uint8Array(4);
|
|
830
|
+
let tailLength = 0;
|
|
831
|
+
const append = (chunk) => {
|
|
832
|
+
if (chunk.byteLength >= tail.length) {
|
|
833
|
+
if (tailLength > 0) {
|
|
834
|
+
parts.push(decoder.decode(tail.subarray(0, tailLength), { stream: true, }));
|
|
835
|
+
}
|
|
836
|
+
const end = chunk.byteLength - tail.length;
|
|
837
|
+
if (end > 0)
|
|
838
|
+
parts.push(decoder.decode(chunk.subarray(0, end), { stream: true, }));
|
|
839
|
+
tail.set(chunk.subarray(end));
|
|
840
|
+
tailLength = tail.length;
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
const excess = Math.max(0, tailLength + chunk.byteLength - tail.length);
|
|
844
|
+
if (excess > 0) {
|
|
845
|
+
parts.push(decoder.decode(tail.subarray(0, excess), { stream: true, }));
|
|
846
|
+
tail.copyWithin(0, excess, tailLength);
|
|
847
|
+
tailLength -= excess;
|
|
848
|
+
}
|
|
849
|
+
tail.set(chunk, tailLength);
|
|
850
|
+
tailLength += chunk.byteLength;
|
|
851
|
+
}
|
|
852
|
+
};
|
|
662
853
|
let bytesRead = 0;
|
|
663
854
|
let truncated = false;
|
|
664
855
|
const startedAt = Date.now();
|
|
665
856
|
try {
|
|
666
857
|
while (bytesRead < limit) {
|
|
667
|
-
const remainingMs =
|
|
858
|
+
const remainingMs = bodyTimeoutMs - (Date.now() - startedAt);
|
|
668
859
|
if (remainingMs <= 0)
|
|
669
|
-
throw buildBodyReadTimeoutError(
|
|
670
|
-
const { done, value, } = await readChunkWithDeadline(reader, remainingMs,
|
|
860
|
+
throw buildBodyReadTimeoutError(bodyTimeoutMs);
|
|
861
|
+
const { done, value, } = await readChunkWithDeadline(reader, remainingMs, bodyTimeoutMs);
|
|
671
862
|
if (done)
|
|
672
863
|
break;
|
|
673
864
|
const room = limit - bytesRead;
|
|
674
865
|
if (value.byteLength > room) {
|
|
675
|
-
|
|
866
|
+
append(value.subarray(0, room));
|
|
676
867
|
bytesRead += room;
|
|
677
868
|
truncated = true;
|
|
678
869
|
break;
|
|
679
870
|
}
|
|
680
|
-
|
|
871
|
+
append(value);
|
|
681
872
|
bytesRead += value.byteLength;
|
|
682
873
|
}
|
|
683
874
|
if (!truncated && bytesRead >= limit) {
|
|
684
875
|
// Buffer filled exactly on a chunk boundary; peek whether more data remains.
|
|
685
|
-
const remainingMs =
|
|
876
|
+
const remainingMs = bodyTimeoutMs - (Date.now() - startedAt);
|
|
686
877
|
if (remainingMs <= 0)
|
|
687
|
-
throw buildBodyReadTimeoutError(
|
|
688
|
-
const { done, } = await readChunkWithDeadline(reader, remainingMs,
|
|
878
|
+
throw buildBodyReadTimeoutError(bodyTimeoutMs);
|
|
879
|
+
const { done, } = await readChunkWithDeadline(reader, remainingMs, bodyTimeoutMs);
|
|
689
880
|
if (!done)
|
|
690
881
|
truncated = true;
|
|
691
882
|
}
|
|
@@ -693,12 +884,9 @@ export class DataikuClient {
|
|
|
693
884
|
finally {
|
|
694
885
|
void reader.cancel().catch(() => { });
|
|
695
886
|
}
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
// emit a 3-byte replacement char for a split multibyte sequence).
|
|
700
|
-
const usable = truncated ? trimToUtf8Boundary(collected) : collected;
|
|
701
|
-
return { text: new TextDecoder().decode(usable), truncated, };
|
|
887
|
+
const remaining = tail.subarray(0, tailLength);
|
|
888
|
+
parts.push(decoder.decode(truncated ? trimToUtf8Boundary(remaining) : remaining));
|
|
889
|
+
return { text: parts.join(""), truncated, };
|
|
702
890
|
}
|
|
703
891
|
/**
|
|
704
892
|
* Read a 2xx response body keeping only the last `maxBytes` bytes.
|
|
@@ -781,7 +969,7 @@ export class DataikuClient {
|
|
|
781
969
|
return new DataikuError(res.status, RESPONSE_TOO_LARGE_STATUS_TEXT, `Response body exceeded the ${limit}-byte limit; the body was cancelled after ${limit} bytes.`, undefined, this.requestIdFromHeaders(res.headers));
|
|
782
970
|
}
|
|
783
971
|
/* ---- private: retry loop ---- */
|
|
784
|
-
async fetchWithRetry(url, init, retryMaxAttempts) {
|
|
972
|
+
async fetchWithRetry(url, init, retryMaxAttempts, deadlineAt) {
|
|
785
973
|
const method = (init.method ?? "GET").toUpperCase();
|
|
786
974
|
const retryEnabled = shouldRetryMethod(method) || retryMaxAttempts !== undefined;
|
|
787
975
|
const maxAttempts = retryMaxAttempts === undefined
|
|
@@ -793,11 +981,21 @@ export class DataikuClient {
|
|
|
793
981
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
794
982
|
let timedOut = false;
|
|
795
983
|
const startedAt = Date.now();
|
|
984
|
+
// An overall deadline may shorten, but never extend, the attempt cap.
|
|
985
|
+
const remainingMs = deadlineAt === undefined
|
|
986
|
+
? undefined
|
|
987
|
+
: deadlineAt - startedAt;
|
|
988
|
+
if (remainingMs !== undefined && remainingMs <= 0) {
|
|
989
|
+
throw new DataikuError(0, "Request Timeout", `Request deadline exceeded after ${attempt - 1} attempt(s) and ${delaysMs.length} backoff delay(s).`, buildRetryMetadata(method, retryEnabled, maxAttempts, attempt, delaysMs, true));
|
|
990
|
+
}
|
|
991
|
+
const attemptTimeoutMs = remainingMs === undefined
|
|
992
|
+
? this.requestTimeoutMs
|
|
993
|
+
: Math.min(remainingMs, this.requestTimeoutMs);
|
|
796
994
|
const controller = new AbortController();
|
|
797
995
|
const timeout = setTimeout(() => {
|
|
798
996
|
timedOut = true;
|
|
799
997
|
controller.abort();
|
|
800
|
-
},
|
|
998
|
+
}, attemptTimeoutMs);
|
|
801
999
|
this.logTrace({ phase: "request", method, url, attempt, maxAttempts, });
|
|
802
1000
|
try {
|
|
803
1001
|
const requestInit = {
|
|
@@ -818,30 +1016,45 @@ export class DataikuClient {
|
|
|
818
1016
|
elapsedMs: Date.now() - startedAt,
|
|
819
1017
|
});
|
|
820
1018
|
if (!res.ok) {
|
|
821
|
-
const text = await res.
|
|
1019
|
+
const { text, truncated, } = await this.readBoundedBodyText(res, this.maxResponseBodyBytes, startedAt + attemptTimeoutMs);
|
|
822
1020
|
const canRetry = retryEnabled && attempt < maxAttempts && isTransientError(res.status, text);
|
|
823
1021
|
if (canRetry) {
|
|
824
1022
|
const delayMs = computeBackoffDelayMs(attempt);
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1023
|
+
// Do not start a retry whose backoff cannot fit in the budget.
|
|
1024
|
+
if (deadlineAt === undefined || Date.now() + delayMs <= deadlineAt) {
|
|
1025
|
+
delaysMs.push(delayMs);
|
|
1026
|
+
await sleep(delayMs);
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
await sleep(Math.max(0, deadlineAt - Date.now()));
|
|
1030
|
+
throw new DataikuError(0, "Request Timeout", `Request deadline exceeded before backoff retry ${attempt + 1}.`, buildRetryMetadata(method, retryEnabled, maxAttempts, attempt, delaysMs, true));
|
|
828
1031
|
}
|
|
829
|
-
throw new DataikuError(res.status, res.statusText, text, buildRetryMetadata(method, retryEnabled, maxAttempts, attempt, delaysMs, false), this.requestIdFromHeaders(res.headers));
|
|
1032
|
+
throw new DataikuError(res.status, res.statusText, text, buildRetryMetadata(method, retryEnabled, maxAttempts, attempt, delaysMs, false), this.requestIdFromHeaders(res.headers), truncated ? { bodyTruncated: true, } : undefined);
|
|
830
1033
|
}
|
|
831
1034
|
return res;
|
|
832
1035
|
}
|
|
833
1036
|
catch (error) {
|
|
834
|
-
if (error instanceof DataikuError)
|
|
835
|
-
|
|
1037
|
+
if (error instanceof DataikuError) {
|
|
1038
|
+
// A bounded error-body read is still part of this transport attempt.
|
|
1039
|
+
// Preserve the same retry/deadline policy as a fetch body abort.
|
|
1040
|
+
if (error.status !== 0 || error.statusText !== "Request Timeout" || error.retry !== undefined) {
|
|
1041
|
+
throw error;
|
|
1042
|
+
}
|
|
1043
|
+
timedOut = true;
|
|
1044
|
+
}
|
|
836
1045
|
const canRetry = retryEnabled && attempt < maxAttempts;
|
|
837
1046
|
if (canRetry) {
|
|
838
1047
|
const delayMs = computeBackoffDelayMs(attempt);
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
1048
|
+
if (deadlineAt === undefined || Date.now() + delayMs <= deadlineAt) {
|
|
1049
|
+
delaysMs.push(delayMs);
|
|
1050
|
+
await sleep(delayMs);
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
await sleep(Math.max(0, deadlineAt - Date.now()));
|
|
1054
|
+
throw new DataikuError(0, "Request Timeout", `Request deadline exceeded before backoff retry ${attempt + 1}.`, buildRetryMetadata(method, retryEnabled, maxAttempts, attempt, delaysMs, true));
|
|
842
1055
|
}
|
|
843
1056
|
const detail = timedOut
|
|
844
|
-
? `Request timed out after ${
|
|
1057
|
+
? `Request timed out after ${attemptTimeoutMs}ms`
|
|
845
1058
|
: error instanceof Error
|
|
846
1059
|
? error.message
|
|
847
1060
|
: "Unknown transport error";
|