dataiku-sdk 3.3.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.
Files changed (126) hide show
  1. package/README.md +6 -4
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/src/cli/command-syntax.json +14524 -0
  4. package/dist/src/cli/commands/analysis.js +6 -9
  5. package/dist/src/cli/commands/api-deployer.js +15 -33
  6. package/dist/src/cli/commands/api-service.js +17 -27
  7. package/dist/src/cli/commands/app.js +22 -39
  8. package/dist/src/cli/commands/bundle.js +15 -24
  9. package/dist/src/cli/commands/business-app.js +10 -17
  10. package/dist/src/cli/commands/code-env.js +23 -36
  11. package/dist/src/cli/commands/code.js +5 -5
  12. package/dist/src/cli/commands/connection.js +12 -22
  13. package/dist/src/cli/commands/continuous-activity.js +6 -9
  14. package/dist/src/cli/commands/dashboard.js +8 -13
  15. package/dist/src/cli/commands/data-collection.js +9 -16
  16. package/dist/src/cli/commands/data-quality.js +14 -26
  17. package/dist/src/cli/commands/dataset.js +28 -51
  18. package/dist/src/cli/commands/discussion.js +7 -10
  19. package/dist/src/cli/commands/doctor.js +3 -3
  20. package/dist/src/cli/commands/flow-zone.js +8 -17
  21. package/dist/src/cli/commands/folder.js +12 -20
  22. package/dist/src/cli/commands/future.js +7 -10
  23. package/dist/src/cli/commands/group.js +7 -11
  24. package/dist/src/cli/commands/insight.js +7 -11
  25. package/dist/src/cli/commands/job.js +17 -25
  26. package/dist/src/cli/commands/knowledge-bank.js +5 -6
  27. package/dist/src/cli/commands/llm.js +5 -8
  28. package/dist/src/cli/commands/macro.js +11 -18
  29. package/dist/src/cli/commands/meaning.js +7 -11
  30. package/dist/src/cli/commands/metrics.js +7 -10
  31. package/dist/src/cli/commands/ml-task.js +12 -20
  32. package/dist/src/cli/commands/model-evaluation-store.js +7 -11
  33. package/dist/src/cli/commands/notebook.js +15 -27
  34. package/dist/src/cli/commands/plugin.js +35 -68
  35. package/dist/src/cli/commands/project-deployer.js +10 -22
  36. package/dist/src/cli/commands/project-folder.js +10 -17
  37. package/dist/src/cli/commands/project-git.js +39 -71
  38. package/dist/src/cli/commands/project-library.js +15 -23
  39. package/dist/src/cli/commands/project.js +9 -26
  40. package/dist/src/cli/commands/recipe.d.ts +16 -0
  41. package/dist/src/cli/commands/recipe.js +83 -94
  42. package/dist/src/cli/commands/saved-model.js +23 -39
  43. package/dist/src/cli/commands/scenario.js +21 -35
  44. package/dist/src/cli/commands/sql.js +4 -4
  45. package/dist/src/cli/commands/statistics.js +11 -18
  46. package/dist/src/cli/commands/streaming-endpoint.js +7 -11
  47. package/dist/src/cli/commands/user.js +11 -22
  48. package/dist/src/cli/commands/variable.js +3 -4
  49. package/dist/src/cli/commands/webapp.js +10 -16
  50. package/dist/src/cli/commands/wiki.js +9 -13
  51. package/dist/src/cli/commands/workspace.js +8 -14
  52. package/dist/src/cli/contract.d.ts +5 -15
  53. package/dist/src/cli/contract.js +179 -461
  54. package/dist/src/cli/doctor.js +1 -0
  55. package/dist/src/cli/env.d.ts +2 -0
  56. package/dist/src/cli/env.js +15 -1
  57. package/dist/src/cli/helpers/app-permissions.js +2 -1
  58. package/dist/src/cli/helpers/flow-zone.js +2 -1
  59. package/dist/src/cli/helpers/recipe.js +2 -1
  60. package/dist/src/cli/output-schemas.d.ts +24 -0
  61. package/dist/src/cli/output-schemas.js +233 -0
  62. package/dist/src/cli/syntax.d.ts +83 -0
  63. package/dist/src/cli/syntax.js +217 -0
  64. package/dist/src/cli/types.d.ts +3 -0
  65. package/dist/src/cli/usage.d.ts +1 -1
  66. package/dist/src/cli/usage.js +4 -2
  67. package/dist/src/cli/version.js +2 -0
  68. package/dist/src/cli.js +37 -64
  69. package/dist/src/client.d.ts +10 -1
  70. package/dist/src/client.js +27 -11
  71. package/dist/src/errors.d.ts +12 -1
  72. package/dist/src/errors.js +34 -7
  73. package/dist/src/generated/action-output-schemas.json +1735 -0
  74. package/dist/src/index.d.ts +1 -3
  75. package/dist/src/index.js +2 -2
  76. package/dist/src/resources/api-services.js +2 -3
  77. package/dist/src/resources/applications.js +2 -4
  78. package/dist/src/resources/code-envs.js +2 -2
  79. package/dist/src/resources/data-quality.js +4 -3
  80. package/dist/src/resources/datasets.d.ts +13 -0
  81. package/dist/src/resources/datasets.js +42 -8
  82. package/dist/src/resources/flow-zones.d.ts +6 -2
  83. package/dist/src/resources/flow-zones.js +10 -5
  84. package/dist/src/resources/folders.js +4 -2
  85. package/dist/src/resources/futures.d.ts +2 -0
  86. package/dist/src/resources/futures.js +39 -27
  87. package/dist/src/resources/insights.js +3 -2
  88. package/dist/src/resources/jobs.js +41 -31
  89. package/dist/src/resources/macros.js +2 -4
  90. package/dist/src/resources/ml-tasks.js +28 -19
  91. package/dist/src/resources/notebooks.js +2 -3
  92. package/dist/src/resources/plugins.d.ts +0 -2
  93. package/dist/src/resources/plugins.js +0 -3
  94. package/dist/src/resources/project-git.d.ts +6 -12
  95. package/dist/src/resources/project-git.js +31 -58
  96. package/dist/src/resources/project-library.d.ts +0 -2
  97. package/dist/src/resources/project-library.js +2 -7
  98. package/dist/src/resources/projects.d.ts +4 -2
  99. package/dist/src/resources/projects.js +37 -36
  100. package/dist/src/resources/recipes.d.ts +28 -0
  101. package/dist/src/resources/recipes.js +116 -84
  102. package/dist/src/resources/sql.js +8 -3
  103. package/dist/src/resources/statistics.js +2 -1
  104. package/dist/src/resources/variables.js +2 -1
  105. package/dist/src/resources/webapps.js +2 -3
  106. package/dist/src/schemas.d.ts +1 -2
  107. package/dist/src/schemas.js +1 -1
  108. package/dist/src/skill.js +6 -7
  109. package/dist/src/utils/cleanup-ledger.js +9 -8
  110. package/dist/src/utils/git-reference.d.ts +12 -0
  111. package/dist/src/utils/git-reference.js +38 -0
  112. package/dist/src/utils/polling.d.ts +3 -0
  113. package/dist/src/utils/polling.js +3 -0
  114. package/dist/src/utils/project-archive.js +2 -2
  115. package/dist/src/utils/stable-hash.d.ts +3 -1
  116. package/dist/src/utils/stable-hash.js +2 -0
  117. package/package.json +8 -8
  118. package/plugin.json +1 -1
  119. package/skills/dataiku-dss/SKILL.md +3 -2
  120. package/skills/dataiku-dss/references/app-releases.md +2 -3
  121. package/skills/dataiku-dss/references/authentication.md +7 -7
  122. package/skills/dataiku-dss/references/coding.md +1 -1
  123. package/skills/dataiku-dss/references/mutations.md +1 -1
  124. package/skills/dataiku-dss/references/troubleshooting.md +2 -2
  125. package/dist/src/utils/pagination.d.ts +0 -8
  126. package/dist/src/utils/pagination.js +0 -23
@@ -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
- const parsed = JSON.parse(payload);
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 Error(`${optionName} values must use COL or LEFT=RIGHT.`);
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 Error("fuzzyDistance must be one of DAMERAU_LEVENSHTEIN, HAMMING, JACCARD, COSINE, or EUCLIDEAN.");
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 Error("joinType must be one of LEFT, INNER, RIGHT, or FULL.");
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 Error(`Unsafe SQL rewrite target for ${from}: ${to}`);
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 Error(`Recipe "${recipeName}" has no output items to build.`);
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 Error(`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.`);
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 Error(`Recipe "${recipeName}" output "${item.ref}" was not found as a dataset or managed folder in project "${pk}".`);
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 { type, payload, outputConnection: rawConnection, joinType: rawJoinType, } = opts;
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 new Error('DSS create-recipe response did not include the final recipe name (documented body: {"name": ...}).');
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 new Error('DSS create-recipe response did not include the final recipe name (documented body: {"name": ...}).', { cause: error, });
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 Error(`Recipe "${recipeName}" was not found or returned an empty definition.`);
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 Error(`Recipe fields ${misplacedRecipeFields.join(", ")} must be nested under "recipe". Example: {"recipe":{"outputs":{...},"params":{...}}}`);
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 Error(`Recipe "${sourceName}" has ${outputs.length} dataset outputs; pass explicit outputRewrites instead of outputDataset.`);
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 Error("Cannot reuse --path or --metastore-table for multiple cloned output datasets; pass per-output settings in a separate step.");
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 Error(`Recipe "${recipeName}" has no code payload.`);
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 Error(`Recipe "${recipeName}" has no code payload.`);
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
- return JSON.parse(text);
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 Error(`SQL query ${queryId} failed: ${text}`);
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 Error("statistics.runWorksheet requires worksheet.rootCard");
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 Error("At least one of standard or local must be provided");
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 = {
@@ -1,10 +1,9 @@
1
1
  import { ClientValidationError, } from "../errors.js";
2
2
  import { deepMerge, } from "../utils/deep-merge.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
  function assertExpectedHash(id, expectHash, current, projectKey) {
7
- if (!EXPECT_HASH_PATTERN.test(expectHash)) {
6
+ if (!SHA256_HEX_PATTERN.test(expectHash)) {
8
7
  throw new ClientValidationError("Expected webapp hash must be a 64-character SHA-256 hex digest.", "validation_failed", "Use the hash value returned by a previous read.", { projectKey, id, });
9
8
  }
10
9
  const actual = stableHash(current);
@@ -3,5 +3,4 @@
3
3
  * The types package (packages/types/) owns the TypeBox schema definitions.
4
4
  * SDK consumers get everything through this re-export.
5
5
  */
6
- export { BuildModeSchema, CodeEnvActionResultSchema, CodeEnvCreateOptionsSchema, CodeEnvDetailsSchema, CodeEnvGetLogOptionsSchema, CodeEnvLogResultSchema, CodeEnvLogSummaryArraySchema, CodeEnvLogSummarySchema, CodeEnvPackageListSchema, CodeEnvSetPackagesOptionsSchema, CodeEnvSummaryArraySchema, CodeEnvSummarySchema, CodeEnvUpdateImagesOptionsSchema, CodeEnvUpdatePackagesOptionsSchema, CodeEnvUsageArraySchema, CodeEnvVersionForProjectSchema, CodeEnvWaitOptionsSchema, ConnectionSummarySchema, DashboardDetailsSchema, DashboardSummaryArraySchema, DashboardSummarySchema, DataQualityComputeResultSchema, DataQualityProjectStatusSchema, DataQualityRuleArraySchema, DataQualityRuleResultArraySchema, DataQualityRuleResultSchema, DataQualityRuleSchema, DataQualityRulesSchema, DataQualityStatusByPartitionSchema, DataQualityStatusSchema, DataQualityTimelineEntrySchema, DataQualityTimelineSchema, DatasetCreateOptionsSchema, DatasetDetailsSchema, DatasetSchemaSchema, DatasetSummaryArraySchema, DatasetSummarySchema, FlowMapOptionsSchema, FlowZoneArraySchema, FlowZoneCreateOptionsSchema, FlowZoneItemSchema, FlowZoneObjectTypeSchema, FlowZonePositionSchema, FlowZoneSchema, FlowZoneUpdateOptionsSchema, FolderCreateOptionsSchema, FolderDetailsSchema, FolderItemArraySchema, FolderItemSchema, FolderSummaryArraySchema, FolderSummarySchema, FutureStateSchema, FutureWaitResultSchema, InsightDetailsSchema, InsightSummaryArraySchema, InsightSummarySchema, JobSummaryArraySchema, JobSummarySchema, JobWaitResultSchema, JupyterCellSchema, JupyterNotebookContentSchema, JupyterNotebookSummaryArraySchema, JupyterNotebookSummarySchema, MacroDefinitionSchema, MacroRunStartSchema, MacroStateSchema, MacroSummaryArraySchema, MacroSummarySchema, MacroWaitResultSchema, NotebookSessionArraySchema, NotebookSessionSchema, parseSchema, ProjectDetailsSchema, ProjectGitActionResultSchema, ProjectGitDiffResultSchema, ProjectGitFutureResponseSchema, ProjectGitFutureStateSchema, ProjectGitLibrariesSchema, ProjectGitLibrarySchema, ProjectGitLogResultSchema, ProjectGitRemoteSchema, ProjectGitStatusSchema, ProjectGitTagSchema, ProjectGitTagsSchema, ProjectMetadataSchema, ProjectSummaryArraySchema, ProjectSummarySchema, ProjectTagsSchema, ProjectVariablesSchema, RecipeCreateOptionsSchema, RecipeCreateResultSchema, RecipeDetailsSchema, RecipeSummaryArraySchema, RecipeSummarySchema, safeParseSchema, ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioStepRunSchema, ScenarioSummaryArraySchema, ScenarioSummarySchema, ScenarioWaitResultSchema, SqlNotebookCellSchema, SqlNotebookContentSchema, SqlNotebookHistorySchema, SqlNotebookSummaryArraySchema, SqlNotebookSummarySchema, SqlQueryResponseSchema, SqlQueryResultSchema, SqlQuerySchemaSchema, WikiArticleDataArraySchema, WikiArticleDataSchema, WikiArticleMetadataSchema, WikiSettingsSchema, WikiTaxonomyNodeSchema, } from "../packages/types/src/index.js";
7
- export type { BuildMode, CodeEnvActionResult, CodeEnvCreateOptions, CodeEnvDetails, CodeEnvGetLogOptions, CodeEnvLogResult, CodeEnvLogSummary, CodeEnvPackageList, CodeEnvSetPackagesOptions, CodeEnvSummary, CodeEnvUpdateImagesOptions, CodeEnvUpdatePackagesOptions, CodeEnvUsage, CodeEnvVersionForProject, CodeEnvWaitOptions, ConnectionSummary, DashboardDetails, DashboardSummary, DataQualityComputeResult, DataQualityProjectStatus, DataQualityRule, DataQualityRuleResult, DataQualityRules, DataQualityStatus, DataQualityStatusByPartition, DataQualityTimeline, DataQualityTimelineEntry, DatasetCreateOptions, DatasetDetails, DatasetSchema, DatasetSummary, FlowMapOptions, FlowZone, FlowZoneCreateOptions, FlowZoneItem, FlowZoneObjectType, FlowZonePosition, FlowZoneUpdateOptions, FolderCreateOptions, FolderDetails, FolderItem, FolderSummary, FutureState, FutureWaitResult, InsightDetails, InsightSummary, JobSummary, JobWaitResult, JupyterCell, JupyterNotebookContent, JupyterNotebookSummary, MacroDefinition, MacroRunStart, MacroState, MacroSummary, MacroSummaryArray, MacroWaitResult, NotebookSession, ProjectDetails, ProjectGitActionResult, ProjectGitDiffResult, ProjectGitFutureResponse, ProjectGitFutureState, ProjectGitLibraries, ProjectGitLibrary, ProjectGitLogResult, ProjectGitRemote, ProjectGitStatus, ProjectGitTag, ProjectGitTags, ProjectMetadata, ProjectSummary, ProjectTags, ProjectVariables, RecipeCreateOptions, RecipeCreateResult, RecipeDetails, RecipeSummary, SafeParseResult, ScenarioDetails, ScenarioStatus, ScenarioStepRun, ScenarioSummary, ScenarioWaitResult, SqlNotebookCell, SqlNotebookContent, SqlNotebookHistory, SqlNotebookSummary, SqlQueryResponse, SqlQueryResult, SqlQuerySchema, WikiArticleData, WikiArticleMetadata, WikiSettings, WikiTaxonomyNode, } from "../packages/types/src/index.js";
6
+ export * from "../packages/types/src/index.js";
@@ -3,4 +3,4 @@
3
3
  * The types package (packages/types/) owns the TypeBox schema definitions.
4
4
  * SDK consumers get everything through this re-export.
5
5
  */
6
- export { BuildModeSchema, CodeEnvActionResultSchema, CodeEnvCreateOptionsSchema, CodeEnvDetailsSchema, CodeEnvGetLogOptionsSchema, CodeEnvLogResultSchema, CodeEnvLogSummaryArraySchema, CodeEnvLogSummarySchema, CodeEnvPackageListSchema, CodeEnvSetPackagesOptionsSchema, CodeEnvSummaryArraySchema, CodeEnvSummarySchema, CodeEnvUpdateImagesOptionsSchema, CodeEnvUpdatePackagesOptionsSchema, CodeEnvUsageArraySchema, CodeEnvVersionForProjectSchema, CodeEnvWaitOptionsSchema, ConnectionSummarySchema, DashboardDetailsSchema, DashboardSummaryArraySchema, DashboardSummarySchema, DataQualityComputeResultSchema, DataQualityProjectStatusSchema, DataQualityRuleArraySchema, DataQualityRuleResultArraySchema, DataQualityRuleResultSchema, DataQualityRuleSchema, DataQualityRulesSchema, DataQualityStatusByPartitionSchema, DataQualityStatusSchema, DataQualityTimelineEntrySchema, DataQualityTimelineSchema, DatasetCreateOptionsSchema, DatasetDetailsSchema, DatasetSchemaSchema, DatasetSummaryArraySchema, DatasetSummarySchema, FlowMapOptionsSchema, FlowZoneArraySchema, FlowZoneCreateOptionsSchema, FlowZoneItemSchema, FlowZoneObjectTypeSchema, FlowZonePositionSchema, FlowZoneSchema, FlowZoneUpdateOptionsSchema, FolderCreateOptionsSchema, FolderDetailsSchema, FolderItemArraySchema, FolderItemSchema, FolderSummaryArraySchema, FolderSummarySchema, FutureStateSchema, FutureWaitResultSchema, InsightDetailsSchema, InsightSummaryArraySchema, InsightSummarySchema, JobSummaryArraySchema, JobSummarySchema, JobWaitResultSchema, JupyterCellSchema, JupyterNotebookContentSchema, JupyterNotebookSummaryArraySchema, JupyterNotebookSummarySchema, MacroDefinitionSchema, MacroRunStartSchema, MacroStateSchema, MacroSummaryArraySchema, MacroSummarySchema, MacroWaitResultSchema, NotebookSessionArraySchema, NotebookSessionSchema, parseSchema, ProjectDetailsSchema, ProjectGitActionResultSchema, ProjectGitDiffResultSchema, ProjectGitFutureResponseSchema, ProjectGitFutureStateSchema, ProjectGitLibrariesSchema, ProjectGitLibrarySchema, ProjectGitLogResultSchema, ProjectGitRemoteSchema, ProjectGitStatusSchema, ProjectGitTagSchema, ProjectGitTagsSchema, ProjectMetadataSchema, ProjectSummaryArraySchema, ProjectSummarySchema, ProjectTagsSchema, ProjectVariablesSchema, RecipeCreateOptionsSchema, RecipeCreateResultSchema, RecipeDetailsSchema, RecipeSummaryArraySchema, RecipeSummarySchema, safeParseSchema, ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioStepRunSchema, ScenarioSummaryArraySchema, ScenarioSummarySchema, ScenarioWaitResultSchema, SqlNotebookCellSchema, SqlNotebookContentSchema, SqlNotebookHistorySchema, SqlNotebookSummaryArraySchema, SqlNotebookSummarySchema, SqlQueryResponseSchema, SqlQueryResultSchema, SqlQuerySchemaSchema, WikiArticleDataArraySchema, WikiArticleDataSchema, WikiArticleMetadataSchema, WikiSettingsSchema, WikiTaxonomyNodeSchema, } from "../packages/types/src/index.js";
6
+ export * from "../packages/types/src/index.js";
package/dist/src/skill.js CHANGED
@@ -1,8 +1,9 @@
1
- import { createHash, } from "node:crypto";
2
1
  import * as fs from "node:fs";
3
2
  import * as os from "node:os";
4
3
  import * as path from "node:path";
5
4
  import { fileURLToPath, } from "node:url";
5
+ import { ClientValidationError, } from "./errors.js";
6
+ import { sha256Hex, } from "./utils/stable-hash.js";
6
7
  const SKILL_URLS = [
7
8
  new URL("../skills/dataiku-dss/SKILL.md", import.meta.url),
8
9
  new URL("../../skills/dataiku-dss/SKILL.md", import.meta.url),
@@ -12,7 +13,7 @@ function skillDirectory() {
12
13
  if (fs.existsSync(skillUrl))
13
14
  return new URL("./", skillUrl);
14
15
  }
15
- throw new Error(`Bundled Dataiku skill not found. Checked: ${SKILL_URLS.map((url) => url.pathname).join(", ")}`);
16
+ throw new ClientValidationError(`Bundled Dataiku skill not found. Checked: ${SKILL_URLS.map((url) => url.pathname).join(", ")}`, "internal_error");
16
17
  }
17
18
  function skillContent() {
18
19
  return fs.readFileSync(new URL("SKILL.md", skillDirectory()), "utf-8");
@@ -102,9 +103,6 @@ export function findWorkspaceRoot(startDir) {
102
103
  }
103
104
  return startDir;
104
105
  }
105
- function sha256Hex(value) {
106
- return createHash("sha256").update(value).digest("hex");
107
- }
108
106
  function skillState(target, expectedSha256) {
109
107
  if (!fs.existsSync(target))
110
108
  return { status: "missing", };
@@ -127,8 +125,9 @@ function skillFiles() {
127
125
  else if (entry.isFile()) {
128
126
  files.set(relativePath, fs.readFileSync(path.join(root, relativePath)));
129
127
  }
130
- else
131
- throw new Error(`Unsupported bundled skill entry: ${relativePath}`);
128
+ else {
129
+ throw new ClientValidationError(`Unsupported bundled skill entry: ${relativePath}`, "internal_error");
130
+ }
132
131
  }
133
132
  }
134
133
  visit("");
@@ -1,5 +1,6 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
+ import { ClientValidationError, } from "../errors.js";
3
4
  import { canonicalDssUrl, } from "./dss-url.js";
4
5
  function isCleanupLedgerEntry(value) {
5
6
  if (!value || typeof value !== "object")
@@ -35,11 +36,11 @@ export async function preflightCleanupLedgerPath(filePath) {
35
36
  }
36
37
  catch (error) {
37
38
  if (error.code !== "ENOENT") {
38
- throw new Error(`Could not preflight cleanup ledger ${resolved}: ${error instanceof Error ? error.message : String(error)}`, { cause: error, });
39
+ throw new ClientValidationError(`Could not preflight cleanup ledger ${resolved}: ${error instanceof Error ? error.message : String(error)}`, "validation_failed", undefined, undefined, { cause: error, });
39
40
  }
40
41
  }
41
42
  if (stats?.isDirectory()) {
42
- throw new Error(`Cleanup ledger path is a directory, not a file: ${resolved}`);
43
+ throw new ClientValidationError(`Cleanup ledger path is a directory, not a file: ${resolved}`, "validation_failed");
43
44
  }
44
45
  if (stats !== undefined) {
45
46
  try {
@@ -48,9 +49,9 @@ export async function preflightCleanupLedgerPath(filePath) {
48
49
  }
49
50
  catch (error) {
50
51
  const code = error.code;
51
- throw new Error(code === "EACCES" || code === "EPERM"
52
+ throw new ClientValidationError(code === "EACCES" || code === "EPERM"
52
53
  ? `Cleanup ledger file is not writable: ${resolved}`
53
- : `Could not preflight cleanup ledger ${resolved}: ${error instanceof Error ? error.message : String(error)}`, { cause: error, });
54
+ : `Could not preflight cleanup ledger ${resolved}: ${error instanceof Error ? error.message : String(error)}`, "validation_failed", undefined, undefined, { cause: error, });
54
55
  }
55
56
  return;
56
57
  }
@@ -60,7 +61,7 @@ export async function preflightCleanupLedgerPath(filePath) {
60
61
  await fs.rm(probe, { recursive: true, force: true, });
61
62
  }
62
63
  catch (error) {
63
- throw new Error(`Cleanup ledger directory is not writable: ${parent}${error instanceof Error ? ` (${error.message})` : ""}`, { cause: error, });
64
+ throw new ClientValidationError(`Cleanup ledger directory is not writable: ${parent}${error instanceof Error ? ` (${error.message})` : ""}`, "validation_failed", undefined, undefined, { cause: error, });
64
65
  }
65
66
  }
66
67
  /**
@@ -94,10 +95,10 @@ export async function reserveCleanupLedgerDssUrl(filePath, dssUrl) {
94
95
  found = canonicalDssUrl(raw.trim());
95
96
  }
96
97
  catch (error) {
97
- throw new Error("Cleanup ledger DSS binding is invalid.", { cause: error, });
98
+ throw new ClientValidationError("Cleanup ledger DSS binding is invalid.", "validation_failed", undefined, undefined, { cause: error, });
98
99
  }
99
100
  if (found !== expected) {
100
- throw new Error("Cleanup ledger is reserved for a different DSS server.");
101
+ throw new ClientValidationError("Cleanup ledger is reserved for a different DSS server.", "validation_failed");
101
102
  }
102
103
  }
103
104
  /**
@@ -140,7 +141,7 @@ export async function readCleanupLedger(filePath) {
140
141
  .map((line, index) => {
141
142
  const parsed = JSON.parse(line);
142
143
  if (!isCleanupLedgerEntry(parsed)) {
143
- throw new Error(`Invalid cleanup ledger entry at line ${index + 1}`);
144
+ throw new ClientValidationError(`Invalid cleanup ledger entry at line ${index + 1}`, "validation_failed");
144
145
  }
145
146
  return parsed;
146
147
  });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Validate and canonicalize a Git library reference path. This is the single
3
+ * validator used by both SDK dispatch and CLI mutation planning.
4
+ *
5
+ * Strip only the permitted outer '/' separators, then require nonempty
6
+ * ordinary segments: no empty segments and no '.' or '..' segments. A hostile
7
+ * path therefore cannot change which API resource a dispatcher's URL
8
+ * normalization would otherwise select.
9
+ */
10
+ export declare function validateGitReferencePath(path: string): string;
11
+ /** Validate a Git library reference path and URL-encode it per segment. */
12
+ export declare function encodeGitReferencePath(path: string): string;
@@ -0,0 +1,38 @@
1
+ import { ClientValidationError, } from "../errors.js";
2
+ function gitReferencePathError(path, message) {
3
+ return new ClientValidationError(message, "validation_failed", "Use a Git library reference path relative to the project library root: '/' -separated segment names, with no '.' or '..' segments.", { path, });
4
+ }
5
+ /**
6
+ * Validate and canonicalize a Git library reference path. This is the single
7
+ * validator used by both SDK dispatch and CLI mutation planning.
8
+ *
9
+ * Strip only the permitted outer '/' separators, then require nonempty
10
+ * ordinary segments: no empty segments and no '.' or '..' segments. A hostile
11
+ * path therefore cannot change which API resource a dispatcher's URL
12
+ * normalization would otherwise select.
13
+ */
14
+ export function validateGitReferencePath(path) {
15
+ if (typeof path !== "string" || path.trim() === "") {
16
+ throw gitReferencePathError(path, "A Git library reference path is required.");
17
+ }
18
+ const normalized = path.replace(/^\/+/, "").replace(/\/+$/, "");
19
+ if (normalized === "") {
20
+ throw gitReferencePathError(path, "A Git library reference path is required.");
21
+ }
22
+ for (const segment of normalized.split("/")) {
23
+ if (segment === "") {
24
+ throw gitReferencePathError(path, "A Git library reference path must not contain empty segments (consecutive or repeated '/').");
25
+ }
26
+ if (segment === "." || segment === "..") {
27
+ throw gitReferencePathError(path, "A Git library reference path must not contain '.' or '..' segments.");
28
+ }
29
+ }
30
+ return normalized;
31
+ }
32
+ /** Validate a Git library reference path and URL-encode it per segment. */
33
+ export function encodeGitReferencePath(path) {
34
+ return validateGitReferencePath(path)
35
+ .split("/")
36
+ .map((segment) => encodeURIComponent(segment))
37
+ .join("/");
38
+ }
@@ -1,3 +1,6 @@
1
+ /** Default fixed poll interval and overall wait budget for long-running DSS operations. */
2
+ export declare const DEFAULT_POLL_INTERVAL_MS = 2000;
3
+ export declare const DEFAULT_TIMEOUT_MS = 120000;
1
4
  /**
2
5
  * Shared polling helpers used by every long-running wait loop (jobs, futures,
3
6
  * scenarios, macros, Visual ML training, Project Git futures).
@@ -1,3 +1,6 @@
1
+ /** Default fixed poll interval and overall wait budget for long-running DSS operations. */
2
+ export const DEFAULT_POLL_INTERVAL_MS = 2_000;
3
+ export const DEFAULT_TIMEOUT_MS = 120_000;
1
4
  import { DataikuError, } from "../errors.js";
2
5
  /**
3
6
  * Shared polling helpers used by every long-running wait loop (jobs, futures,
@@ -1,6 +1,6 @@
1
1
  import StreamZip from "node-stream-zip";
2
2
  import { stat, } from "node:fs/promises";
3
- import { ClientValidationError, } from "../errors.js";
3
+ import { ClientValidationError, unexpectedResponseError, } from "../errors.js";
4
4
  const MANIFEST_MEMBER = "export-manifest.json";
5
5
  const DATASETS_ROOT = "project_config/datasets";
6
6
  const RECIPES_ROOT = "project_config/recipes";
@@ -144,7 +144,7 @@ function streamMember(zip, entry, accumulate, bufferMaxBytes, limits) {
144
144
  return reject(err);
145
145
  }
146
146
  if (!stm) {
147
- return reject(new Error("archive produced no member stream"));
147
+ return reject(unexpectedResponseError("archive produced no member stream"));
148
148
  }
149
149
  const stream = stm;
150
150
  let bytes = 0;
@@ -1,4 +1,6 @@
1
- export declare function sha256Hex(value: string): string;
1
+ export declare function sha256Hex(value: string | Uint8Array): string;
2
+ /** Lowercase or uppercase SHA-256 hex digest accepted by every `--expect-hash` guard. */
3
+ export declare const SHA256_HEX_PATTERN: RegExp;
2
4
  /** Locale-independent UTF-16 code-unit order for deterministic machine output. */
3
5
  export declare function compareStrings(a: string, b: string): number;
4
6
  /**
@@ -1,6 +1,8 @@
1
1
  export function sha256Hex(value) {
2
2
  return new Bun.CryptoHasher("sha256").update(value).digest("hex");
3
3
  }
4
+ /** Lowercase or uppercase SHA-256 hex digest accepted by every `--expect-hash` guard. */
5
+ export const SHA256_HEX_PATTERN = /^[0-9a-fA-F]{64}$/;
4
6
  /** Locale-independent UTF-16 code-unit order for deterministic machine output. */
5
7
  export function compareStrings(a, b) {
6
8
  return a < b ? -1 : a > b ? 1 : 0;