dataiku-sdk 3.4.0 → 3.6.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 +4 -4
- package/bin/dss.js +28 -2
- package/dist/build-metadata.json +1 -1
- package/dist/src/cli/coerce.d.ts +0 -1
- package/dist/src/cli/coerce.js +0 -5
- 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 +25 -41
- 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 +29 -54
- 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 +17 -26
- 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 +157 -254
- package/dist/src/cli/doctor.js +1 -0
- package/dist/src/cli/env.d.ts +2 -0
- package/dist/src/cli/env.js +16 -1
- package/dist/src/cli/flags.d.ts +1 -0
- package/dist/src/cli/flags.js +4 -9
- package/dist/src/cli/helpers/app-permissions.js +7 -5
- package/dist/src/cli/helpers/flow-zone.js +8 -6
- 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.d.ts +0 -2
- package/dist/src/cli/version.js +6 -7
- package/dist/src/cli.js +132 -274
- package/dist/src/client.d.ts +2 -1
- package/dist/src/client.js +32 -17
- 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/base.d.ts +2 -0
- package/dist/src/resources/base.js +14 -2
- package/dist/src/resources/code-envs.js +3 -5
- package/dist/src/resources/data-quality.js +14 -13
- 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.d.ts +1 -1
- package/dist/src/resources/macros.js +3 -7
- 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 +117 -89
- package/dist/src/resources/saved-models.js +1 -13
- package/dist/src/resources/scenarios.js +1 -3
- package/dist/src/resources/sql.js +9 -8
- package/dist/src/resources/statistics.js +3 -4
- 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/deep-merge.js +2 -4
- package/dist/src/utils/flow-map.js +1 -6
- package/dist/src/utils/polling.d.ts +3 -0
- package/dist/src/utils/polling.js +3 -0
- package/dist/src/utils/project-archive.js +10 -14
- package/dist/src/utils/project-incarnation.js +3 -7
- package/dist/src/utils/records.d.ts +4 -0
- package/dist/src/utils/records.js +8 -0
- package/dist/src/utils/stable-hash.d.ts +3 -1
- package/dist/src/utils/stable-hash.js +2 -0
- package/package.json +10 -14
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +3 -4
- package/skills/dataiku-dss/references/authentication.md +3 -3
- package/skills/dataiku-dss/references/coding.md +1 -1
- package/skills/dataiku-dss/references/discovery.md +1 -1
- package/skills/dataiku-dss/references/mutations.md +0 -1
- package/dist/src/utils/pagination.d.ts +0 -8
- package/dist/src/utils/pagination.js +0 -23
- package/packages/types/dist/index.d.ts +0 -1062
- package/packages/types/dist/index.js +0 -871
|
@@ -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,4 +1,5 @@
|
|
|
1
1
|
import type { MacroDefinition, MacroRunStart, MacroState, MacroSummary, MacroWaitResult } from "../schemas.js";
|
|
2
|
+
import { isRecord } from "../utils/records.js";
|
|
2
3
|
import { BaseResource } from "./base.js";
|
|
3
4
|
export interface MacroRunOptions {
|
|
4
5
|
/**
|
|
@@ -42,7 +43,6 @@ export interface MacroResultOptions {
|
|
|
42
43
|
* inclusive range [1, 2 GiB], rejecting non-integers before any DSS request.
|
|
43
44
|
*/
|
|
44
45
|
export declare function macroResultMaxBytes(maxBytes: number | undefined): number;
|
|
45
|
-
declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
46
46
|
/**
|
|
47
47
|
* Run result of {@link MacrosResource.run} when the caller opted into
|
|
48
48
|
* `wait: false` (the default): a run id for abort/state/result tracking.
|
|
@@ -1,9 +1,8 @@
|
|
|
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
|
+
import { isRecord, } from "../utils/records.js";
|
|
4
5
|
import { BaseResource, requireNonEmpty, } from "./base.js";
|
|
5
|
-
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
6
|
-
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
7
6
|
/** Hard floor for the result download cap; keeps a caller typo from hanging the read. */
|
|
8
7
|
const MIN_RESULT_MAX_BYTES = 1;
|
|
9
8
|
const MAX_RESULT_MAX_BYTES = 2 * 1024 * 1024 * 1024;
|
|
@@ -18,9 +17,6 @@ export function macroResultMaxBytes(maxBytes) {
|
|
|
18
17
|
}
|
|
19
18
|
return Math.min(raw, MAX_RESULT_MAX_BYTES);
|
|
20
19
|
}
|
|
21
|
-
function isRecord(value) {
|
|
22
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
-
}
|
|
24
20
|
/**
|
|
25
21
|
* Build the run POST body from params/adminParams. DSS macro runs accept
|
|
26
22
|
* `params` keyed by the macro's declared param names and `adminParams` keyed
|
|
@@ -121,7 +117,7 @@ export class MacrosResource extends BaseResource {
|
|
|
121
117
|
const maxBytes = macroResultMaxBytes(opts?.maxBytes);
|
|
122
118
|
const raw = await this.client.getTextLimited(`${this.base(runnableType, opts?.projectKey)}/result/${encodeURIComponent(runId)}`, maxBytes);
|
|
123
119
|
if (raw.truncated) {
|
|
124
|
-
throw new
|
|
120
|
+
throw new ClientValidationError(`Macro result exceeded ${String(maxBytes)} bytes; retry with a larger maxBytes.`, "validation_failed");
|
|
125
121
|
}
|
|
126
122
|
const text = raw.text;
|
|
127
123
|
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,8 +1,9 @@
|
|
|
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
|
+
import { asRecord, } from "../utils/records.js";
|
|
6
7
|
import { sanitizeFileName, } from "../utils/sanitize.js";
|
|
7
8
|
import { BaseResource, } from "./base.js";
|
|
8
9
|
// ---------------------------------------------------------------------------
|
|
@@ -17,15 +18,16 @@ function asStringArray(value) {
|
|
|
17
18
|
const out = value.filter((v) => typeof v === "string" && v.length > 0);
|
|
18
19
|
return out.length > 0 ? out : undefined;
|
|
19
20
|
}
|
|
20
|
-
function asRecord(value) {
|
|
21
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
22
|
-
return undefined;
|
|
23
|
-
return value;
|
|
24
|
-
}
|
|
25
21
|
function parseRecipePayload(payload) {
|
|
26
22
|
if (!payload)
|
|
27
23
|
return {};
|
|
28
|
-
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = JSON.parse(payload);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw new DataikuError(200, "Invalid JSON response", nonJsonResponseBody(payload));
|
|
30
|
+
}
|
|
29
31
|
return asRecord(parsed) ?? {};
|
|
30
32
|
}
|
|
31
33
|
function parseJoinKeyPairs(values, optionName = "joinOn") {
|
|
@@ -41,8 +43,9 @@ function parseJoinKeyPairs(values, optionName = "joinOn") {
|
|
|
41
43
|
}
|
|
42
44
|
const left = token.slice(0, eq).trim();
|
|
43
45
|
const right = token.slice(eq + 1).trim();
|
|
44
|
-
if (!left || !right)
|
|
45
|
-
throw new
|
|
46
|
+
if (!left || !right) {
|
|
47
|
+
throw new ClientValidationError(`${optionName} values must use COL or LEFT=RIGHT.`, "validation_failed");
|
|
48
|
+
}
|
|
46
49
|
pairs.push({ left, right, });
|
|
47
50
|
}
|
|
48
51
|
return pairs;
|
|
@@ -57,7 +60,7 @@ function normalizeFuzzyDistance(value) {
|
|
|
57
60
|
|| normalized === "EUCLIDEAN") {
|
|
58
61
|
return normalized;
|
|
59
62
|
}
|
|
60
|
-
throw new
|
|
63
|
+
throw new ClientValidationError("fuzzyDistance must be one of DAMERAU_LEVENSHTEIN, HAMMING, JACCARD, COSINE, or EUCLIDEAN.", "validation_failed");
|
|
61
64
|
}
|
|
62
65
|
function normalizeJoinType(value) {
|
|
63
66
|
const normalized = typeof value === "string" && value.trim().length > 0
|
|
@@ -67,7 +70,7 @@ function normalizeJoinType(value) {
|
|
|
67
70
|
|| normalized === "FULL") {
|
|
68
71
|
return normalized;
|
|
69
72
|
}
|
|
70
|
-
throw new
|
|
73
|
+
throw new ClientValidationError("joinType must be one of LEFT, INNER, RIGHT, or FULL.", "validation_failed");
|
|
71
74
|
}
|
|
72
75
|
function recipeVirtualInputs(payload, inputCount) {
|
|
73
76
|
const current = Array.isArray(payload.virtualInputs) ? payload.virtualInputs : [];
|
|
@@ -208,7 +211,7 @@ function rewriteSqlTableReferences(payload, rewrites) {
|
|
|
208
211
|
return `${keyword}${space}[${escapedTo}]`;
|
|
209
212
|
}
|
|
210
213
|
if (!bareIdentifierPattern.test(to)) {
|
|
211
|
-
throw new
|
|
214
|
+
throw new ClientValidationError(`Unsafe SQL rewrite target for ${from}: ${to}`, "validation_failed");
|
|
212
215
|
}
|
|
213
216
|
return `${keyword}${space}${to}`;
|
|
214
217
|
});
|
|
@@ -292,6 +295,96 @@ function shouldRetryRecipeCreateWithOutputProvisioning(error) {
|
|
|
292
295
|
// ---------------------------------------------------------------------------
|
|
293
296
|
// Resource
|
|
294
297
|
// ---------------------------------------------------------------------------
|
|
298
|
+
/**
|
|
299
|
+
* Pure request construction for POST /recipes/ (no DSS calls). Shared with
|
|
300
|
+
* `recipe create --plan` so the plan equals the request the command sends.
|
|
301
|
+
*/
|
|
302
|
+
export function buildRecipeCreateRequest(opts, pk) {
|
|
303
|
+
const { type, payload, outputConnection: rawConnection, joinType: rawJoinType, } = opts;
|
|
304
|
+
const outputFolder = asString(opts.outputFolder);
|
|
305
|
+
// Build inputs/outputs from simple form (inputDatasets + outputDataset) or
|
|
306
|
+
// advanced form (inputs + outputs); both may coexist — simple form wins when
|
|
307
|
+
// the advanced form is absent.
|
|
308
|
+
const inputDatasets = asStringArray(opts.inputDatasets);
|
|
309
|
+
const requestedOutputDataset = asString(opts.outputDataset);
|
|
310
|
+
let inputs = asRecord(opts.inputs);
|
|
311
|
+
let outputs = asRecord(opts.outputs);
|
|
312
|
+
if (!inputs) {
|
|
313
|
+
inputs = {
|
|
314
|
+
main: {
|
|
315
|
+
items: inputDatasets?.map((ref) => ({ ref, deps: [], })) ?? [],
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
// Auto-generate name if not provided
|
|
320
|
+
const outputNameForDefaultRecipe = requestedOutputDataset ?? outputFolder;
|
|
321
|
+
const name = opts.name ?? (type && outputNameForDefaultRecipe
|
|
322
|
+
? `${type}_${outputNameForDefaultRecipe}`
|
|
323
|
+
: undefined);
|
|
324
|
+
const temporaryOutputDataset = outputFolder && !requestedOutputDataset && name
|
|
325
|
+
? `${name}_folder_output_marker`
|
|
326
|
+
: undefined;
|
|
327
|
+
const outputDataset = requestedOutputDataset ?? temporaryOutputDataset;
|
|
328
|
+
if (!outputs && outputDataset) {
|
|
329
|
+
outputs = {
|
|
330
|
+
main: {
|
|
331
|
+
items: [{ ref: outputDataset, appendMode: false, },],
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
if (!type || !name || !outputs) {
|
|
336
|
+
throw new ClientValidationError("type and outputDataset/outputFolder or (name + outputs) are required for create.", "validation_failed");
|
|
337
|
+
}
|
|
338
|
+
const joinCols = typeof opts.joinOn === "string" ? [opts.joinOn,] : asStringArray(opts.joinOn);
|
|
339
|
+
const joinKeys = type === "join" && joinCols?.length
|
|
340
|
+
? parseJoinKeyPairs(joinCols)
|
|
341
|
+
: undefined;
|
|
342
|
+
const fuzzyOn = opts.fuzzyOn ?? (type === "fuzzyjoin" ? opts.joinOn : undefined);
|
|
343
|
+
const fuzzyCols = typeof fuzzyOn === "string" ? [fuzzyOn,] : asStringArray(fuzzyOn);
|
|
344
|
+
const fuzzyKeys = type === "fuzzyjoin" && fuzzyCols?.length
|
|
345
|
+
? parseJoinKeyPairs(fuzzyCols, "fuzzyOn")
|
|
346
|
+
: undefined;
|
|
347
|
+
const normalizedJoinType = joinKeys?.length || fuzzyKeys?.length
|
|
348
|
+
? normalizeJoinType(rawJoinType)
|
|
349
|
+
: undefined;
|
|
350
|
+
const fuzzyDistance = fuzzyKeys?.length ? normalizeFuzzyDistance(opts.fuzzyDistance) : undefined;
|
|
351
|
+
const fuzzyThreshold = opts.fuzzyThreshold ?? 1;
|
|
352
|
+
if (!Number.isFinite(fuzzyThreshold)) {
|
|
353
|
+
throw new ClientValidationError("fuzzyThreshold must be a finite number.", "validation_failed");
|
|
354
|
+
}
|
|
355
|
+
const recipePrototype = {
|
|
356
|
+
type,
|
|
357
|
+
name,
|
|
358
|
+
projectKey: pk,
|
|
359
|
+
...(type === "fuzzyjoin" ? {} : { inputs, }),
|
|
360
|
+
outputs,
|
|
361
|
+
};
|
|
362
|
+
const creationSettings = {};
|
|
363
|
+
if (payload !== undefined) {
|
|
364
|
+
creationSettings.script = payload;
|
|
365
|
+
}
|
|
366
|
+
if (type === "fuzzyjoin") {
|
|
367
|
+
creationSettings.virtualInputs = recipeInputItems({ inputs, }).map((item) => item.ref);
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
creationSettings,
|
|
371
|
+
fuzzyDistance,
|
|
372
|
+
fuzzyKeys,
|
|
373
|
+
fuzzyThreshold,
|
|
374
|
+
inputDatasets,
|
|
375
|
+
inputs,
|
|
376
|
+
joinKeys,
|
|
377
|
+
name,
|
|
378
|
+
normalizedJoinType,
|
|
379
|
+
outputFolder,
|
|
380
|
+
outputs,
|
|
381
|
+
payload,
|
|
382
|
+
rawConnection,
|
|
383
|
+
recipePrototype,
|
|
384
|
+
temporaryOutputDataset,
|
|
385
|
+
type,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
295
388
|
export class RecipesResource extends BaseResource {
|
|
296
389
|
/** List all recipes in a project. */
|
|
297
390
|
async list(projectKey) {
|
|
@@ -433,7 +526,7 @@ export class RecipesResource extends BaseResource {
|
|
|
433
526
|
const { recipe, } = await this.get(recipeName, { projectKey: pk, });
|
|
434
527
|
const outputItems = recipeOutputItems(recipe);
|
|
435
528
|
if (outputItems.length === 0) {
|
|
436
|
-
throw new
|
|
529
|
+
throw new ClientValidationError(`Recipe "${recipeName}" has no output items to build.`, "validation_failed");
|
|
437
530
|
}
|
|
438
531
|
const [datasets, folders,] = await Promise.all([
|
|
439
532
|
this.client.datasets.list(pk),
|
|
@@ -483,7 +576,7 @@ export class RecipesResource extends BaseResource {
|
|
|
483
576
|
}
|
|
484
577
|
const isDataset = datasetNames.has(item.ref);
|
|
485
578
|
if (isDataset && folderId) {
|
|
486
|
-
throw new
|
|
579
|
+
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
580
|
}
|
|
488
581
|
if (folderId) {
|
|
489
582
|
return {
|
|
@@ -505,7 +598,7 @@ export class RecipesResource extends BaseResource {
|
|
|
505
598
|
partition: opts?.partition,
|
|
506
599
|
};
|
|
507
600
|
}
|
|
508
|
-
throw new
|
|
601
|
+
throw new ClientValidationError(`Recipe "${recipeName}" output "${item.ref}" was not found as a dataset or managed folder in project "${pk}".`, "validation_failed");
|
|
509
602
|
});
|
|
510
603
|
}
|
|
511
604
|
/** Run a recipe by building its resolved outputs. */
|
|
@@ -543,72 +636,7 @@ export class RecipesResource extends BaseResource {
|
|
|
543
636
|
async create(opts) {
|
|
544
637
|
const pk = this.resolveProjectKey(opts.projectKey);
|
|
545
638
|
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
|
-
}
|
|
639
|
+
const { creationSettings, fuzzyDistance, fuzzyKeys, fuzzyThreshold, inputDatasets, inputs, joinKeys, name, normalizedJoinType, outputFolder, outputs, rawConnection, recipePrototype, temporaryOutputDataset, type, } = buildRecipeCreateRequest(opts, pk);
|
|
612
640
|
const createRecipe = () => this.client.post(`/public/api/projects/${enc}/recipes/`, {
|
|
613
641
|
recipePrototype,
|
|
614
642
|
creationSettings,
|
|
@@ -695,7 +723,7 @@ export class RecipesResource extends BaseResource {
|
|
|
695
723
|
const created = await createRecipe();
|
|
696
724
|
const receivedName = asString(created?.["name"]);
|
|
697
725
|
if (!receivedName) {
|
|
698
|
-
throw
|
|
726
|
+
throw unexpectedResponseError('DSS create-recipe response did not include the final recipe name (documented body: {"name": ...}).');
|
|
699
727
|
}
|
|
700
728
|
finalRecipeName = receivedName;
|
|
701
729
|
}
|
|
@@ -708,7 +736,7 @@ export class RecipesResource extends BaseResource {
|
|
|
708
736
|
const retryCreated = await createRecipe();
|
|
709
737
|
const retryName = asString(retryCreated?.["name"]);
|
|
710
738
|
if (!retryName) {
|
|
711
|
-
throw
|
|
739
|
+
throw unexpectedResponseError('DSS create-recipe response did not include the final recipe name (documented body: {"name": ...}).');
|
|
712
740
|
}
|
|
713
741
|
finalRecipeName = retryName;
|
|
714
742
|
}
|
|
@@ -869,11 +897,11 @@ export class RecipesResource extends BaseResource {
|
|
|
869
897
|
const current = await this.client.get(`/public/api/projects/${enc}/recipes/${rnEnc}`);
|
|
870
898
|
const currentRecipe = asRecord(current.recipe);
|
|
871
899
|
if (!currentRecipe) {
|
|
872
|
-
throw new
|
|
900
|
+
throw new ClientValidationError(`Recipe "${recipeName}" was not found or returned an empty definition.`, "validation_failed");
|
|
873
901
|
}
|
|
874
902
|
const misplacedRecipeFields = rootRecipeDefinitionFields(data);
|
|
875
903
|
if (misplacedRecipeFields.length > 0) {
|
|
876
|
-
throw new
|
|
904
|
+
throw new ClientValidationError(`Recipe fields ${misplacedRecipeFields.join(", ")} must be nested under "recipe". Example: {"recipe":{"outputs":{...},"params":{...}}}`, "validation_failed");
|
|
877
905
|
}
|
|
878
906
|
const mergedRecipe = deepMerge(currentRecipe, asRecord(data.recipe) ?? {});
|
|
879
907
|
const merged = { ...current, ...data, recipe: mergedRecipe, };
|
|
@@ -895,7 +923,7 @@ export class RecipesResource extends BaseResource {
|
|
|
895
923
|
if (opts.outputDataset !== undefined) {
|
|
896
924
|
const outputs = recipeOutputItems(source.recipe).filter((item) => item.type !== "MANAGED_FOLDER");
|
|
897
925
|
if (outputs.length !== 1 && Object.keys(outputRewrites).length === 0) {
|
|
898
|
-
throw new
|
|
926
|
+
throw new ClientValidationError(`Recipe "${sourceName}" has ${outputs.length} dataset outputs; pass explicit outputRewrites instead of outputDataset.`, "validation_failed");
|
|
899
927
|
}
|
|
900
928
|
if (outputs[0])
|
|
901
929
|
outputRewrites[outputs[0].ref] = opts.outputDataset;
|
|
@@ -910,7 +938,7 @@ export class RecipesResource extends BaseResource {
|
|
|
910
938
|
if (opts.copyOutputSettings === true
|
|
911
939
|
&& Object.keys(outputRewrites).length > 1
|
|
912
940
|
&& (opts.outputPath !== undefined || opts.metastoreTableName !== undefined)) {
|
|
913
|
-
throw new
|
|
941
|
+
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
942
|
}
|
|
915
943
|
const payloadTextRewrites = {};
|
|
916
944
|
if (opts.payloadTextRewrites)
|
|
@@ -956,7 +984,7 @@ export class RecipesResource extends BaseResource {
|
|
|
956
984
|
projectKey: opts?.projectKey,
|
|
957
985
|
});
|
|
958
986
|
if (!result.payload) {
|
|
959
|
-
throw new
|
|
987
|
+
throw new ClientValidationError(`Recipe "${recipeName}" has no code payload.`, "validation_failed");
|
|
960
988
|
}
|
|
961
989
|
const safeRecipeName = sanitizeFileName(recipeName, "recipe");
|
|
962
990
|
const filePath = opts?.outputPath ?? resolve(process.cwd(), `${safeRecipeName}${inferRecipeCodeExtension(result.recipe.type)}`);
|
|
@@ -970,7 +998,7 @@ export class RecipesResource extends BaseResource {
|
|
|
970
998
|
projectKey: opts?.projectKey,
|
|
971
999
|
});
|
|
972
1000
|
if (!result.payload) {
|
|
973
|
-
throw new
|
|
1001
|
+
throw new ClientValidationError(`Recipe "${recipeName}" has no code payload.`, "validation_failed");
|
|
974
1002
|
}
|
|
975
1003
|
return result.payload;
|
|
976
1004
|
}
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
import { basename, } from "node:path";
|
|
2
2
|
import { ClientValidationError, } from "../errors.js";
|
|
3
|
-
import { BaseResource, requireNonEmpty, } from "./base.js";
|
|
4
|
-
function requireObject(value, name) {
|
|
5
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
6
|
-
throw new ClientValidationError(`${name} must be a JSON object.`, "validation_failed");
|
|
7
|
-
}
|
|
8
|
-
return value;
|
|
9
|
-
}
|
|
10
|
-
function requireNonEmptyArray(value, name) {
|
|
11
|
-
if (!Array.isArray(value) || value.length === 0) {
|
|
12
|
-
throw new ClientValidationError(`${name} must be a non-empty array.`, "validation_failed");
|
|
13
|
-
}
|
|
14
|
-
return value;
|
|
15
|
-
}
|
|
3
|
+
import { BaseResource, requireNonEmpty, requireNonEmptyArray, requireObject, } from "./base.js";
|
|
16
4
|
function boolQuery(value) {
|
|
17
5
|
return value === undefined ? "true" : String(value);
|
|
18
6
|
}
|
|
@@ -3,6 +3,7 @@ import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
|
3
3
|
import { ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioSummaryArraySchema, } from "../schemas.js";
|
|
4
4
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
5
5
|
import { computeNextPollDelayMs, isRequestDeadlineError, } from "../utils/polling.js";
|
|
6
|
+
import { isRecord, } from "../utils/records.js";
|
|
6
7
|
import { BaseResource, requireArrayResponse, requireNonEmpty, } from "./base.js";
|
|
7
8
|
export const SCENARIO_CANONICAL_EDITABLE_FIELDS = [
|
|
8
9
|
"params.steps",
|
|
@@ -147,9 +148,6 @@ function extractCodeRunOutput(log) {
|
|
|
147
148
|
body.pop();
|
|
148
149
|
return body.join("\n");
|
|
149
150
|
}
|
|
150
|
-
function isRecord(value) {
|
|
151
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
152
|
-
}
|
|
153
151
|
function scenarioStepWarningSummary(stepResult) {
|
|
154
152
|
const container = stepResult?.warnings;
|
|
155
153
|
if (!isRecord(container))
|