dataiku-sdk 3.6.1 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +14 -6
  2. package/bin/bun-version.js +14 -0
  3. package/bin/dss.js +11 -15
  4. package/dist/build-metadata.json +1 -1
  5. package/dist/packages/types/src/index.d.ts +114 -5
  6. package/dist/packages/types/src/index.js +75 -2
  7. package/dist/src/cli/coerce.d.ts +2 -0
  8. package/dist/src/cli/coerce.js +20 -16
  9. package/dist/src/cli/command-syntax.json +3369 -144
  10. package/dist/src/cli/commands/analysis.js +19 -1
  11. package/dist/src/cli/commands/bundle.js +22 -0
  12. package/dist/src/cli/commands/connection.js +26 -2
  13. package/dist/src/cli/commands/data-quality.js +33 -1
  14. package/dist/src/cli/commands/dataset.js +76 -9
  15. package/dist/src/cli/commands/discussion.js +15 -0
  16. package/dist/src/cli/commands/doctor.js +1 -1
  17. package/dist/src/cli/commands/flow-zone.js +241 -46
  18. package/dist/src/cli/commands/flow.d.ts +5 -0
  19. package/dist/src/cli/commands/flow.js +57 -0
  20. package/dist/src/cli/commands/future.js +8 -0
  21. package/dist/src/cli/commands/index.js +6 -0
  22. package/dist/src/cli/commands/job.js +44 -6
  23. package/dist/src/cli/commands/metrics.js +21 -1
  24. package/dist/src/cli/commands/ml-task.d.ts +5 -0
  25. package/dist/src/cli/commands/ml-task.js +86 -16
  26. package/dist/src/cli/commands/mlflow.d.ts +11 -0
  27. package/dist/src/cli/commands/mlflow.js +121 -0
  28. package/dist/src/cli/commands/plugin.js +5 -11
  29. package/dist/src/cli/commands/project-deployer.js +70 -0
  30. package/dist/src/cli/commands/project-library.js +2 -5
  31. package/dist/src/cli/commands/project.js +8 -0
  32. package/dist/src/cli/commands/recipe.d.ts +13 -0
  33. package/dist/src/cli/commands/recipe.js +130 -7
  34. package/dist/src/cli/commands/saved-model.d.ts +1 -1
  35. package/dist/src/cli/commands/saved-model.js +9 -76
  36. package/dist/src/cli/commands/streaming-endpoint.js +30 -1
  37. package/dist/src/cli/commands/webapp.js +8 -0
  38. package/dist/src/cli/commands/wiki.js +78 -0
  39. package/dist/src/cli/commands/workspace.js +8 -0
  40. package/dist/src/cli/contract.d.ts +16 -15
  41. package/dist/src/cli/contract.js +150 -3407
  42. package/dist/src/cli/doctor.d.ts +13 -0
  43. package/dist/src/cli/doctor.js +43 -1
  44. package/dist/src/cli/flags.d.ts +14 -1
  45. package/dist/src/cli/flags.js +106 -8
  46. package/dist/src/cli/helpers/app-successor.js +1 -2
  47. package/dist/src/cli/helpers/dataset.d.ts +12 -0
  48. package/dist/src/cli/helpers/dataset.js +36 -0
  49. package/dist/src/cli/helpers/documentation.d.ts +21 -0
  50. package/dist/src/cli/helpers/documentation.js +53 -0
  51. package/dist/src/cli/helpers/flow-zone.d.ts +21 -0
  52. package/dist/src/cli/helpers/flow-zone.js +144 -4
  53. package/dist/src/cli/helpers/trained-model.d.ts +26 -0
  54. package/dist/src/cli/helpers/trained-model.js +144 -0
  55. package/dist/src/cli/list-output.d.ts +7 -0
  56. package/dist/src/cli/list-output.js +77 -0
  57. package/dist/src/cli/list-shapes.d.ts +21 -0
  58. package/dist/src/cli/list-shapes.js +111 -0
  59. package/dist/src/cli/output-schemas.d.ts +0 -14
  60. package/dist/src/cli/output-schemas.js +4 -7
  61. package/dist/src/cli/output.d.ts +0 -1
  62. package/dist/src/cli/output.js +33 -19
  63. package/dist/src/cli/plans.d.ts +14 -0
  64. package/dist/src/cli/plans.js +3740 -0
  65. package/dist/src/cli/types.d.ts +2 -0
  66. package/dist/src/cli/usage.d.ts +7 -0
  67. package/dist/src/cli/usage.js +20 -1
  68. package/dist/src/cli/version.d.ts +2 -2
  69. package/dist/src/cli/version.js +2 -2
  70. package/dist/src/cli.js +137 -115
  71. package/dist/src/client.d.ts +52 -43
  72. package/dist/src/client.js +267 -96
  73. package/dist/src/errors.d.ts +22 -2
  74. package/dist/src/errors.js +49 -50
  75. package/dist/src/generated/action-output-schemas.json +345 -89
  76. package/dist/src/index.d.ts +5 -1
  77. package/dist/src/index.js +3 -0
  78. package/dist/src/resources/analyses.d.ts +8 -0
  79. package/dist/src/resources/analyses.js +13 -1
  80. package/dist/src/resources/bundles.d.ts +34 -0
  81. package/dist/src/resources/bundles.js +67 -0
  82. package/dist/src/resources/connections.d.ts +9 -0
  83. package/dist/src/resources/connections.js +15 -0
  84. package/dist/src/resources/data-quality.d.ts +20 -0
  85. package/dist/src/resources/data-quality.js +30 -0
  86. package/dist/src/resources/dataset-create.d.ts +38 -0
  87. package/dist/src/resources/dataset-create.js +135 -0
  88. package/dist/src/resources/datasets.d.ts +46 -15
  89. package/dist/src/resources/datasets.js +120 -112
  90. package/dist/src/resources/discussions.d.ts +5 -0
  91. package/dist/src/resources/discussions.js +7 -0
  92. package/dist/src/resources/documentation.d.ts +25 -0
  93. package/dist/src/resources/documentation.js +16 -0
  94. package/dist/src/resources/flow-zones.d.ts +29 -3
  95. package/dist/src/resources/flow-zones.js +53 -3
  96. package/dist/src/resources/flow.d.ts +48 -0
  97. package/dist/src/resources/flow.js +65 -0
  98. package/dist/src/resources/futures.d.ts +23 -0
  99. package/dist/src/resources/futures.js +32 -0
  100. package/dist/src/resources/jobs.d.ts +34 -0
  101. package/dist/src/resources/jobs.js +170 -3
  102. package/dist/src/resources/metrics.d.ts +10 -0
  103. package/dist/src/resources/metrics.js +10 -0
  104. package/dist/src/resources/ml-tasks.d.ts +65 -2
  105. package/dist/src/resources/ml-tasks.js +102 -28
  106. package/dist/src/resources/mlflow-extension.d.ts +73 -0
  107. package/dist/src/resources/mlflow-extension.js +105 -0
  108. package/dist/src/resources/projects.d.ts +15 -0
  109. package/dist/src/resources/projects.js +18 -3
  110. package/dist/src/resources/recipe-create.d.ts +79 -0
  111. package/dist/src/resources/recipe-create.js +454 -0
  112. package/dist/src/resources/recipes.d.ts +28 -29
  113. package/dist/src/resources/recipes.js +201 -267
  114. package/dist/src/resources/saved-models.d.ts +10 -6
  115. package/dist/src/resources/saved-models.js +16 -14
  116. package/dist/src/resources/scenarios.js +8 -14
  117. package/dist/src/resources/sql.js +52 -13
  118. package/dist/src/resources/streaming-endpoints.d.ts +12 -0
  119. package/dist/src/resources/streaming-endpoints.js +15 -0
  120. package/dist/src/resources/trained-model.d.ts +60 -0
  121. package/dist/src/resources/trained-model.js +98 -0
  122. package/dist/src/resources/webapps.d.ts +8 -0
  123. package/dist/src/resources/webapps.js +10 -0
  124. package/dist/src/resources/wiki.d.ts +52 -1
  125. package/dist/src/resources/wiki.js +96 -0
  126. package/dist/src/resources/workspaces.d.ts +2 -0
  127. package/dist/src/resources/workspaces.js +4 -0
  128. package/dist/src/utils/flow-analysis.js +4 -0
  129. package/dist/src/utils/response-file.js +20 -9
  130. package/dist/src/utils/secret-sanitize.d.ts +7 -0
  131. package/dist/src/utils/secret-sanitize.js +46 -0
  132. package/package.json +25 -26
  133. package/plugin.json +1 -1
  134. package/skills/dataiku-dss/SKILL.md +1 -1
  135. package/skills/dataiku-dss/references/authentication.md +3 -1
  136. package/skills/dataiku-dss/references/coding.md +1 -0
  137. package/skills/dataiku-dss/references/discovery.md +4 -6
  138. package/skills/dataiku-dss/references/flow-maps.md +2 -2
  139. package/skills/dataiku-dss/references/troubleshooting.md +3 -2
@@ -0,0 +1,454 @@
1
+ import { ClientValidationError, } from "../errors.js";
2
+ import { asRecord, } from "../utils/records.js";
3
+ /*
4
+ * Pure, schema-free recipe-create request construction. `recipe create --plan`
5
+ * imports this without loading the SDK schema graph.
6
+ */
7
+ export function asString(value) {
8
+ return typeof value === "string" && value.length > 0 ? value : undefined;
9
+ }
10
+ export function asStringArray(value) {
11
+ if (!Array.isArray(value))
12
+ return undefined;
13
+ const out = value.filter((v) => typeof v === "string" && v.length > 0);
14
+ return out.length > 0 ? out : undefined;
15
+ }
16
+ export function parseJoinKeyPairs(values, optionName = "joinOn") {
17
+ const pairs = [];
18
+ for (const raw of values) {
19
+ const token = raw.trim();
20
+ if (!token)
21
+ continue;
22
+ const eq = token.indexOf("=");
23
+ if (eq === -1) {
24
+ pairs.push({ left: token, right: token, });
25
+ continue;
26
+ }
27
+ const left = token.slice(0, eq).trim();
28
+ const right = token.slice(eq + 1).trim();
29
+ if (!left || !right) {
30
+ throw new ClientValidationError(`${optionName} values must use COL or LEFT=RIGHT.`, "validation_failed");
31
+ }
32
+ pairs.push({ left, right, });
33
+ }
34
+ return pairs;
35
+ }
36
+ export function normalizeFuzzyDistance(value) {
37
+ const normalized = typeof value === "string" && value.trim().length > 0
38
+ ? value.trim().toUpperCase()
39
+ : "DAMERAU_LEVENSHTEIN";
40
+ if (normalized === "DAMERAU_LEVENSHTEIN")
41
+ return "LEVENSHTEIN";
42
+ if (normalized === "HAMMING" || normalized === "JACCARD" || normalized === "COSINE"
43
+ || normalized === "EUCLIDEAN") {
44
+ return normalized;
45
+ }
46
+ throw new ClientValidationError("fuzzyDistance must be one of DAMERAU_LEVENSHTEIN, HAMMING, JACCARD, COSINE, or EUCLIDEAN.", "validation_failed");
47
+ }
48
+ export function normalizeJoinType(value) {
49
+ const normalized = typeof value === "string" && value.trim().length > 0
50
+ ? value.trim().toUpperCase()
51
+ : "LEFT";
52
+ if (normalized === "LEFT" || normalized === "INNER" || normalized === "RIGHT"
53
+ || normalized === "FULL") {
54
+ return normalized;
55
+ }
56
+ throw new ClientValidationError("joinType must be one of LEFT, INNER, RIGHT, or FULL.", "validation_failed");
57
+ }
58
+ export function recipeInputItems(recipe) {
59
+ const inputs = asRecord(recipe.inputs);
60
+ if (!inputs)
61
+ return [];
62
+ const result = [];
63
+ const seen = new Set();
64
+ for (const [role, roleValue,] of Object.entries(inputs)) {
65
+ const items = asRecord(roleValue)?.items;
66
+ if (!Array.isArray(items))
67
+ continue;
68
+ for (const itemValue of items) {
69
+ const item = asRecord(itemValue);
70
+ const ref = asString(item?.ref);
71
+ if (!ref || seen.has(ref))
72
+ continue;
73
+ seen.add(ref);
74
+ result.push({ ref, role, });
75
+ }
76
+ }
77
+ return result;
78
+ }
79
+ /**
80
+ * Recipe types whose output schema DSS refuses to compute ("Output schema can't
81
+ * be automatically computed on 'python' recipes"): the code sets the schema
82
+ * when it runs. Plugin recipes (`CustomCode_*`) are code too.
83
+ */
84
+ const CODE_RECIPE_TYPES = {
85
+ python: true,
86
+ r: true,
87
+ julia: true,
88
+ pyspark: true,
89
+ sparkr: true,
90
+ spark_scala: true,
91
+ shell: true,
92
+ sql_script: true,
93
+ };
94
+ /** Whether DSS can compute this recipe type's output schema (visual and SQL query recipes). */
95
+ export function recipeOutputSchemaIsComputable(recipeType) {
96
+ const normalized = recipeType.trim().toLowerCase();
97
+ return CODE_RECIPE_TYPES[normalized] !== true && !normalized.startsWith("customcode_");
98
+ }
99
+ /** The id DSS expects in actions/updateOutputSchema (dataikuapi RequiredSchemaUpdates.apply). */
100
+ export function schemaUpdateComputableId(computable) {
101
+ return (computable.type === "DATASET" ? computable.datasetName : computable.id) ?? "";
102
+ }
103
+ /** A computed schema is worth writing when it has columns and differs from (or fills) the current one. */
104
+ export function schemaUpdateIsPending(computable) {
105
+ const columns = computable.newSchema?.columns ?? [];
106
+ if (columns.length === 0)
107
+ return false;
108
+ return computable.previousSchemaWasEmpty === true
109
+ || (computable.incompatibilities?.length ?? 0) > 0;
110
+ }
111
+ /** DSS grouping aggregate flags on each `values[]` entry, by lowercase name. */
112
+ const GROUPING_AGGREGATES = {
113
+ count: "count",
114
+ countdistinct: "countDistinct",
115
+ sum: "sum",
116
+ avg: "avg",
117
+ min: "min",
118
+ max: "max",
119
+ median: "median",
120
+ stddev: "stddev",
121
+ first: "first",
122
+ last: "last",
123
+ concat: "concat",
124
+ concatdistinct: "concatDistinct",
125
+ sum2: "sum2",
126
+ };
127
+ function splitPair(raw, flag) {
128
+ const eq = raw.indexOf("=");
129
+ const left = eq === -1 ? "" : raw.slice(0, eq).trim();
130
+ const right = eq === -1 ? "" : raw.slice(eq + 1).trim();
131
+ if (left.length === 0 || right.length === 0) {
132
+ throw new ClientValidationError(`${flag} expects NAME=VALUE, got "${raw}".`, "validation_failed");
133
+ }
134
+ return [left, right,];
135
+ }
136
+ /** Grouping configuration from create options, or undefined when none was asked for. */
137
+ export function groupingPayloadConfig(opts) {
138
+ const keys = opts.groupBy ?? [];
139
+ const specs = opts.aggregate ?? [];
140
+ if (keys.length === 0 && specs.length === 0)
141
+ return undefined;
142
+ const aggregates = {};
143
+ for (const spec of specs) {
144
+ const colon = spec.lastIndexOf(":");
145
+ const column = colon === -1 ? "" : spec.slice(0, colon).trim();
146
+ const fns = colon === -1
147
+ ? []
148
+ : spec.slice(colon + 1).split("+").map((fn) => fn.trim().toLowerCase());
149
+ if (column.length === 0 || fns.length === 0
150
+ || fns.some((fn) => GROUPING_AGGREGATES[fn] === undefined)) {
151
+ throw new ClientValidationError(`--aggregate expects COL:FN[+FN] with FN one of ${Object.values(GROUPING_AGGREGATES).join(", ")}; got "${spec}".`, "validation_failed");
152
+ }
153
+ aggregates[column] = [
154
+ ...(aggregates[column] ?? []),
155
+ ...fns.map((fn) => GROUPING_AGGREGATES[fn]),
156
+ ];
157
+ }
158
+ const ordered = Object.values(aggregates).some((fns) => fns.includes("first") || fns.includes("last"));
159
+ if (ordered && !opts.orderBy) {
160
+ throw new ClientValidationError("first/last aggregates need an ordering column: pass --order-by COL.", "validation_failed");
161
+ }
162
+ return { keys, aggregates, ...(opts.orderBy ? { orderBy: opts.orderBy, } : {}), };
163
+ }
164
+ /**
165
+ * Apply grouping keys and aggregates to the payload DSS created: `keys` lists
166
+ * the group columns; each aggregated column's `values[]` entry gets its
167
+ * aggregate flags (and `orderColumn` for first/last).
168
+ */
169
+ export function applyGroupingPayload(payload, config) {
170
+ const values = Array.isArray(payload.values)
171
+ ? payload.values.map((value) => asRecord(value) ?? {})
172
+ : [];
173
+ for (const [column, fns,] of Object.entries(config.aggregates)) {
174
+ let entry = values.find((value) => value.column === column);
175
+ if (!entry) {
176
+ entry = { column, };
177
+ values.push(entry);
178
+ }
179
+ for (const fn of fns)
180
+ entry[fn] = true;
181
+ if (config.orderBy && (fns.includes("first") || fns.includes("last"))) {
182
+ entry.orderColumn = config.orderBy;
183
+ }
184
+ }
185
+ return { ...payload, keys: config.keys.map((column) => ({ column, })), values, };
186
+ }
187
+ function processorStep(type, params) {
188
+ return {
189
+ metaType: "PROCESSOR",
190
+ type,
191
+ params,
192
+ disabled: false,
193
+ preview: false,
194
+ alwaysShowComment: false,
195
+ };
196
+ }
197
+ /** Prepare option groups, in the default step order. */
198
+ export const PREPARE_STEP_KINDS = [
199
+ "rename",
200
+ "fillEmpty",
201
+ "formula",
202
+ "filter",
203
+ "dropColumns",
204
+ "keepColumns",
205
+ ];
206
+ function isPrepareStepKind(value) {
207
+ return PREPARE_STEP_KINDS.includes(value);
208
+ }
209
+ /**
210
+ * Order the prepare option groups run in: `stepOrder` (the CLI passes the order
211
+ * the flags were typed in) first, then any group it leaves out in the default order.
212
+ */
213
+ function prepareStepOrder(opts) {
214
+ const order = [];
215
+ for (const kind of opts.stepOrder ?? []) {
216
+ if (!isPrepareStepKind(kind)) {
217
+ throw new ClientValidationError(`Unknown stepOrder entry "${kind}"; expected one of ${PREPARE_STEP_KINDS.join(", ")}.`, "invalid_flag_value");
218
+ }
219
+ if (!order.includes(kind))
220
+ order.push(kind);
221
+ }
222
+ for (const kind of PREPARE_STEP_KINDS) {
223
+ if (!order.includes(kind))
224
+ order.push(kind);
225
+ }
226
+ return order;
227
+ }
228
+ /** Prepare steps for one option group (empty when the option was not given). */
229
+ function prepareStepsFor(kind, opts) {
230
+ switch (kind) {
231
+ case "rename": {
232
+ const renamings = (opts.rename ?? []).map((raw) => {
233
+ const [from, to,] = splitPair(raw, "--rename");
234
+ return { from, to, };
235
+ });
236
+ return renamings.length > 0 ? [processorStep("ColumnRenamer", { renamings, }),] : [];
237
+ }
238
+ case "fillEmpty":
239
+ return (opts.fillEmpty ?? []).map((raw) => {
240
+ const [column, value,] = splitPair(raw, "--fill-empty");
241
+ return processorStep("FillEmptyWithValue", {
242
+ appliesTo: "SINGLE_COLUMN",
243
+ columns: [column,],
244
+ value,
245
+ });
246
+ });
247
+ case "formula": {
248
+ if (!opts.formula)
249
+ return [];
250
+ const [column, expression,] = splitPair(opts.formula, "--formula");
251
+ return [processorStep("CreateColumnWithGREL", { column, expression, }),];
252
+ }
253
+ case "filter":
254
+ return opts.filter
255
+ ? [processorStep("FilterOnCustomFormula", { expression: opts.filter, action: "KEEP_ROW", }),]
256
+ : [];
257
+ case "dropColumns":
258
+ return opts.dropColumns?.length
259
+ ? [processorStep("ColumnsSelector", {
260
+ appliesTo: "COLUMNS",
261
+ columns: opts.dropColumns,
262
+ keep: false,
263
+ }),]
264
+ : [];
265
+ case "keepColumns":
266
+ return opts.keepColumns?.length
267
+ ? [processorStep("ColumnsSelector", {
268
+ appliesTo: "COLUMNS",
269
+ columns: opts.keepColumns,
270
+ keep: true,
271
+ }),]
272
+ : [];
273
+ }
274
+ }
275
+ /**
276
+ * Prepare (shaker) steps from create options: rename, fill empty, formula
277
+ * column, row filter, drop columns, keep columns. They run in `stepOrder`
278
+ * (flag order on the CLI), defaulting to that sequence, so a step sees the
279
+ * columns the steps before it produced. Empty when no prepare option was given.
280
+ */
281
+ export function prepareSteps(opts) {
282
+ return prepareStepOrder(opts).flatMap((kind) => prepareStepsFor(kind, opts));
283
+ }
284
+ /**
285
+ * Columns an expression refers to, conservatively: `val('name')` arguments
286
+ * plus bare identifiers outside string literals (not method names, calls, or
287
+ * members of another object).
288
+ */
289
+ function expressionColumnReferences(expression) {
290
+ const references = new Set();
291
+ for (const match of expression.matchAll(/\bval\(\s*(['"])((?:\\.|(?!\1).)*)\1\s*\)/g)) {
292
+ references.add(match[2]);
293
+ }
294
+ const withoutStrings = expression.replace(/(['"])(?:\\.|(?!\1).)*\1/g, " ");
295
+ for (const match of withoutStrings.matchAll(/(?<![\w.$])([A-Za-z_]\w*)(?!\w)(?!\s*\()/g)) {
296
+ references.add(match[1]);
297
+ }
298
+ return references;
299
+ }
300
+ /**
301
+ * Warnings about the prepare options that DSS cannot report itself: a
302
+ * `--formula`/`--filter` that runs after a `--rename` but still names a
303
+ * column that rename removed (it would fail or read an empty column).
304
+ */
305
+ export function recipeCreateWarnings(opts) {
306
+ const warnings = [];
307
+ const removed = new Map();
308
+ for (const kind of prepareStepOrder(opts)) {
309
+ if (kind === "rename") {
310
+ const renamings = (opts.rename ?? []).map((raw) => splitPair(raw, "--rename"));
311
+ const targets = new Set(renamings.map(([, to,]) => to));
312
+ for (const [from, to,] of renamings) {
313
+ if (!targets.has(from))
314
+ removed.set(from, to);
315
+ }
316
+ }
317
+ else if ((kind === "formula" && opts.formula) || (kind === "filter" && opts.filter)) {
318
+ const expression = kind === "formula"
319
+ ? splitPair(opts.formula, "--formula")[1]
320
+ : opts.filter;
321
+ const stale = [...expressionColumnReferences(expression),].filter((name) => removed.has(name));
322
+ if (stale.length === 0)
323
+ continue;
324
+ const flag = kind === "formula" ? "--formula" : "--filter";
325
+ warnings.push({
326
+ code: "recipe_formula_uses_renamed_column",
327
+ flag,
328
+ columns: stale.map((name) => ({ renamed: name, to: removed.get(name), })),
329
+ message: `${flag} runs after --rename and refers to ${stale.map((name) => `"${name}"`).join(", ")}, which the rename removed.`,
330
+ hint: `Use the new name (${stale.map((name) => removed.get(name)).join(", ")}), or put ${flag} before --rename on the command line: steps run in flag order.`,
331
+ });
332
+ }
333
+ }
334
+ return warnings;
335
+ }
336
+ /**
337
+ * Recipe types created from `creationSettings.virtualInputs` (dataikuapi's
338
+ * VirtualInputsSingleOutputRecipeCreator subclasses: join, fuzzyjoin, geojoin,
339
+ * vstack, generate_features). Live DSS 15: without it these are created with
340
+ * no inputs (and `generate_features` fails with a NullPointerException).
341
+ */
342
+ const VIRTUAL_INPUT_RECIPE_TYPES = {
343
+ vstack: true,
344
+ join: true,
345
+ fuzzyjoin: true,
346
+ geojoin: true,
347
+ generate_features: true,
348
+ };
349
+ /**
350
+ * Pure request construction for POST /recipes/ (no DSS calls). Shared with
351
+ * `recipe create --plan` so the plan equals the request the command sends.
352
+ */
353
+ export function buildRecipeCreateRequest(opts, pk) {
354
+ const { payload, outputConnection: rawConnection, joinType: rawJoinType, } = opts;
355
+ // dataikuapi and the UI call the shaker recipe "prepare".
356
+ const type = opts.type === "prepare" ? "shaker" : opts.type;
357
+ const grouping = groupingPayloadConfig(opts);
358
+ const steps = prepareSteps(opts);
359
+ if (grouping && type !== "grouping") {
360
+ throw new ClientValidationError("--group-by/--aggregate/--order-by need --type grouping.", "validation_failed");
361
+ }
362
+ if (steps.length > 0 && type !== "shaker") {
363
+ throw new ClientValidationError("--rename/--fill-empty/--formula/--filter/--drop-columns/--keep-columns need --type prepare.", "validation_failed");
364
+ }
365
+ const outputFolder = asString(opts.outputFolder);
366
+ // Build inputs/outputs from simple form (inputDatasets + outputDataset) or
367
+ // advanced form (inputs + outputs); both may coexist — simple form wins when
368
+ // the advanced form is absent.
369
+ const inputDatasets = asStringArray(opts.inputDatasets);
370
+ const requestedOutputDataset = asString(opts.outputDataset);
371
+ let inputs = asRecord(opts.inputs);
372
+ let outputs = asRecord(opts.outputs);
373
+ if (!inputs) {
374
+ inputs = {
375
+ main: {
376
+ items: inputDatasets?.map((ref) => ({ ref, deps: [], })) ?? [],
377
+ },
378
+ };
379
+ }
380
+ // Auto-generate name if not provided
381
+ const outputNameForDefaultRecipe = requestedOutputDataset ?? outputFolder;
382
+ const name = opts.name ?? (type && outputNameForDefaultRecipe
383
+ ? `${type}_${outputNameForDefaultRecipe}`
384
+ : undefined);
385
+ const temporaryOutputDataset = outputFolder && !requestedOutputDataset && name
386
+ ? `${name}_folder_output_marker`
387
+ : undefined;
388
+ const outputDataset = requestedOutputDataset ?? temporaryOutputDataset;
389
+ if (!outputs && outputDataset) {
390
+ outputs = {
391
+ main: {
392
+ items: [{ ref: outputDataset, appendMode: false, },],
393
+ },
394
+ };
395
+ }
396
+ if (!type || !name || !outputs) {
397
+ throw new ClientValidationError("type and outputDataset/outputFolder or (name + outputs) are required for create.", "validation_failed");
398
+ }
399
+ const joinCols = typeof opts.joinOn === "string" ? [opts.joinOn,] : asStringArray(opts.joinOn);
400
+ const joinKeys = type === "join" && joinCols?.length
401
+ ? parseJoinKeyPairs(joinCols)
402
+ : undefined;
403
+ const fuzzyOn = opts.fuzzyOn ?? (type === "fuzzyjoin" ? opts.joinOn : undefined);
404
+ const fuzzyCols = typeof fuzzyOn === "string" ? [fuzzyOn,] : asStringArray(fuzzyOn);
405
+ const fuzzyKeys = type === "fuzzyjoin" && fuzzyCols?.length
406
+ ? parseJoinKeyPairs(fuzzyCols, "fuzzyOn")
407
+ : undefined;
408
+ const normalizedJoinType = joinKeys?.length || fuzzyKeys?.length
409
+ ? normalizeJoinType(rawJoinType)
410
+ : undefined;
411
+ const fuzzyDistance = fuzzyKeys?.length ? normalizeFuzzyDistance(opts.fuzzyDistance) : undefined;
412
+ const fuzzyThreshold = opts.fuzzyThreshold ?? 1;
413
+ if (!Number.isFinite(fuzzyThreshold)) {
414
+ throw new ClientValidationError("fuzzyThreshold must be a finite number.", "validation_failed");
415
+ }
416
+ // Virtual-input recipes: DSS builds `inputs` from creationSettings.virtualInputs
417
+ // (dataikuapi VirtualInputsSingleOutputRecipeCreator) and ignores or drops the
418
+ // prototype's inputs, creating the recipe with `inputs: {}` and an empty payload.
419
+ const hasVirtualInputs = VIRTUAL_INPUT_RECIPE_TYPES[type] === true;
420
+ const recipePrototype = {
421
+ type,
422
+ name,
423
+ projectKey: pk,
424
+ ...(type === "fuzzyjoin" ? {} : { inputs, }),
425
+ outputs,
426
+ };
427
+ const creationSettings = {};
428
+ if (payload !== undefined) {
429
+ creationSettings.script = payload;
430
+ }
431
+ if (hasVirtualInputs) {
432
+ creationSettings.virtualInputs = recipeInputItems({ inputs, }).map((item) => item.ref);
433
+ }
434
+ return {
435
+ creationSettings,
436
+ fuzzyDistance,
437
+ fuzzyKeys,
438
+ fuzzyThreshold,
439
+ grouping,
440
+ inputDatasets,
441
+ inputs,
442
+ joinKeys,
443
+ name,
444
+ normalizedJoinType,
445
+ outputFolder,
446
+ outputs,
447
+ payload,
448
+ prepareSteps: steps,
449
+ rawConnection,
450
+ recipePrototype,
451
+ temporaryOutputDataset,
452
+ type,
453
+ };
454
+ }
@@ -1,11 +1,6 @@
1
- import type { BuildMode, JobWaitResult, ProjectMetadata, RecipeCreateOptions, RecipeCreateResult, RecipeSummary } from "../schemas.js";
1
+ import type { BuildMode, JobWaitResult, ProjectMetadata, RecipeCreateOptions, RecipeCreateResult, RecipeSchemaUpdates, RecipeSummary, RecipeUpdateSchemaResult } 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
- };
9
4
  export interface RecipeRunOutput extends JobBuildTarget {
10
5
  ref: string;
11
6
  role: string;
@@ -70,28 +65,6 @@ export interface RecipeCloneResult {
70
65
  payloadTextRewrites: Record<string, string>;
71
66
  copiedOutputDatasets: string[];
72
67
  }
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
- };
95
68
  export declare class RecipesResource extends BaseResource {
96
69
  /** List all recipes in a project. */
97
70
  list(projectKey?: string): Promise<RecipeSummary[]>;
@@ -134,6 +107,33 @@ export declare class RecipesResource extends BaseResource {
134
107
  run(recipeName: string, opts?: RecipeRunOptions): Promise<RecipeRunResult>;
135
108
  /** Create a recipe, with optional output dataset provisioning and join configuration. */
136
109
  create(opts: RecipeCreateOptions): Promise<RecipeCreateResult>;
110
+ /**
111
+ * Compute the output schemas DSS derives from the recipe's current settings
112
+ * (GET /recipes/{name}/schema-update, dataikuapi `compute_schema_updates`).
113
+ * DSS rejects code recipes, whose code sets the schema at run time.
114
+ */
115
+ computeSchemaUpdates(recipeName: string, projectKey?: string): Promise<RecipeSchemaUpdates>;
116
+ /**
117
+ * Write each pending computed schema accepted by `select` through
118
+ * actions/updateOutputSchema. Pending outputs `select` rejects are reported,
119
+ * not written.
120
+ */
121
+ private applySchemaUpdates;
122
+ /**
123
+ * Replace each output schema that differs from the DSS-computed one
124
+ * (dataikuapi `compute_schema_updates().apply()`, the UI's "Update schema").
125
+ * With `derivedOutputs`, only outputs whose schema is empty or listed there
126
+ * are written: callers pass the outputs whose schema matched the computed
127
+ * one before an edit, so DSS-derived schemas follow the edit while
128
+ * hand-edited ones are left alone. `dryRun` writes nothing. Skipped
129
+ * differing outputs are listed under `pending`. Code recipes are rejected
130
+ * before the schema computation: DSS cannot compute their output schema.
131
+ */
132
+ updateSchema(recipeName: string, opts?: {
133
+ projectKey?: string;
134
+ derivedOutputs?: string[];
135
+ dryRun?: boolean;
136
+ }): Promise<RecipeUpdateSchemaResult>;
137
137
  /**
138
138
  * Update a recipe by merging the patch into the current definition.
139
139
  * The `recipe` sub-object is deep-merged to preserve nested fields.
@@ -171,4 +171,3 @@ export declare class RecipesResource extends BaseResource {
171
171
  projectKey?: string;
172
172
  }): Promise<string>;
173
173
  }
174
- export {};