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
@@ -46,9 +46,7 @@ export { WebappsResource, } from "./resources/webapps.js";
46
46
  export { WikiResource, } from "./resources/wiki.js";
47
47
  export { WorkspacesResource, } from "./resources/workspaces.js";
48
48
  export { computeNextPollDelayMs, } from "./utils/polling.js";
49
- 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 "./schemas.js";
50
- export type { SafeParseResult, } from "./schemas.js";
51
- 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, ScenarioDetails, ScenarioStatus, ScenarioStepRun, ScenarioSummary, ScenarioWaitResult, SqlNotebookCell, SqlNotebookContent, SqlNotebookSummary, SqlQueryResponse, SqlQueryResult, SqlQuerySchema, WikiArticleData, WikiArticleMetadata, WikiSettings, WikiTaxonomyNode, } from "./schemas.js";
49
+ export * from "./schemas.js";
52
50
  export { deepMerge, } from "./utils/deep-merge.js";
53
51
  export { type AnalyzedFlowMap, type AnalyzedFlowNode, analyzeFlowMap, type FlowMapComponent, type FlowMapDiagnostic, type FlowMapRendering, type FlowMapZone, type FlowRenderFormat, flowTopologyFingerprint, renderFlowMap, } from "./utils/flow-analysis.js";
54
52
  export { type NormalizedFlowEdge, type NormalizedFlowMap, type NormalizedFlowNode, normalizeFlowGraph, } from "./utils/flow-map.js";
package/dist/src/index.js CHANGED
@@ -50,8 +50,8 @@ export { WebappsResource, } from "./resources/webapps.js";
50
50
  export { WikiResource, } from "./resources/wiki.js";
51
51
  export { WorkspacesResource, } from "./resources/workspaces.js";
52
52
  export { computeNextPollDelayMs, } from "./utils/polling.js";
53
- // Schemas (TypeBox schema objects for runtime validation)
54
- 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 "./schemas.js";
53
+ // Schemas, derived types, and parse helpers
54
+ export * from "./schemas.js";
55
55
  // Utilities
56
56
  export { deepMerge, } from "./utils/deep-merge.js";
57
57
  export { analyzeFlowMap, flowTopologyFingerprint, renderFlowMap, } from "./utils/flow-analysis.js";
@@ -1,9 +1,8 @@
1
1
  import { ClientValidationError, } from "../errors.js";
2
- import { stableHash, } from "../utils/stable-hash.js";
2
+ import { SHA256_HEX_PATTERN, stableHash, } from "../utils/stable-hash.js";
3
3
  import { BaseResource, } from "./base.js";
4
- const EXPECT_HASH_PATTERN = /^[0-9a-fA-F]{64}$/;
5
4
  function assertExpectedSettingsHash(serviceId, expectHash, current, projectKey) {
6
- if (!EXPECT_HASH_PATTERN.test(expectHash)) {
5
+ if (!SHA256_HEX_PATTERN.test(expectHash)) {
7
6
  throw new ClientValidationError("Expected API service settings hash must be a 64-character SHA-256 hex digest.", "validation_failed", "Use the hash value returned by a previous read.", { projectKey, serviceId, });
8
7
  }
9
8
  const actual = stableHash(current);
@@ -1,6 +1,6 @@
1
1
  import { ClientValidationError, DataikuError, } from "../errors.js";
2
2
  import { projectIncarnationHash, } from "../utils/project-incarnation.js";
3
- import { compareStrings, stableHash, stableJson, } from "../utils/stable-hash.js";
3
+ import { compareStrings, SHA256_HEX_PATTERN, stableHash, stableJson, } from "../utils/stable-hash.js";
4
4
  import { BaseResource, } from "./base.js";
5
5
  /**
6
6
  * Source-verifiable app-manifest reference locations. Validation is scoped to
@@ -93,8 +93,6 @@ function omitRootFields(value, omitted) {
93
93
  }
94
94
  return result;
95
95
  }
96
- /** Lowercase or uppercase SHA-256 hex digest, as emitted by `stableHash`. */
97
- const EXPECT_HASH_PATTERN = /^[0-9a-fA-F]{64}$/;
98
96
  /**
99
97
  * Project a manifest onto its version state. Only string values count: a
100
98
  * non-string `version` is reported as null rather than coerced, so callers can
@@ -295,7 +293,7 @@ export class ApplicationsResource extends BaseResource {
295
293
  }
296
294
  const expectHash = update.expectHash;
297
295
  if (expectHash !== undefined
298
- && (typeof expectHash !== "string" || !EXPECT_HASH_PATTERN.test(expectHash))) {
296
+ && (typeof expectHash !== "string" || !SHA256_HEX_PATTERN.test(expectHash))) {
299
297
  throw new ClientValidationError("Expected manifest hash must be a 64-character SHA-256 hex digest.", "validation_failed", "Use the manifestHash value returned by the manifest-version read.", { projectKey: pk, });
300
298
  }
301
299
  if (update.dryRun !== undefined && typeof update.dryRun !== "boolean") {
@@ -158,7 +158,7 @@ export class CodeEnvsResource extends BaseResource {
158
158
  let raw;
159
159
  if (envLang !== undefined || envName !== undefined) {
160
160
  if (!envLang || !envName) {
161
- throw new Error("codeEnvs.listUsages requires both envLang and envName when either is provided");
161
+ throw new ClientValidationError("codeEnvs.listUsages requires both envLang and envName when either is provided", "validation_failed");
162
162
  }
163
163
  const langEnc = encodeURIComponent(envLang);
164
164
  const nameEnc = encodeURIComponent(envName);
@@ -191,7 +191,7 @@ function tailLog(log, maxLines) {
191
191
  */
192
192
  function assertDefinitionHash(envLang, envName, current, expected) {
193
193
  const currentHash = stableHash(current);
194
- if (currentHash === expected)
194
+ if (currentHash === expected.toLowerCase())
195
195
  return;
196
196
  throw new ClientValidationError(`Code env ${envLang}/${envName} changed since the expected definition hash was captured; refusing to overwrite it.`, "validation_failed", "Re-read the definition with dss code-env get-definition (definitionHash comes from code-env get), review the diff, and retry with the fresh hash.", {
197
197
  envLang,
@@ -1,4 +1,4 @@
1
- import { DataikuError, } from "../errors.js";
1
+ import { DataikuError, unexpectedResponseError, } from "../errors.js";
2
2
  import { DataQualityComputeResultSchema, DataQualityProjectStatusSchema, DataQualityRuleArraySchema, DataQualityRuleResultArraySchema, DataQualityRuleSchema, DataQualityRulesSchema, DataQualityStatusByPartitionSchema, DataQualityStatusSchema, DataQualityTimelineSchema, } from "../schemas.js";
3
3
  import { deepMerge, } from "../utils/deep-merge.js";
4
4
  import { BaseResource, } from "./base.js";
@@ -96,8 +96,9 @@ export class DataQualityResource extends BaseResource {
96
96
  }
97
97
  async computeRulesAndWait(datasetName, opts = {}) {
98
98
  const future = await this.computeRules(datasetName, opts);
99
- if (!future.jobId)
100
- throw new Error("Data quality compute did not return a future jobId.");
99
+ if (!future.jobId) {
100
+ throw unexpectedResponseError("Data quality compute did not return a future jobId.");
101
+ }
101
102
  return this.client.futures.wait(future.jobId, {
102
103
  pollIntervalMs: opts.pollIntervalMs,
103
104
  timeoutMs: opts.timeoutMs,
@@ -89,6 +89,19 @@ export interface UploadDatasetFileResult {
89
89
  export declare function validateStreamColumns(headerRow: string[], expectedColumns: {
90
90
  name: string;
91
91
  }[]): string[];
92
+ /** Wire body for POST /datasets/; shared with `dataset create --plan` so the plan matches the request. */
93
+ export declare function buildDatasetCreateBody(opts: {
94
+ projectKey: string;
95
+ datasetName: string;
96
+ connection?: string;
97
+ dsType: string;
98
+ table?: string;
99
+ dbSchema?: string;
100
+ catalog?: string;
101
+ formatType?: string;
102
+ formatParams?: Record<string, unknown>;
103
+ managed?: boolean;
104
+ }): Record<string, unknown>;
92
105
  export declare function buildDatasetCloneSettings(source: DatasetDetails, targetName: string, projectKey: string, opts: DatasetCloneOptions): Record<string, unknown>;
93
106
  export declare class DatasetsResource extends BaseResource {
94
107
  /** List all datasets in a project. */
@@ -394,12 +394,39 @@ async function collectTsvRowCount(body, stopAfter, timeoutMs) {
394
394
  reader.releaseLock();
395
395
  }
396
396
  }
397
+ /**
398
+ * Encoded CSV bytes accumulated before one stream push. Rows are joined into
399
+ * batch-sized chunks so large downloads do not pay per-row stream overhead; a
400
+ * single escaped row may exceed the budget (rows are never split), and the
401
+ * batch is always flushed at EOF so trailing rows are never dropped.
402
+ */
403
+ const CSV_BATCH_BYTES = 64 * 1024;
397
404
  function tsvToCsvTransform(maxDataRows, stats, onHeader, spreadsheetSafe = true) {
398
405
  const state = createTsvStreamState();
399
406
  const decoder = new StringDecoder("utf-8");
400
407
  const maxRows = Math.max(1, maxDataRows);
401
408
  let headerSeen = false;
402
409
  let done = false;
410
+ // Bounded CSV output batching: emitted rows accumulate here and are joined
411
+ // into a single push once the batch reaches CSV_BATCH_BYTES, so memory stays
412
+ // bounded by the batch budget plus one escaped row (the join briefly holds a
413
+ // second copy of the batch) while per-row push/callback overhead disappears.
414
+ let batchLines = [];
415
+ let batchBytes = 0;
416
+ const flushBatch = (target) => {
417
+ if (batchLines.length === 0)
418
+ return;
419
+ const csv = batchLines.join("");
420
+ batchLines = [];
421
+ batchBytes = 0;
422
+ target.push(csv);
423
+ };
424
+ const queueLine = (target, line) => {
425
+ batchLines.push(line);
426
+ batchBytes += Buffer.byteLength(line);
427
+ if (batchBytes >= CSV_BATCH_BYTES)
428
+ flushBatch(target);
429
+ };
403
430
  let columnCount = 0;
404
431
  const handleRow = (row, push) => {
405
432
  if (done)
@@ -430,16 +457,19 @@ function tsvToCsvTransform(maxDataRows, stats, onHeader, spreadsheetSafe = true)
430
457
  return;
431
458
  }
432
459
  try {
433
- consumeTsvChunk(decoder.write(chunk), state, (row) => handleRow(row, (line) => this.push(line)));
460
+ consumeTsvChunk(decoder.write(chunk), state, (row) => handleRow(row, (line) => queueLine(this, line)));
434
461
  }
435
462
  catch (error) {
436
463
  // Byte-limit overflow: destroy the transform so the pipeline
437
464
  // cancels the response and the partial file is removed.
438
- callback(error instanceof Error ? error : new Error(String(error)));
465
+ callback(error instanceof Error ? error : new ClientValidationError(String(error), "internal_error"));
439
466
  return;
440
467
  }
441
- if (done)
468
+ if (done) {
469
+ // Truncation reached: emit the batch queued so far before EOF.
470
+ flushBatch(this);
442
471
  this.push(null);
472
+ }
443
473
  callback();
444
474
  },
445
475
  flush(callback) {
@@ -450,14 +480,17 @@ function tsvToCsvTransform(maxDataRows, stats, onHeader, spreadsheetSafe = true)
450
480
  try {
451
481
  const tail = decoder.end();
452
482
  if (tail !== "") {
453
- consumeTsvChunk(tail, state, (row) => handleRow(row, (line) => this.push(line)));
483
+ consumeTsvChunk(tail, state, (row) => handleRow(row, (line) => queueLine(this, line)));
454
484
  }
455
- flushTsvStream(state, (row) => handleRow(row, (line) => this.push(line)));
485
+ flushTsvStream(state, (row) => handleRow(row, (line) => queueLine(this, line)));
456
486
  }
457
487
  catch (error) {
458
- callback(error instanceof Error ? error : new Error(String(error)));
488
+ callback(error instanceof Error ? error : new ClientValidationError(String(error), "internal_error"));
459
489
  return;
460
490
  }
491
+ // Emit the trailing partial batch; without this the last rows of a
492
+ // download (below the batch budget) would be silently dropped.
493
+ flushBatch(this);
461
494
  callback();
462
495
  },
463
496
  });
@@ -485,7 +518,8 @@ function isMissingUploadedFilesTargetConnection(error) {
485
518
  return (detail.includes("without a target connection")
486
519
  || detail.includes("target connection is required"));
487
520
  }
488
- function buildDatasetCreateBody(opts) {
521
+ /** Wire body for POST /datasets/; shared with `dataset create --plan` so the plan matches the request. */
522
+ export function buildDatasetCreateBody(opts) {
489
523
  if (opts.dsType.toLowerCase() === "uploadedfiles") {
490
524
  return {
491
525
  projectKey: opts.projectKey,
@@ -604,7 +638,7 @@ export function buildDatasetCloneSettings(source, targetName, projectKey, opts)
604
638
  && source.managed === true
605
639
  && sourcePath !== undefined
606
640
  && settingsParams.path === sourcePath) {
607
- throw new Error(`Refusing to clone managed dataset "${source.name}" with the same storage path. Pass a new path or allowSamePath: true.`);
641
+ throw new ClientValidationError(`Refusing to clone managed dataset "${source.name}" with the same storage path. Pass a new path or allowSamePath: true.`, "validation_failed");
608
642
  }
609
643
  return settings;
610
644
  }
@@ -1,3 +1,4 @@
1
+ import type { DataikuGetOptions } from "../client.js";
1
2
  import type { FlowZone, FlowZoneCreateOptions, FlowZoneObjectType, FlowZoneUpdateOptions } from "../schemas.js";
2
3
  import { BaseResource } from "./base.js";
3
4
  export type FlowZoneItemInput = {
@@ -6,8 +7,11 @@ export type FlowZoneItemInput = {
6
7
  projectKey?: string;
7
8
  };
8
9
  export declare class FlowZonesResource extends BaseResource {
9
- /** List all flow zones in a project. */
10
- list(projectKey?: string): Promise<FlowZone[]>;
10
+ /**
11
+ * List all flow zones in a project. Pass `timeoutMs` to bound the whole
12
+ * request (attempts, backoff, and body read) by a total budget.
13
+ */
14
+ list(projectKey?: string, options?: DataikuGetOptions): Promise<FlowZone[]>;
11
15
  /** Get one flow zone by id. */
12
16
  get(zoneId: string, projectKey?: string): Promise<FlowZone>;
13
17
  /** Create a flow zone. */
@@ -1,3 +1,4 @@
1
+ import { ClientValidationError, } from "../errors.js";
1
2
  import { FlowZoneArraySchema, FlowZoneSchema, } from "../schemas.js";
2
3
  import { deepMerge, } from "../utils/deep-merge.js";
3
4
  import { BaseResource, } from "./base.js";
@@ -9,9 +10,12 @@ function normalizeZoneItem(item) {
9
10
  };
10
11
  }
11
12
  export class FlowZonesResource extends BaseResource {
12
- /** List all flow zones in a project. */
13
- async list(projectKey) {
14
- const raw = await this.client.get(`/public/api/projects/${this.enc(projectKey)}/flow/zones`);
13
+ /**
14
+ * List all flow zones in a project. Pass `timeoutMs` to bound the whole
15
+ * request (attempts, backoff, and body read) by a total budget.
16
+ */
17
+ async list(projectKey, options) {
18
+ const raw = await this.client.get(`/public/api/projects/${this.enc(projectKey)}/flow/zones`, options);
15
19
  return this.client.safeParse(FlowZoneArraySchema, raw, "flowZones.list");
16
20
  }
17
21
  /** Get one flow zone by id. */
@@ -45,8 +49,9 @@ export class FlowZonesResource extends BaseResource {
45
49
  }
46
50
  /** Move items into a flow zone. */
47
51
  async moveItems(zoneId, items, projectKey) {
48
- if (items.length === 0)
49
- throw new Error("flowZones.moveItems requires at least one item");
52
+ if (items.length === 0) {
53
+ throw new ClientValidationError("flowZones.moveItems requires at least one item", "validation_failed");
54
+ }
50
55
  const raw = await this.client.post(`/public/api/projects/${this.enc(projectKey)}/flow/zones/${encodeURIComponent(zoneId)}/add-items`, items.map(normalizeZoneItem));
51
56
  return this.client.safeParse(FlowZoneSchema, raw, "flowZones.moveItems");
52
57
  }
@@ -1,4 +1,5 @@
1
1
  import * as nodePath from "node:path";
2
+ import { unexpectedResponseError, } from "../errors.js";
2
3
  import { FolderDetailsSchema, FolderItemArraySchema, FolderSummaryArraySchema, } from "../schemas.js";
3
4
  import { deepMerge, } from "../utils/deep-merge.js";
4
5
  import { writeResponseToFile, } from "../utils/response-file.js";
@@ -70,8 +71,9 @@ export class FoldersResource extends BaseResource {
70
71
  const normalizedPath = normalizeRemotePath(path);
71
72
  const pEnc = encodeURIComponent(normalizedPath);
72
73
  const res = await this.client.stream(`/public/api/projects/${this.enc(opts?.projectKey)}/managedfolders/${fEnc}/contents/${pEnc}`);
73
- if (!res.body)
74
- throw new Error("folders.download response did not include a body");
74
+ if (!res.body) {
75
+ throw unexpectedResponseError("folders.download response did not include a body");
76
+ }
75
77
  const dest = opts?.localPath
76
78
  ?? nodePath.resolve(process.cwd(), inferDownloadFileName(normalizedPath));
77
79
  await writeResponseToFile(dest, res);
@@ -7,11 +7,13 @@ export interface FutureWaitOptions {
7
7
  export declare class FuturesResource extends BaseResource {
8
8
  get(futureId: string, opts?: {
9
9
  timeoutMs?: number;
10
+ noRetry?: boolean;
10
11
  }): Promise<FutureState>;
11
12
  peek(futureId: string): Promise<FutureState>;
12
13
  state(futureId: string, opts?: {
13
14
  peek?: boolean;
14
15
  timeoutMs?: number;
16
+ noRetry?: boolean;
15
17
  }): Promise<FutureState>;
16
18
  abort(futureId: string): Promise<void>;
17
19
  wait(futureId: string, opts?: FutureWaitOptions): Promise<FutureWaitResult>;
@@ -1,8 +1,6 @@
1
1
  import { FutureStateSchema, FutureWaitResultSchema, } from "../schemas.js";
2
- import { computeNextPollDelayMs, isRequestDeadlineError, } from "../utils/polling.js";
2
+ import { computeNextPollDelayMs, DEFAULT_POLL_INTERVAL_MS, DEFAULT_TIMEOUT_MS, isRequestDeadlineError, } from "../utils/polling.js";
3
3
  import { BaseResource, } from "./base.js";
4
- const DEFAULT_POLL_INTERVAL_MS = 2_000;
5
- const DEFAULT_TIMEOUT_MS = 120_000;
6
4
  function sleep(ms) {
7
5
  return new Promise((resolve) => setTimeout(resolve, ms));
8
6
  }
@@ -25,7 +23,11 @@ function waitState(state) {
25
23
  }
26
24
  export class FuturesResource extends BaseResource {
27
25
  async get(futureId, opts = {}) {
28
- return this.state(futureId, { peek: false, timeoutMs: opts.timeoutMs, });
26
+ return this.state(futureId, {
27
+ peek: false,
28
+ timeoutMs: opts.timeoutMs,
29
+ noRetry: opts.noRetry,
30
+ });
29
31
  }
30
32
  async peek(futureId) {
31
33
  return this.state(futureId, { peek: true, });
@@ -33,7 +35,7 @@ export class FuturesResource extends BaseResource {
33
35
  async state(futureId, opts = {}) {
34
36
  const params = new URLSearchParams();
35
37
  params.set("peek", String(opts.peek === true));
36
- const raw = await this.client.get(`/public/api/futures/${encodeURIComponent(futureId)}?${params.toString()}`, { timeoutMs: opts.timeoutMs, });
38
+ const raw = await this.client.get(`/public/api/futures/${encodeURIComponent(futureId)}?${params.toString()}`, { timeoutMs: opts.timeoutMs, noRetry: opts.noRetry, });
37
39
  return this.client.safeParse(FutureStateSchema, raw, "futures.state");
38
40
  }
39
41
  async abort(futureId) {
@@ -51,16 +53,37 @@ export class FuturesResource extends BaseResource {
51
53
  const timeoutMs = Math.max(0, opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
52
54
  const startedAt = Date.now();
53
55
  let pollCount = 0;
56
+ let lastState;
54
57
  while (true) {
58
+ const elapsedBeforeMs = Date.now() - startedAt;
59
+ // The first observation always happens, even when the budget is
60
+ // already spent. A later poll is never started after the deadline:
61
+ // the loop reports the structured timeout from the last observed
62
+ // state instead of issuing a request the budget cannot cover.
63
+ if (lastState !== undefined && elapsedBeforeMs >= timeoutMs) {
64
+ return this.client.safeParse(FutureWaitResultSchema, {
65
+ futureId,
66
+ jobId: lastState.jobId,
67
+ state: waitState(lastState),
68
+ elapsedMs: elapsedBeforeMs,
69
+ pollCount,
70
+ success: false,
71
+ timedOut: true,
72
+ hasResult: lastState.hasResult === true,
73
+ alive: lastState.alive,
74
+ aborted: lastState.aborted,
75
+ unknown: lastState.unknown,
76
+ }, "futures.wait");
77
+ }
55
78
  pollCount += 1;
56
- // One state observation always happens, even when the budget is
57
- // already spent (the poll precedes the deadline check); only the
58
- // requests issued while budget remains are bounded by the remaining
59
- // time, so a stalled status endpoint can never defeat the deadline.
60
- const remainingMs = timeoutMs - (Date.now() - startedAt);
79
+ // Requests issued while budget remains are bounded by the remaining
80
+ // time; a spent budget still issues exactly one transport attempt
81
+ // (no retries, client requestTimeoutMs cap) so the first observation
82
+ // reaches the server instead of failing before any attempt.
83
+ const remainingMs = timeoutMs - elapsedBeforeMs;
61
84
  let state;
62
85
  try {
63
- state = await this.get(futureId, remainingMs > 0 ? { timeoutMs: remainingMs, } : undefined);
86
+ state = await this.get(futureId, remainingMs > 0 ? { timeoutMs: remainingMs, } : { noRetry: true, });
64
87
  }
65
88
  catch (error) {
66
89
  // The poll budget ran out mid-request: report the structured
@@ -76,6 +99,7 @@ export class FuturesResource extends BaseResource {
76
99
  timedOut: true,
77
100
  }, "futures.wait");
78
101
  }
102
+ lastState = state;
79
103
  const elapsedMs = Date.now() - startedAt;
80
104
  const status = waitState(state);
81
105
  if (isFinished(state)) {
@@ -94,22 +118,10 @@ export class FuturesResource extends BaseResource {
94
118
  };
95
119
  return this.client.safeParse(FutureWaitResultSchema, result, "futures.wait");
96
120
  }
97
- if (elapsedMs >= timeoutMs) {
98
- const result = {
99
- futureId,
100
- jobId: state.jobId,
101
- state: status,
102
- elapsedMs,
103
- pollCount,
104
- success: false,
105
- timedOut: true,
106
- hasResult: state.hasResult === true,
107
- alive: state.alive,
108
- aborted: state.aborted,
109
- unknown: state.unknown,
110
- };
111
- return this.client.safeParse(FutureWaitResultSchema, result, "futures.wait");
112
- }
121
+ // Deadline reached: the guard at the top of the loop reports the
122
+ // timeout from this observation without issuing another request.
123
+ if (elapsedMs >= timeoutMs)
124
+ continue;
113
125
  // Only the remaining budget is slept: a longer sleep would report an
114
126
  // elapsed time the caller never authorized.
115
127
  const nextDelayMs = computeNextPollDelayMs({
@@ -1,3 +1,4 @@
1
+ import { ClientValidationError, unexpectedResponseError, } from "../errors.js";
1
2
  import { InsightDetailsSchema, InsightSummaryArraySchema, } from "../schemas.js";
2
3
  import { deepMerge, } from "../utils/deep-merge.js";
3
4
  import { BaseResource, } from "./base.js";
@@ -33,7 +34,7 @@ export class InsightsResource extends BaseResource {
33
34
  const pk = this.resolveProjectKey(opts.projectKey);
34
35
  const prototype = applyInsightFields(opts.data ?? {}, opts);
35
36
  if (!hasCreateShape(prototype)) {
36
- throw new Error("Insight create requires name and type, either as options or in data.");
37
+ throw new ClientValidationError("Insight create requires name and type, either as options or in data.", "validation_failed");
37
38
  }
38
39
  if (prototype.projectKey === undefined)
39
40
  prototype.projectKey = pk;
@@ -43,7 +44,7 @@ export class InsightsResource extends BaseResource {
43
44
  ...(opts.payload !== undefined ? { payload: opts.payload, } : {}),
44
45
  });
45
46
  if (typeof created.id !== "string" || created.id.length === 0) {
46
- throw new Error("Insight create response did not include an id.");
47
+ throw unexpectedResponseError("Insight create response did not include an id.");
47
48
  }
48
49
  return this.get(created.id, pk);
49
50
  }
@@ -1,9 +1,7 @@
1
- import { DataikuError, } from "../errors.js";
1
+ import { ClientValidationError, DataikuError, } from "../errors.js";
2
2
  import { JobSummaryArraySchema, } from "../schemas.js";
3
- import { computeNextPollDelayMs, isRequestDeadlineError, } from "../utils/polling.js";
3
+ import { computeNextPollDelayMs, DEFAULT_POLL_INTERVAL_MS, DEFAULT_TIMEOUT_MS, isRequestDeadlineError, } from "../utils/polling.js";
4
4
  import { BaseResource, } from "./base.js";
5
- const DEFAULT_POLL_INTERVAL_MS = 2_000;
6
- const DEFAULT_TIMEOUT_MS = 120_000;
7
5
  const DEFAULT_MAX_LOG_LINES = 500;
8
6
  /**
9
7
  * Default bytes retained from a job log body (default 10 MiB). Logs larger
@@ -51,7 +49,7 @@ function jobBuildOutput(target, defaultProjectKey, defaultPartition, defaultTarg
51
49
  }
52
50
  function jobBuildDefinition(targets, defaultProjectKey, opts) {
53
51
  if (targets.length === 0) {
54
- throw new Error("At least one build target is required.");
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 Error("Log URL must include projectKey, jobId, and activityId query parameters.");
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,
@@ -289,19 +287,44 @@ export class JobsResource extends BaseResource {
289
287
  const timeout = Math.max(baseIntervalMs, opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS);
290
288
  const startedAt = Date.now();
291
289
  let pollCount = 0;
290
+ let lastJ;
292
291
  while (true) {
292
+ const elapsedBeforeMs = Date.now() - startedAt;
293
+ // The first observation always happens, even when the budget is
294
+ // already spent. A later poll is never started after the deadline:
295
+ // the loop reports the structured timeout from the last observed
296
+ // state instead of issuing a request the budget cannot cover.
297
+ if (lastJ !== undefined && elapsedBeforeMs >= timeout) {
298
+ const bs = lastJ.baseStatus ?? {};
299
+ const def = bs.def ?? {};
300
+ const gs = lastJ.globalState ?? {};
301
+ return {
302
+ success: false,
303
+ jobId,
304
+ state: bs.state ?? "unknown",
305
+ type: def.type ?? "unknown",
306
+ elapsedMs: elapsedBeforeMs,
307
+ pollCount,
308
+ timedOut: true,
309
+ progress: {
310
+ done: gs.done ?? 0,
311
+ failed: gs.failed ?? 0,
312
+ running: gs.running ?? 0,
313
+ total: gs.total ?? null,
314
+ },
315
+ };
316
+ }
293
317
  pollCount += 1;
294
- // One status observation always happens, even when the budget is
295
- // already spent (the poll precedes the deadline check); only the
296
- // requests issued while budget remains are bounded by the remaining
297
- // time, so a stalled status endpoint can never defeat the deadline.
298
- const elapsedBefore = Date.now() - startedAt;
299
- const remainingMs = timeout - elapsedBefore;
318
+ // Requests issued while budget remains are bounded by the remaining
319
+ // time; a spent budget still issues exactly one transport attempt
320
+ // (no retries, client requestTimeoutMs cap) so the first observation
321
+ // reaches the server instead of failing before any attempt.
322
+ const remainingMs = timeout - elapsedBeforeMs;
300
323
  let j;
301
324
  try {
302
325
  j = await this.client.get(`/public/api/projects/${projectEnc}/jobs/${jobEnc}/`, remainingMs > 0
303
326
  ? { timeoutMs: remainingMs, }
304
- : undefined);
327
+ : { noRetry: true, });
305
328
  }
306
329
  catch (error) {
307
330
  // The poll budget ran out mid-request: report the documented
@@ -325,6 +348,7 @@ export class JobsResource extends BaseResource {
325
348
  },
326
349
  };
327
350
  }
351
+ lastJ = j;
328
352
  const bs = j.baseStatus ?? {};
329
353
  const def = bs.def ?? {};
330
354
  const gs = j.globalState ?? {};
@@ -387,24 +411,10 @@ export class JobsResource extends BaseResource {
387
411
  ...(removed !== undefined ? { removed, } : {}),
388
412
  };
389
413
  }
390
- // Timeout — return failure result, don't throw
391
- if (elapsedMs >= timeout) {
392
- return {
393
- success: false,
394
- jobId,
395
- state,
396
- type: def.type ?? "unknown",
397
- elapsedMs,
398
- pollCount,
399
- timedOut: true,
400
- progress: {
401
- done: gs.done ?? 0,
402
- failed: gs.failed ?? 0,
403
- running: gs.running ?? 0,
404
- total: gs.total ?? null,
405
- },
406
- };
407
- }
414
+ // Timeout — the guard at the top of the loop returns the failure
415
+ // result from this observation without issuing another request.
416
+ if (elapsedMs >= timeout)
417
+ continue;
408
418
  const nextDelayMs = computeNextPollDelayMs({
409
419
  pollCount,
410
420
  baseIntervalMs,
@@ -1,9 +1,7 @@
1
1
  import { ClientValidationError, DataikuError, } from "../errors.js";
2
2
  import { MacroDefinitionSchema, MacroRunStartSchema, MacroStateSchema, MacroSummaryArraySchema, MacroWaitResultSchema, } from "../schemas.js";
3
- import { computeNextPollDelayMs, isRequestDeadlineError, } from "../utils/polling.js";
3
+ import { computeNextPollDelayMs, DEFAULT_POLL_INTERVAL_MS, DEFAULT_TIMEOUT_MS, isRequestDeadlineError, } from "../utils/polling.js";
4
4
  import { BaseResource, requireNonEmpty, } from "./base.js";
5
- const DEFAULT_POLL_INTERVAL_MS = 2_000;
6
- const DEFAULT_TIMEOUT_MS = 120_000;
7
5
  /** Hard floor for the result download cap; keeps a caller typo from hanging the read. */
8
6
  const MIN_RESULT_MAX_BYTES = 1;
9
7
  const MAX_RESULT_MAX_BYTES = 2 * 1024 * 1024 * 1024;
@@ -121,7 +119,7 @@ export class MacrosResource extends BaseResource {
121
119
  const maxBytes = macroResultMaxBytes(opts?.maxBytes);
122
120
  const raw = await this.client.getTextLimited(`${this.base(runnableType, opts?.projectKey)}/result/${encodeURIComponent(runId)}`, maxBytes);
123
121
  if (raw.truncated) {
124
- throw new Error(`Macro result exceeded ${String(maxBytes)} bytes; retry with a larger maxBytes.`);
122
+ throw new ClientValidationError(`Macro result exceeded ${String(maxBytes)} bytes; retry with a larger maxBytes.`, "validation_failed");
125
123
  }
126
124
  const text = raw.text;
127
125
  const trimmed = text.trim();