dataiku-sdk 3.4.0 → 3.5.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 +3 -3
- package/dist/build-metadata.json +1 -1
- package/dist/src/cli/command-syntax.json +14524 -0
- package/dist/src/cli/commands/analysis.js +6 -9
- package/dist/src/cli/commands/api-deployer.js +15 -33
- package/dist/src/cli/commands/api-service.js +17 -27
- package/dist/src/cli/commands/app.js +22 -39
- package/dist/src/cli/commands/bundle.js +15 -24
- package/dist/src/cli/commands/business-app.js +10 -17
- package/dist/src/cli/commands/code-env.js +23 -36
- package/dist/src/cli/commands/code.js +4 -4
- package/dist/src/cli/commands/connection.js +12 -22
- package/dist/src/cli/commands/continuous-activity.js +6 -9
- package/dist/src/cli/commands/dashboard.js +8 -13
- package/dist/src/cli/commands/data-collection.js +9 -16
- package/dist/src/cli/commands/data-quality.js +14 -26
- package/dist/src/cli/commands/dataset.js +27 -50
- package/dist/src/cli/commands/discussion.js +7 -10
- package/dist/src/cli/commands/doctor.js +3 -3
- package/dist/src/cli/commands/flow-zone.js +8 -17
- package/dist/src/cli/commands/folder.js +12 -20
- package/dist/src/cli/commands/future.js +7 -10
- package/dist/src/cli/commands/group.js +7 -11
- package/dist/src/cli/commands/insight.js +7 -11
- package/dist/src/cli/commands/job.js +13 -23
- package/dist/src/cli/commands/knowledge-bank.js +5 -6
- package/dist/src/cli/commands/llm.js +5 -8
- package/dist/src/cli/commands/macro.js +9 -16
- package/dist/src/cli/commands/meaning.js +7 -11
- package/dist/src/cli/commands/metrics.js +7 -10
- package/dist/src/cli/commands/ml-task.js +12 -20
- package/dist/src/cli/commands/model-evaluation-store.js +7 -11
- package/dist/src/cli/commands/notebook.js +15 -27
- package/dist/src/cli/commands/plugin.js +35 -68
- package/dist/src/cli/commands/project-deployer.js +10 -22
- package/dist/src/cli/commands/project-folder.js +10 -17
- package/dist/src/cli/commands/project-git.js +38 -70
- package/dist/src/cli/commands/project-library.js +15 -23
- package/dist/src/cli/commands/project.js +9 -26
- package/dist/src/cli/commands/recipe.d.ts +16 -0
- package/dist/src/cli/commands/recipe.js +83 -94
- package/dist/src/cli/commands/saved-model.js +23 -39
- package/dist/src/cli/commands/scenario.js +20 -34
- package/dist/src/cli/commands/sql.js +4 -4
- package/dist/src/cli/commands/statistics.js +11 -18
- package/dist/src/cli/commands/streaming-endpoint.js +7 -11
- package/dist/src/cli/commands/user.js +11 -22
- package/dist/src/cli/commands/variable.js +3 -4
- package/dist/src/cli/commands/webapp.js +10 -16
- package/dist/src/cli/commands/wiki.js +9 -13
- package/dist/src/cli/commands/workspace.js +8 -14
- package/dist/src/cli/contract.d.ts +5 -15
- package/dist/src/cli/contract.js +156 -253
- package/dist/src/cli/doctor.js +1 -0
- package/dist/src/cli/env.d.ts +2 -0
- package/dist/src/cli/env.js +15 -1
- package/dist/src/cli/helpers/app-permissions.js +2 -1
- package/dist/src/cli/helpers/flow-zone.js +2 -1
- package/dist/src/cli/helpers/recipe.js +2 -1
- package/dist/src/cli/syntax.d.ts +83 -0
- package/dist/src/cli/syntax.js +217 -0
- package/dist/src/cli/types.d.ts +3 -0
- package/dist/src/cli/usage.d.ts +1 -1
- package/dist/src/cli/usage.js +4 -2
- package/dist/src/cli/version.js +2 -0
- package/dist/src/cli.js +37 -64
- package/dist/src/client.js +7 -6
- package/dist/src/errors.d.ts +12 -1
- package/dist/src/errors.js +28 -3
- package/dist/src/index.d.ts +1 -3
- package/dist/src/index.js +2 -2
- package/dist/src/resources/api-services.js +2 -3
- package/dist/src/resources/applications.js +2 -4
- package/dist/src/resources/code-envs.js +2 -2
- package/dist/src/resources/data-quality.js +4 -3
- package/dist/src/resources/datasets.d.ts +13 -0
- package/dist/src/resources/datasets.js +5 -4
- package/dist/src/resources/flow-zones.js +4 -2
- package/dist/src/resources/folders.js +4 -2
- package/dist/src/resources/futures.js +1 -3
- package/dist/src/resources/insights.js +3 -2
- package/dist/src/resources/jobs.js +4 -6
- package/dist/src/resources/macros.js +2 -4
- package/dist/src/resources/notebooks.js +2 -3
- package/dist/src/resources/project-git.js +9 -11
- package/dist/src/resources/project-library.d.ts +0 -2
- package/dist/src/resources/project-library.js +2 -7
- package/dist/src/resources/projects.js +4 -3
- package/dist/src/resources/recipes.d.ts +28 -0
- package/dist/src/resources/recipes.js +116 -84
- package/dist/src/resources/sql.js +8 -3
- package/dist/src/resources/statistics.js +2 -1
- package/dist/src/resources/variables.js +2 -1
- package/dist/src/resources/webapps.js +2 -3
- package/dist/src/schemas.d.ts +1 -2
- package/dist/src/schemas.js +1 -1
- package/dist/src/skill.js +6 -7
- package/dist/src/utils/cleanup-ledger.js +9 -8
- package/dist/src/utils/polling.d.ts +3 -0
- package/dist/src/utils/polling.js +3 -0
- package/dist/src/utils/project-archive.js +2 -2
- package/dist/src/utils/stable-hash.d.ts +3 -1
- package/dist/src/utils/stable-hash.js +2 -0
- package/package.json +4 -4
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +2 -3
- package/skills/dataiku-dss/references/authentication.md +3 -3
- package/skills/dataiku-dss/references/coding.md +1 -1
- package/dist/src/utils/pagination.d.ts +0 -8
- package/dist/src/utils/pagination.js +0 -23
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ClientValidationError, unexpectedResponseError, } from "../errors.js";
|
|
1
2
|
import { InsightDetailsSchema, InsightSummaryArraySchema, } from "../schemas.js";
|
|
2
3
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
3
4
|
import { BaseResource, } from "./base.js";
|
|
@@ -33,7 +34,7 @@ export class InsightsResource extends BaseResource {
|
|
|
33
34
|
const pk = this.resolveProjectKey(opts.projectKey);
|
|
34
35
|
const prototype = applyInsightFields(opts.data ?? {}, opts);
|
|
35
36
|
if (!hasCreateShape(prototype)) {
|
|
36
|
-
throw new
|
|
37
|
+
throw new ClientValidationError("Insight create requires name and type, either as options or in data.", "validation_failed");
|
|
37
38
|
}
|
|
38
39
|
if (prototype.projectKey === undefined)
|
|
39
40
|
prototype.projectKey = pk;
|
|
@@ -43,7 +44,7 @@ export class InsightsResource extends BaseResource {
|
|
|
43
44
|
...(opts.payload !== undefined ? { payload: opts.payload, } : {}),
|
|
44
45
|
});
|
|
45
46
|
if (typeof created.id !== "string" || created.id.length === 0) {
|
|
46
|
-
throw
|
|
47
|
+
throw unexpectedResponseError("Insight create response did not include an id.");
|
|
47
48
|
}
|
|
48
49
|
return this.get(created.id, pk);
|
|
49
50
|
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { DataikuError, } from "../errors.js";
|
|
1
|
+
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
2
2
|
import { JobSummaryArraySchema, } from "../schemas.js";
|
|
3
|
-
import { computeNextPollDelayMs, isRequestDeadlineError, } from "../utils/polling.js";
|
|
3
|
+
import { computeNextPollDelayMs, DEFAULT_POLL_INTERVAL_MS, DEFAULT_TIMEOUT_MS, isRequestDeadlineError, } from "../utils/polling.js";
|
|
4
4
|
import { BaseResource, } from "./base.js";
|
|
5
|
-
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
6
|
-
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
7
5
|
const DEFAULT_MAX_LOG_LINES = 500;
|
|
8
6
|
/**
|
|
9
7
|
* Default bytes retained from a job log body (default 10 MiB). Logs larger
|
|
@@ -51,7 +49,7 @@ function jobBuildOutput(target, defaultProjectKey, defaultPartition, defaultTarg
|
|
|
51
49
|
}
|
|
52
50
|
function jobBuildDefinition(targets, defaultProjectKey, opts) {
|
|
53
51
|
if (targets.length === 0) {
|
|
54
|
-
throw new
|
|
52
|
+
throw new ClientValidationError("At least one build target is required.", "validation_failed");
|
|
55
53
|
}
|
|
56
54
|
const payload = {
|
|
57
55
|
outputs: targets.map((target) => jobBuildOutput(target, defaultProjectKey, opts?.partition, opts?.targetType)),
|
|
@@ -203,7 +201,7 @@ export class JobsResource extends BaseResource {
|
|
|
203
201
|
const jobId = parsed.searchParams.get("jobId") ?? undefined;
|
|
204
202
|
const activity = parsed.searchParams.get("activityId") ?? undefined;
|
|
205
203
|
if (!projectKey || !jobId || !activity) {
|
|
206
|
-
throw new
|
|
204
|
+
throw new ClientValidationError("Log URL must include projectKey, jobId, and activityId query parameters.", "validation_failed");
|
|
207
205
|
}
|
|
208
206
|
return this.log(jobId, {
|
|
209
207
|
activity,
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
2
2
|
import { MacroDefinitionSchema, MacroRunStartSchema, MacroStateSchema, MacroSummaryArraySchema, MacroWaitResultSchema, } from "../schemas.js";
|
|
3
|
-
import { computeNextPollDelayMs, isRequestDeadlineError, } from "../utils/polling.js";
|
|
3
|
+
import { computeNextPollDelayMs, DEFAULT_POLL_INTERVAL_MS, DEFAULT_TIMEOUT_MS, isRequestDeadlineError, } from "../utils/polling.js";
|
|
4
4
|
import { BaseResource, requireNonEmpty, } from "./base.js";
|
|
5
|
-
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
6
|
-
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
7
5
|
/** Hard floor for the result download cap; keeps a caller typo from hanging the read. */
|
|
8
6
|
const MIN_RESULT_MAX_BYTES = 1;
|
|
9
7
|
const MAX_RESULT_MAX_BYTES = 2 * 1024 * 1024 * 1024;
|
|
@@ -121,7 +119,7 @@ export class MacrosResource extends BaseResource {
|
|
|
121
119
|
const maxBytes = macroResultMaxBytes(opts?.maxBytes);
|
|
122
120
|
const raw = await this.client.getTextLimited(`${this.base(runnableType, opts?.projectKey)}/result/${encodeURIComponent(runId)}`, maxBytes);
|
|
123
121
|
if (raw.truncated) {
|
|
124
|
-
throw new
|
|
122
|
+
throw new ClientValidationError(`Macro result exceeded ${String(maxBytes)} bytes; retry with a larger maxBytes.`, "validation_failed");
|
|
125
123
|
}
|
|
126
124
|
const text = raw.text;
|
|
127
125
|
const trimmed = text.trim();
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
2
2
|
import { JupyterNotebookContentSchema, JupyterNotebookSummaryArraySchema, NotebookSessionArraySchema, SqlNotebookContentSchema, SqlNotebookHistorySchema, SqlNotebookSummaryArraySchema, } from "../schemas.js";
|
|
3
|
-
import { stableHash, } from "../utils/stable-hash.js";
|
|
3
|
+
import { SHA256_HEX_PATTERN, stableHash, } from "../utils/stable-hash.js";
|
|
4
4
|
import { BaseResource, } from "./base.js";
|
|
5
|
-
const EXPECT_HASH_PATTERN = /^[0-9a-fA-F]{64}$/;
|
|
6
5
|
export class NotebooksResource extends BaseResource {
|
|
7
6
|
// ── Jupyter Notebooks ──────────────────────────────────────────────
|
|
8
7
|
/**
|
|
@@ -260,7 +259,7 @@ export class NotebooksResource extends BaseResource {
|
|
|
260
259
|
}
|
|
261
260
|
/** Validate the armed `--expect-hash` value into a comparable hex digest. */
|
|
262
261
|
validatedExpectHash(expectHash, pk, resourceLabel, id) {
|
|
263
|
-
if (!
|
|
262
|
+
if (!SHA256_HEX_PATTERN.test(expectHash)) {
|
|
264
263
|
throw new ClientValidationError(`Expected ${resourceLabel} hash must be a 64-character SHA-256 hex digest.`, "validation_failed", "Use the hash value returned by a previous save or read.", { projectKey: pk, id, });
|
|
265
264
|
}
|
|
266
265
|
return expectHash.toLowerCase();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ClientValidationError, } from "../errors.js";
|
|
1
|
+
import { ClientValidationError, DataikuError, nonJsonResponseBody, } from "../errors.js";
|
|
2
2
|
import { ProjectGitActionResultSchema, ProjectGitDiffResultSchema, ProjectGitFutureResponseSchema, ProjectGitFutureStateSchema, ProjectGitLibrariesSchema, ProjectGitLogResultSchema, ProjectGitRemoteSchema, ProjectGitStatusSchema, ProjectGitTagsSchema, } from "../schemas.js";
|
|
3
3
|
import { encodeGitReferencePath, validateGitReferencePath, } from "../utils/git-reference.js";
|
|
4
|
-
import { computeNextPollDelayMs, isRequestDeadlineError, } from "../utils/polling.js";
|
|
4
|
+
import { computeNextPollDelayMs, DEFAULT_POLL_INTERVAL_MS, DEFAULT_TIMEOUT_MS, isRequestDeadlineError, } from "../utils/polling.js";
|
|
5
5
|
import { sanitizeErrorSecrets, } from "../utils/secret-sanitize.js";
|
|
6
6
|
import { BaseResource, } from "./base.js";
|
|
7
7
|
/**
|
|
@@ -13,8 +13,6 @@ const GIT_API_BASE = "/dip/publicapi";
|
|
|
13
13
|
const DEFAULT_REMOTE = "origin";
|
|
14
14
|
const DEFAULT_LOG_COUNT = 1_000;
|
|
15
15
|
const DEFAULT_TAG_REFERENCE = "HEAD";
|
|
16
|
-
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
17
|
-
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
18
16
|
function sleep(ms) {
|
|
19
17
|
const { promise, resolve, } = Promise.withResolvers();
|
|
20
18
|
setTimeout(resolve, ms);
|
|
@@ -362,7 +360,7 @@ export class ProjectGitResource extends BaseResource {
|
|
|
362
360
|
// the loop reports the documented timeout from the last observed
|
|
363
361
|
// state instead of issuing a request the budget cannot cover.
|
|
364
362
|
if (lastState !== undefined && elapsedBeforeMs >= timeoutMs) {
|
|
365
|
-
throw new
|
|
363
|
+
throw new ClientValidationError(`Timed out after ${String(elapsedBeforeMs)}ms waiting for Dataiku future ${jobId}`, "long_running_failure");
|
|
366
364
|
}
|
|
367
365
|
// Unlike peek state, the ordinary state includes the completed result.
|
|
368
366
|
// Requests issued while budget remains are bounded by the remaining
|
|
@@ -386,23 +384,23 @@ export class ProjectGitResource extends BaseResource {
|
|
|
386
384
|
// error escape.
|
|
387
385
|
if (!isRequestDeadlineError(error, startedAt + timeoutMs))
|
|
388
386
|
throw error;
|
|
389
|
-
throw new
|
|
387
|
+
throw new ClientValidationError(`Timed out after ${String(Date.now() - startedAt)}ms waiting for Dataiku future ${jobId}`, "long_running_failure", undefined, undefined, { cause: error, });
|
|
390
388
|
}
|
|
391
389
|
lastState = state;
|
|
392
390
|
const failure = describeFutureFailure(state.error);
|
|
393
391
|
if (failure !== undefined) {
|
|
394
|
-
throw new
|
|
392
|
+
throw new ClientValidationError(`Dataiku future ${jobId} failed: ${failure}`, "long_running_failure");
|
|
395
393
|
}
|
|
396
394
|
if (state.aborted === true) {
|
|
397
|
-
throw new
|
|
395
|
+
throw new ClientValidationError(`Dataiku future ${jobId} was aborted`, "long_running_failure");
|
|
398
396
|
}
|
|
399
397
|
if (state.hasResult === true)
|
|
400
398
|
return state.result;
|
|
401
399
|
if (state.unknown === true) {
|
|
402
|
-
throw new
|
|
400
|
+
throw new ClientValidationError(`Dataiku future ${jobId} is unknown to the server`, "long_running_failure");
|
|
403
401
|
}
|
|
404
402
|
if (state.alive === false) {
|
|
405
|
-
throw new
|
|
403
|
+
throw new ClientValidationError(`Dataiku future ${jobId} ended without producing a result`, "long_running_failure");
|
|
406
404
|
}
|
|
407
405
|
const elapsedMs = Date.now() - startedAt;
|
|
408
406
|
// Deadline reached: the guard at the top of the loop reports the
|
|
@@ -449,7 +447,7 @@ export class ProjectGitResource extends BaseResource {
|
|
|
449
447
|
return JSON.parse(text);
|
|
450
448
|
}
|
|
451
449
|
catch {
|
|
452
|
-
throw new
|
|
450
|
+
throw new DataikuError(res.status, "Invalid JSON response", nonJsonResponseBody(text));
|
|
453
451
|
}
|
|
454
452
|
}
|
|
455
453
|
catch (error) {
|
|
@@ -75,8 +75,6 @@ export interface ProjectLibraryDiffResult {
|
|
|
75
75
|
* checked; it can never turn the subsequent POST into a conditional write.
|
|
76
76
|
*/
|
|
77
77
|
export declare const PROJECT_LIBRARY_CONCURRENCY_CONTROL = "client-side-non-atomic-stale-read-check";
|
|
78
|
-
/** Lowercase or uppercase SHA-256 hex digest. */
|
|
79
|
-
export declare const EXPECT_SHA256_PATTERN: RegExp;
|
|
80
78
|
/**
|
|
81
79
|
* Validate and canonicalize a project library path.
|
|
82
80
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createHash, } from "node:crypto";
|
|
2
1
|
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
2
|
+
import { SHA256_HEX_PATTERN, sha256Hex, } from "../utils/stable-hash.js";
|
|
3
3
|
import { BaseResource, } from "./base.js";
|
|
4
4
|
/**
|
|
5
5
|
* The only concurrency control the public project-library API permits. DSS
|
|
@@ -8,11 +8,6 @@ import { BaseResource, } from "./base.js";
|
|
|
8
8
|
* checked; it can never turn the subsequent POST into a conditional write.
|
|
9
9
|
*/
|
|
10
10
|
export const PROJECT_LIBRARY_CONCURRENCY_CONTROL = "client-side-non-atomic-stale-read-check";
|
|
11
|
-
/** Lowercase or uppercase SHA-256 hex digest. */
|
|
12
|
-
export const EXPECT_SHA256_PATTERN = /^[0-9a-fA-F]{64}$/;
|
|
13
|
-
function sha256Hex(value) {
|
|
14
|
-
return createHash("sha256").update(value).digest("hex");
|
|
15
|
-
}
|
|
16
11
|
const CONTROL_CHARACTER_RE = /[\u0000-\u001f\u007f]/;
|
|
17
12
|
const BACKSLASH_RE = /\\/;
|
|
18
13
|
const TRAILING_AMBIGUOUS_RE = /[\s.]$/;
|
|
@@ -376,7 +371,7 @@ export class ProjectLibraryResource extends BaseResource {
|
|
|
376
371
|
}
|
|
377
372
|
const expectSha256 = options?.expectSha256;
|
|
378
373
|
if (expectSha256 !== undefined
|
|
379
|
-
&& (typeof expectSha256 !== "string" || !
|
|
374
|
+
&& (typeof expectSha256 !== "string" || !SHA256_HEX_PATTERN.test(expectSha256))) {
|
|
380
375
|
throw new ClientValidationError("expectSha256 must be a 64-character SHA-256 hex digest.", "validation_failed", "Use the sha256 value returned by a project library read or diff.", { path: valid, });
|
|
381
376
|
}
|
|
382
377
|
let beforeSha256;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
1
|
+
import { ClientValidationError, DataikuError, unexpectedResponseError, } from "../errors.js";
|
|
2
2
|
import { isRequestDeadlineError, } from "../utils/polling.js";
|
|
3
3
|
import { writeResponseToFile, } from "../utils/response-file.js";
|
|
4
4
|
import { ProjectDetailsSchema, ProjectMetadataSchema, ProjectSummaryArraySchema, ProjectTagsSchema, } from "../schemas.js";
|
|
@@ -140,8 +140,9 @@ export class ProjectsResource extends BaseResource {
|
|
|
140
140
|
/** Export a project archive to a local file. */
|
|
141
141
|
async exportArchive(projectKey, filePath, options) {
|
|
142
142
|
const res = await this.client.postStream(`/public/api/projects/${encodeURIComponent(projectKey)}/export`, options ?? {});
|
|
143
|
-
if (!res.body)
|
|
144
|
-
throw
|
|
143
|
+
if (!res.body) {
|
|
144
|
+
throw unexpectedResponseError("projects.exportArchive response did not include a body");
|
|
145
|
+
}
|
|
145
146
|
await writeResponseToFile(filePath, res);
|
|
146
147
|
}
|
|
147
148
|
/** Upload a project archive and return its temporary import handle. */
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { BuildMode, JobWaitResult, ProjectMetadata, RecipeCreateOptions, RecipeCreateResult, RecipeSummary } from "../schemas.js";
|
|
2
2
|
import { BaseResource } from "./base.js";
|
|
3
3
|
import type { JobBuildTarget, JobBuildTargetType, JobLogFilter, JobLogSummary } from "./jobs.js";
|
|
4
|
+
type JoinType = "LEFT" | "INNER" | "RIGHT" | "FULL";
|
|
5
|
+
type JoinKeyPair = {
|
|
6
|
+
left: string;
|
|
7
|
+
right: string;
|
|
8
|
+
};
|
|
4
9
|
export interface RecipeRunOutput extends JobBuildTarget {
|
|
5
10
|
ref: string;
|
|
6
11
|
role: string;
|
|
@@ -65,6 +70,28 @@ export interface RecipeCloneResult {
|
|
|
65
70
|
payloadTextRewrites: Record<string, string>;
|
|
66
71
|
copiedOutputDatasets: string[];
|
|
67
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Pure request construction for POST /recipes/ (no DSS calls). Shared with
|
|
75
|
+
* `recipe create --plan` so the plan equals the request the command sends.
|
|
76
|
+
*/
|
|
77
|
+
export declare function buildRecipeCreateRequest(opts: RecipeCreateOptions, pk: string): {
|
|
78
|
+
creationSettings: Record<string, unknown>;
|
|
79
|
+
fuzzyDistance: string | undefined;
|
|
80
|
+
fuzzyKeys: JoinKeyPair[] | undefined;
|
|
81
|
+
fuzzyThreshold: number;
|
|
82
|
+
inputDatasets: string[] | undefined;
|
|
83
|
+
inputs: Record<string, unknown>;
|
|
84
|
+
joinKeys: JoinKeyPair[] | undefined;
|
|
85
|
+
name: string;
|
|
86
|
+
normalizedJoinType: JoinType | undefined;
|
|
87
|
+
outputFolder: string | undefined;
|
|
88
|
+
outputs: Record<string, unknown>;
|
|
89
|
+
payload: string | undefined;
|
|
90
|
+
rawConnection: string | undefined;
|
|
91
|
+
recipePrototype: Record<string, unknown>;
|
|
92
|
+
temporaryOutputDataset: string | undefined;
|
|
93
|
+
type: string;
|
|
94
|
+
};
|
|
68
95
|
export declare class RecipesResource extends BaseResource {
|
|
69
96
|
/** List all recipes in a project. */
|
|
70
97
|
list(projectKey?: string): Promise<RecipeSummary[]>;
|
|
@@ -144,3 +171,4 @@ export declare class RecipesResource extends BaseResource {
|
|
|
144
171
|
projectKey?: string;
|
|
145
172
|
}): Promise<string>;
|
|
146
173
|
}
|
|
174
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { writeFile, } from "node:fs/promises";
|
|
2
2
|
import { resolve, } from "node:path";
|
|
3
|
-
import { DataikuError, } from "../errors.js";
|
|
3
|
+
import { ClientValidationError, DataikuError, nonJsonResponseBody, unexpectedResponseError, } from "../errors.js";
|
|
4
4
|
import { ProjectMetadataSchema, RecipeSummaryArraySchema, } from "../schemas.js";
|
|
5
5
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
6
6
|
import { sanitizeFileName, } from "../utils/sanitize.js";
|
|
@@ -25,7 +25,13 @@ function asRecord(value) {
|
|
|
25
25
|
function parseRecipePayload(payload) {
|
|
26
26
|
if (!payload)
|
|
27
27
|
return {};
|
|
28
|
-
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = JSON.parse(payload);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new DataikuError(200, "Invalid JSON response", nonJsonResponseBody(payload));
|
|
34
|
+
}
|
|
29
35
|
return asRecord(parsed) ?? {};
|
|
30
36
|
}
|
|
31
37
|
function parseJoinKeyPairs(values, optionName = "joinOn") {
|
|
@@ -41,8 +47,9 @@ function parseJoinKeyPairs(values, optionName = "joinOn") {
|
|
|
41
47
|
}
|
|
42
48
|
const left = token.slice(0, eq).trim();
|
|
43
49
|
const right = token.slice(eq + 1).trim();
|
|
44
|
-
if (!left || !right)
|
|
45
|
-
throw new
|
|
50
|
+
if (!left || !right) {
|
|
51
|
+
throw new ClientValidationError(`${optionName} values must use COL or LEFT=RIGHT.`, "validation_failed");
|
|
52
|
+
}
|
|
46
53
|
pairs.push({ left, right, });
|
|
47
54
|
}
|
|
48
55
|
return pairs;
|
|
@@ -57,7 +64,7 @@ function normalizeFuzzyDistance(value) {
|
|
|
57
64
|
|| normalized === "EUCLIDEAN") {
|
|
58
65
|
return normalized;
|
|
59
66
|
}
|
|
60
|
-
throw new
|
|
67
|
+
throw new ClientValidationError("fuzzyDistance must be one of DAMERAU_LEVENSHTEIN, HAMMING, JACCARD, COSINE, or EUCLIDEAN.", "validation_failed");
|
|
61
68
|
}
|
|
62
69
|
function normalizeJoinType(value) {
|
|
63
70
|
const normalized = typeof value === "string" && value.trim().length > 0
|
|
@@ -67,7 +74,7 @@ function normalizeJoinType(value) {
|
|
|
67
74
|
|| normalized === "FULL") {
|
|
68
75
|
return normalized;
|
|
69
76
|
}
|
|
70
|
-
throw new
|
|
77
|
+
throw new ClientValidationError("joinType must be one of LEFT, INNER, RIGHT, or FULL.", "validation_failed");
|
|
71
78
|
}
|
|
72
79
|
function recipeVirtualInputs(payload, inputCount) {
|
|
73
80
|
const current = Array.isArray(payload.virtualInputs) ? payload.virtualInputs : [];
|
|
@@ -208,7 +215,7 @@ function rewriteSqlTableReferences(payload, rewrites) {
|
|
|
208
215
|
return `${keyword}${space}[${escapedTo}]`;
|
|
209
216
|
}
|
|
210
217
|
if (!bareIdentifierPattern.test(to)) {
|
|
211
|
-
throw new
|
|
218
|
+
throw new ClientValidationError(`Unsafe SQL rewrite target for ${from}: ${to}`, "validation_failed");
|
|
212
219
|
}
|
|
213
220
|
return `${keyword}${space}${to}`;
|
|
214
221
|
});
|
|
@@ -292,6 +299,96 @@ function shouldRetryRecipeCreateWithOutputProvisioning(error) {
|
|
|
292
299
|
// ---------------------------------------------------------------------------
|
|
293
300
|
// Resource
|
|
294
301
|
// ---------------------------------------------------------------------------
|
|
302
|
+
/**
|
|
303
|
+
* Pure request construction for POST /recipes/ (no DSS calls). Shared with
|
|
304
|
+
* `recipe create --plan` so the plan equals the request the command sends.
|
|
305
|
+
*/
|
|
306
|
+
export function buildRecipeCreateRequest(opts, pk) {
|
|
307
|
+
const { type, payload, outputConnection: rawConnection, joinType: rawJoinType, } = opts;
|
|
308
|
+
const outputFolder = asString(opts.outputFolder);
|
|
309
|
+
// Build inputs/outputs from simple form (inputDatasets + outputDataset) or
|
|
310
|
+
// advanced form (inputs + outputs); both may coexist — simple form wins when
|
|
311
|
+
// the advanced form is absent.
|
|
312
|
+
const inputDatasets = asStringArray(opts.inputDatasets);
|
|
313
|
+
const requestedOutputDataset = asString(opts.outputDataset);
|
|
314
|
+
let inputs = asRecord(opts.inputs);
|
|
315
|
+
let outputs = asRecord(opts.outputs);
|
|
316
|
+
if (!inputs) {
|
|
317
|
+
inputs = {
|
|
318
|
+
main: {
|
|
319
|
+
items: inputDatasets?.map((ref) => ({ ref, deps: [], })) ?? [],
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
// Auto-generate name if not provided
|
|
324
|
+
const outputNameForDefaultRecipe = requestedOutputDataset ?? outputFolder;
|
|
325
|
+
const name = opts.name ?? (type && outputNameForDefaultRecipe
|
|
326
|
+
? `${type}_${outputNameForDefaultRecipe}`
|
|
327
|
+
: undefined);
|
|
328
|
+
const temporaryOutputDataset = outputFolder && !requestedOutputDataset && name
|
|
329
|
+
? `${name}_folder_output_marker`
|
|
330
|
+
: undefined;
|
|
331
|
+
const outputDataset = requestedOutputDataset ?? temporaryOutputDataset;
|
|
332
|
+
if (!outputs && outputDataset) {
|
|
333
|
+
outputs = {
|
|
334
|
+
main: {
|
|
335
|
+
items: [{ ref: outputDataset, appendMode: false, },],
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (!type || !name || !outputs) {
|
|
340
|
+
throw new ClientValidationError("type and outputDataset/outputFolder or (name + outputs) are required for create.", "validation_failed");
|
|
341
|
+
}
|
|
342
|
+
const joinCols = typeof opts.joinOn === "string" ? [opts.joinOn,] : asStringArray(opts.joinOn);
|
|
343
|
+
const joinKeys = type === "join" && joinCols?.length
|
|
344
|
+
? parseJoinKeyPairs(joinCols)
|
|
345
|
+
: undefined;
|
|
346
|
+
const fuzzyOn = opts.fuzzyOn ?? (type === "fuzzyjoin" ? opts.joinOn : undefined);
|
|
347
|
+
const fuzzyCols = typeof fuzzyOn === "string" ? [fuzzyOn,] : asStringArray(fuzzyOn);
|
|
348
|
+
const fuzzyKeys = type === "fuzzyjoin" && fuzzyCols?.length
|
|
349
|
+
? parseJoinKeyPairs(fuzzyCols, "fuzzyOn")
|
|
350
|
+
: undefined;
|
|
351
|
+
const normalizedJoinType = joinKeys?.length || fuzzyKeys?.length
|
|
352
|
+
? normalizeJoinType(rawJoinType)
|
|
353
|
+
: undefined;
|
|
354
|
+
const fuzzyDistance = fuzzyKeys?.length ? normalizeFuzzyDistance(opts.fuzzyDistance) : undefined;
|
|
355
|
+
const fuzzyThreshold = opts.fuzzyThreshold ?? 1;
|
|
356
|
+
if (!Number.isFinite(fuzzyThreshold)) {
|
|
357
|
+
throw new ClientValidationError("fuzzyThreshold must be a finite number.", "validation_failed");
|
|
358
|
+
}
|
|
359
|
+
const recipePrototype = {
|
|
360
|
+
type,
|
|
361
|
+
name,
|
|
362
|
+
projectKey: pk,
|
|
363
|
+
...(type === "fuzzyjoin" ? {} : { inputs, }),
|
|
364
|
+
outputs,
|
|
365
|
+
};
|
|
366
|
+
const creationSettings = {};
|
|
367
|
+
if (payload !== undefined) {
|
|
368
|
+
creationSettings.script = payload;
|
|
369
|
+
}
|
|
370
|
+
if (type === "fuzzyjoin") {
|
|
371
|
+
creationSettings.virtualInputs = recipeInputItems({ inputs, }).map((item) => item.ref);
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
creationSettings,
|
|
375
|
+
fuzzyDistance,
|
|
376
|
+
fuzzyKeys,
|
|
377
|
+
fuzzyThreshold,
|
|
378
|
+
inputDatasets,
|
|
379
|
+
inputs,
|
|
380
|
+
joinKeys,
|
|
381
|
+
name,
|
|
382
|
+
normalizedJoinType,
|
|
383
|
+
outputFolder,
|
|
384
|
+
outputs,
|
|
385
|
+
payload,
|
|
386
|
+
rawConnection,
|
|
387
|
+
recipePrototype,
|
|
388
|
+
temporaryOutputDataset,
|
|
389
|
+
type,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
295
392
|
export class RecipesResource extends BaseResource {
|
|
296
393
|
/** List all recipes in a project. */
|
|
297
394
|
async list(projectKey) {
|
|
@@ -433,7 +530,7 @@ export class RecipesResource extends BaseResource {
|
|
|
433
530
|
const { recipe, } = await this.get(recipeName, { projectKey: pk, });
|
|
434
531
|
const outputItems = recipeOutputItems(recipe);
|
|
435
532
|
if (outputItems.length === 0) {
|
|
436
|
-
throw new
|
|
533
|
+
throw new ClientValidationError(`Recipe "${recipeName}" has no output items to build.`, "validation_failed");
|
|
437
534
|
}
|
|
438
535
|
const [datasets, folders,] = await Promise.all([
|
|
439
536
|
this.client.datasets.list(pk),
|
|
@@ -483,7 +580,7 @@ export class RecipesResource extends BaseResource {
|
|
|
483
580
|
}
|
|
484
581
|
const isDataset = datasetNames.has(item.ref);
|
|
485
582
|
if (isDataset && folderId) {
|
|
486
|
-
throw new
|
|
583
|
+
throw new ClientValidationError(`Recipe "${recipeName}" output "${item.ref}" matches both a dataset and a managed folder. Add an explicit output type to the recipe definition or build the target directly with --target-type.`, "validation_failed");
|
|
487
584
|
}
|
|
488
585
|
if (folderId) {
|
|
489
586
|
return {
|
|
@@ -505,7 +602,7 @@ export class RecipesResource extends BaseResource {
|
|
|
505
602
|
partition: opts?.partition,
|
|
506
603
|
};
|
|
507
604
|
}
|
|
508
|
-
throw new
|
|
605
|
+
throw new ClientValidationError(`Recipe "${recipeName}" output "${item.ref}" was not found as a dataset or managed folder in project "${pk}".`, "validation_failed");
|
|
509
606
|
});
|
|
510
607
|
}
|
|
511
608
|
/** Run a recipe by building its resolved outputs. */
|
|
@@ -543,72 +640,7 @@ export class RecipesResource extends BaseResource {
|
|
|
543
640
|
async create(opts) {
|
|
544
641
|
const pk = this.resolveProjectKey(opts.projectKey);
|
|
545
642
|
const enc = encodeURIComponent(pk);
|
|
546
|
-
const {
|
|
547
|
-
const outputFolder = asString(opts.outputFolder);
|
|
548
|
-
// Build inputs/outputs from simple form (inputDatasets + outputDataset) or
|
|
549
|
-
// advanced form (inputs + outputs); both may coexist — simple form wins when
|
|
550
|
-
// the advanced form is absent.
|
|
551
|
-
const inputDatasets = asStringArray(opts.inputDatasets);
|
|
552
|
-
const requestedOutputDataset = asString(opts.outputDataset);
|
|
553
|
-
let inputs = asRecord(opts.inputs);
|
|
554
|
-
let outputs = asRecord(opts.outputs);
|
|
555
|
-
if (!inputs) {
|
|
556
|
-
inputs = {
|
|
557
|
-
main: {
|
|
558
|
-
items: inputDatasets?.map((ref) => ({ ref, deps: [], })) ?? [],
|
|
559
|
-
},
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
// Auto-generate name if not provided
|
|
563
|
-
const outputNameForDefaultRecipe = requestedOutputDataset ?? outputFolder;
|
|
564
|
-
const name = opts.name ?? (type && outputNameForDefaultRecipe
|
|
565
|
-
? `${type}_${outputNameForDefaultRecipe}`
|
|
566
|
-
: undefined);
|
|
567
|
-
const temporaryOutputDataset = outputFolder && !requestedOutputDataset && name
|
|
568
|
-
? `${name}_folder_output_marker`
|
|
569
|
-
: undefined;
|
|
570
|
-
const outputDataset = requestedOutputDataset ?? temporaryOutputDataset;
|
|
571
|
-
if (!outputs && outputDataset) {
|
|
572
|
-
outputs = {
|
|
573
|
-
main: {
|
|
574
|
-
items: [{ ref: outputDataset, appendMode: false, },],
|
|
575
|
-
},
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
if (!type || !name || !outputs) {
|
|
579
|
-
throw new Error("type and outputDataset/outputFolder or (name + outputs) are required for create.");
|
|
580
|
-
}
|
|
581
|
-
const joinCols = typeof opts.joinOn === "string" ? [opts.joinOn,] : asStringArray(opts.joinOn);
|
|
582
|
-
const joinKeys = type === "join" && joinCols?.length
|
|
583
|
-
? parseJoinKeyPairs(joinCols)
|
|
584
|
-
: undefined;
|
|
585
|
-
const fuzzyOn = opts.fuzzyOn ?? (type === "fuzzyjoin" ? opts.joinOn : undefined);
|
|
586
|
-
const fuzzyCols = typeof fuzzyOn === "string" ? [fuzzyOn,] : asStringArray(fuzzyOn);
|
|
587
|
-
const fuzzyKeys = type === "fuzzyjoin" && fuzzyCols?.length
|
|
588
|
-
? parseJoinKeyPairs(fuzzyCols, "fuzzyOn")
|
|
589
|
-
: undefined;
|
|
590
|
-
const normalizedJoinType = joinKeys?.length || fuzzyKeys?.length
|
|
591
|
-
? normalizeJoinType(rawJoinType)
|
|
592
|
-
: undefined;
|
|
593
|
-
const fuzzyDistance = fuzzyKeys?.length ? normalizeFuzzyDistance(opts.fuzzyDistance) : undefined;
|
|
594
|
-
const fuzzyThreshold = opts.fuzzyThreshold ?? 1;
|
|
595
|
-
if (!Number.isFinite(fuzzyThreshold)) {
|
|
596
|
-
throw new Error("fuzzyThreshold must be a finite number.");
|
|
597
|
-
}
|
|
598
|
-
const recipePrototype = {
|
|
599
|
-
type,
|
|
600
|
-
name,
|
|
601
|
-
projectKey: pk,
|
|
602
|
-
...(type === "fuzzyjoin" ? {} : { inputs, }),
|
|
603
|
-
outputs,
|
|
604
|
-
};
|
|
605
|
-
const creationSettings = {};
|
|
606
|
-
if (payload !== undefined) {
|
|
607
|
-
creationSettings.script = payload;
|
|
608
|
-
}
|
|
609
|
-
if (type === "fuzzyjoin") {
|
|
610
|
-
creationSettings.virtualInputs = recipeInputItems({ inputs, }).map((item) => item.ref);
|
|
611
|
-
}
|
|
643
|
+
const { creationSettings, fuzzyDistance, fuzzyKeys, fuzzyThreshold, inputDatasets, inputs, joinKeys, name, normalizedJoinType, outputFolder, outputs, rawConnection, recipePrototype, temporaryOutputDataset, type, } = buildRecipeCreateRequest(opts, pk);
|
|
612
644
|
const createRecipe = () => this.client.post(`/public/api/projects/${enc}/recipes/`, {
|
|
613
645
|
recipePrototype,
|
|
614
646
|
creationSettings,
|
|
@@ -695,7 +727,7 @@ export class RecipesResource extends BaseResource {
|
|
|
695
727
|
const created = await createRecipe();
|
|
696
728
|
const receivedName = asString(created?.["name"]);
|
|
697
729
|
if (!receivedName) {
|
|
698
|
-
throw
|
|
730
|
+
throw unexpectedResponseError('DSS create-recipe response did not include the final recipe name (documented body: {"name": ...}).');
|
|
699
731
|
}
|
|
700
732
|
finalRecipeName = receivedName;
|
|
701
733
|
}
|
|
@@ -708,7 +740,7 @@ export class RecipesResource extends BaseResource {
|
|
|
708
740
|
const retryCreated = await createRecipe();
|
|
709
741
|
const retryName = asString(retryCreated?.["name"]);
|
|
710
742
|
if (!retryName) {
|
|
711
|
-
throw
|
|
743
|
+
throw unexpectedResponseError('DSS create-recipe response did not include the final recipe name (documented body: {"name": ...}).');
|
|
712
744
|
}
|
|
713
745
|
finalRecipeName = retryName;
|
|
714
746
|
}
|
|
@@ -869,11 +901,11 @@ export class RecipesResource extends BaseResource {
|
|
|
869
901
|
const current = await this.client.get(`/public/api/projects/${enc}/recipes/${rnEnc}`);
|
|
870
902
|
const currentRecipe = asRecord(current.recipe);
|
|
871
903
|
if (!currentRecipe) {
|
|
872
|
-
throw new
|
|
904
|
+
throw new ClientValidationError(`Recipe "${recipeName}" was not found or returned an empty definition.`, "validation_failed");
|
|
873
905
|
}
|
|
874
906
|
const misplacedRecipeFields = rootRecipeDefinitionFields(data);
|
|
875
907
|
if (misplacedRecipeFields.length > 0) {
|
|
876
|
-
throw new
|
|
908
|
+
throw new ClientValidationError(`Recipe fields ${misplacedRecipeFields.join(", ")} must be nested under "recipe". Example: {"recipe":{"outputs":{...},"params":{...}}}`, "validation_failed");
|
|
877
909
|
}
|
|
878
910
|
const mergedRecipe = deepMerge(currentRecipe, asRecord(data.recipe) ?? {});
|
|
879
911
|
const merged = { ...current, ...data, recipe: mergedRecipe, };
|
|
@@ -895,7 +927,7 @@ export class RecipesResource extends BaseResource {
|
|
|
895
927
|
if (opts.outputDataset !== undefined) {
|
|
896
928
|
const outputs = recipeOutputItems(source.recipe).filter((item) => item.type !== "MANAGED_FOLDER");
|
|
897
929
|
if (outputs.length !== 1 && Object.keys(outputRewrites).length === 0) {
|
|
898
|
-
throw new
|
|
930
|
+
throw new ClientValidationError(`Recipe "${sourceName}" has ${outputs.length} dataset outputs; pass explicit outputRewrites instead of outputDataset.`, "validation_failed");
|
|
899
931
|
}
|
|
900
932
|
if (outputs[0])
|
|
901
933
|
outputRewrites[outputs[0].ref] = opts.outputDataset;
|
|
@@ -910,7 +942,7 @@ export class RecipesResource extends BaseResource {
|
|
|
910
942
|
if (opts.copyOutputSettings === true
|
|
911
943
|
&& Object.keys(outputRewrites).length > 1
|
|
912
944
|
&& (opts.outputPath !== undefined || opts.metastoreTableName !== undefined)) {
|
|
913
|
-
throw new
|
|
945
|
+
throw new ClientValidationError("Cannot reuse --path or --metastore-table for multiple cloned output datasets; pass per-output settings in a separate step.", "validation_failed");
|
|
914
946
|
}
|
|
915
947
|
const payloadTextRewrites = {};
|
|
916
948
|
if (opts.payloadTextRewrites)
|
|
@@ -956,7 +988,7 @@ export class RecipesResource extends BaseResource {
|
|
|
956
988
|
projectKey: opts?.projectKey,
|
|
957
989
|
});
|
|
958
990
|
if (!result.payload) {
|
|
959
|
-
throw new
|
|
991
|
+
throw new ClientValidationError(`Recipe "${recipeName}" has no code payload.`, "validation_failed");
|
|
960
992
|
}
|
|
961
993
|
const safeRecipeName = sanitizeFileName(recipeName, "recipe");
|
|
962
994
|
const filePath = opts?.outputPath ?? resolve(process.cwd(), `${safeRecipeName}${inferRecipeCodeExtension(result.recipe.type)}`);
|
|
@@ -970,7 +1002,7 @@ export class RecipesResource extends BaseResource {
|
|
|
970
1002
|
projectKey: opts?.projectKey,
|
|
971
1003
|
});
|
|
972
1004
|
if (!result.payload) {
|
|
973
|
-
throw new
|
|
1005
|
+
throw new ClientValidationError(`Recipe "${recipeName}" has no code payload.`, "validation_failed");
|
|
974
1006
|
}
|
|
975
1007
|
return result.payload;
|
|
976
1008
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
1
|
+
import { ClientValidationError, DataikuError, nonJsonResponseBody, SQL_QUERY_FAILED_MARKER, } from "../errors.js";
|
|
2
2
|
import { BaseResource, } from "./base.js";
|
|
3
3
|
const UNSUPPORTED_SQL_DATASET_CONNECTION_DETAIL = "neither of sql nor hdfs type";
|
|
4
4
|
/**
|
|
@@ -159,7 +159,12 @@ export class SqlResource extends BaseResource {
|
|
|
159
159
|
if (truncated) {
|
|
160
160
|
throw new DataikuError(0, "Response Too Large", `SQL stream results exceeded the ${maxBytes}-byte response limit and were not parsed.`);
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
try {
|
|
163
|
+
return JSON.parse(text);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
throw new DataikuError(200, "Invalid JSON response", nonJsonResponseBody(text));
|
|
167
|
+
}
|
|
163
168
|
}
|
|
164
169
|
/**
|
|
165
170
|
* Verify that a query finished successfully server-side.
|
|
@@ -173,7 +178,7 @@ export class SqlResource extends BaseResource {
|
|
|
173
178
|
throw new DataikuError(0, "Response Too Large", `SQL finish-streaming response exceeded the ${maxBytes}-byte response limit.`);
|
|
174
179
|
}
|
|
175
180
|
if (text.length > 0) {
|
|
176
|
-
throw new
|
|
181
|
+
throw new DataikuError(200, "SQL Query Failed", `${SQL_QUERY_FAILED_MARKER} (${queryId}): ${text}`);
|
|
177
182
|
}
|
|
178
183
|
}
|
|
179
184
|
async executeQuery(opts) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { UsageError, } from "../cli/usage.js";
|
|
2
|
+
import { ClientValidationError, } from "../errors.js";
|
|
2
3
|
import { BaseResource, } from "./base.js";
|
|
3
4
|
function isRecord(value) {
|
|
4
5
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -36,7 +37,7 @@ export class StatisticsResource extends BaseResource {
|
|
|
36
37
|
async runWorksheet(datasetName, worksheetId, projectKey) {
|
|
37
38
|
const worksheet = await this.getWorksheet(datasetName, worksheetId, projectKey);
|
|
38
39
|
if (!isRecord(worksheet.rootCard)) {
|
|
39
|
-
throw new
|
|
40
|
+
throw new ClientValidationError("statistics.runWorksheet requires worksheet.rootCard", "validation_failed");
|
|
40
41
|
}
|
|
41
42
|
return this.runCard(datasetName, worksheetId, worksheet.rootCard, projectKey);
|
|
42
43
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ClientValidationError, } from "../errors.js";
|
|
1
2
|
import { ProjectVariablesSchema, } from "../schemas.js";
|
|
2
3
|
import { BaseResource, } from "./base.js";
|
|
3
4
|
export class VariablesResource extends BaseResource {
|
|
@@ -17,7 +18,7 @@ export class VariablesResource extends BaseResource {
|
|
|
17
18
|
return replaced;
|
|
18
19
|
}
|
|
19
20
|
if (opts.standard === undefined && opts.local === undefined) {
|
|
20
|
-
throw new
|
|
21
|
+
throw new ClientValidationError("At least one of standard or local must be provided", "validation_failed");
|
|
21
22
|
}
|
|
22
23
|
const existing = await this.get(opts.projectKey);
|
|
23
24
|
const merged = {
|