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
@@ -1,6 +1,7 @@
1
1
  import { basename, } from "node:path";
2
2
  import { ClientValidationError, } from "../errors.js";
3
3
  import { BaseResource, requireNonEmpty, requireNonEmptyArray, requireObject, } from "./base.js";
4
+ import { TrainedModel, } from "./trained-model.js";
4
5
  function boolQuery(value) {
5
6
  return value === undefined ? "true" : String(value);
6
7
  }
@@ -187,30 +188,31 @@ export class SavedModelsResource extends BaseResource {
187
188
  * the stream is read under the client body deadline.
188
189
  */
189
190
  async downloadScoringJar(options, savedModelId, versionId, projectKey) {
190
- const version = encodeURIComponent(requireNonEmpty(versionId, "versionId"));
191
- const query = new URLSearchParams();
192
- if (options?.fullClassName !== undefined) {
193
- query.set("fullClassName", options.fullClassName);
194
- }
195
- if (options?.includeLibs !== undefined) {
196
- query.set("includeLibs", String(options.includeLibs));
197
- }
198
- const suffix = [...query.keys(),].length > 0 ? `?${query.toString()}` : "";
199
- return this.client.stream(`${this.savedModelPath(savedModelId, projectKey)}/versions/${version}/scoring-jar${suffix}`);
191
+ return this.version(savedModelId, versionId, projectKey).downloadScoringJar(options);
200
192
  }
201
193
  /** GET the scoring PMML of a version as a binary/XML stream. */
202
194
  async downloadScoringPmml(savedModelId, versionId, projectKey) {
203
- const version = encodeURIComponent(requireNonEmpty(versionId, "versionId"));
204
- return this.client.stream(`${this.savedModelPath(savedModelId, projectKey)}/versions/${version}/scoring-pmml`);
195
+ return this.version(savedModelId, versionId, projectKey).downloadScoringPmml();
205
196
  }
206
197
  /**
207
198
  * Update the user metadata of a model version. The body must only be the
208
199
  * "userMeta" field of a previously-retrieved version-details object.
209
200
  */
210
201
  async setUserMeta(savedModelId, versionId, userMeta, projectKey) {
202
+ await this.version(savedModelId, versionId, projectKey).setUserMeta(userMeta);
203
+ }
204
+ /**
205
+ * One saved-model version as a {@link TrainedModel}: diagnostics
206
+ * (subpopulation analyses, partial dependencies, timeseries residuals and
207
+ * per-series results), documentation, scoring exports, user metadata.
208
+ */
209
+ version(savedModelId, versionId, projectKey) {
211
210
  const version = encodeURIComponent(requireNonEmpty(versionId, "versionId"));
212
- requireObject(userMeta, "userMeta");
213
- await this.client.putVoid(`${this.savedModelPath(savedModelId, projectKey)}/versions/${version}/user-meta`, userMeta);
211
+ return new TrainedModel(this.client, `${this.savedModelPath(savedModelId, projectKey)}/versions/${version}`);
212
+ }
213
+ /** GET a generated saved-model version documentation (docx) by the `exportId` of its finished future. */
214
+ async downloadDocumentation(exportId, projectKey) {
215
+ return this.client.stream(`/public/api/projects/${this.enc(projectKey)}/savedmodels/documentations/${encodeURIComponent(requireNonEmpty(exportId, "exportId"))}`);
214
216
  }
215
217
  /** Delete a saved model and all of its versions. */
216
218
  async delete(savedModelId, projectKey) {
@@ -1,4 +1,3 @@
1
- import { randomUUID, } from "node:crypto";
2
1
  import { ClientValidationError, DataikuError, } from "../errors.js";
3
2
  import { ScenarioDetailsSchema, ScenarioStatusSchema, ScenarioSummaryArraySchema, } from "../schemas.js";
4
3
  import { deepMerge, } from "../utils/deep-merge.js";
@@ -56,20 +55,15 @@ function scenarioRunSummaryFromRaw(entry) {
56
55
  if (!isRecord(entry))
57
56
  return { runId: "unknown", };
58
57
  const runId = typeof entry.runId === "string" ? entry.runId : "unknown";
59
- const optionalRecord = (value) => isRecord(value) ? value : undefined;
58
+ const start = optionalNumber(entry.start);
59
+ const end = optionalNumber(entry.end);
60
60
  return {
61
61
  runId,
62
- ...(optionalNumber(entry.start) !== undefined ? { start: optionalNumber(entry.start), } : {}),
63
- ...(optionalNumber(entry.end) !== undefined ? { end: optionalNumber(entry.end), } : {}),
64
- ...(optionalRecord(entry.scenario) !== undefined
65
- ? { scenario: optionalRecord(entry.scenario), }
66
- : {}),
67
- ...(optionalRecord(entry.variables) !== undefined
68
- ? { variables: optionalRecord(entry.variables), }
69
- : {}),
70
- ...(optionalRecord(entry.result) !== undefined
71
- ? { result: optionalRecord(entry.result), }
72
- : {}),
62
+ ...(start !== undefined ? { start, } : {}),
63
+ ...(end !== undefined ? { end, } : {}),
64
+ ...(isRecord(entry.scenario) ? { scenario: entry.scenario, } : {}),
65
+ ...(isRecord(entry.variables) ? { variables: entry.variables, } : {}),
66
+ ...(isRecord(entry.result) ? { result: entry.result, } : {}),
73
67
  };
74
68
  }
75
69
  /** Map a light-endpoint response to the documented ScenarioWithStatus shape. */
@@ -422,7 +416,7 @@ export class ScenariosResource extends BaseResource {
422
416
  async runScript(script, opts) {
423
417
  const pk = this.resolveProjectKey(opts?.projectKey);
424
418
  const pkEnc = this.enc(opts?.projectKey);
425
- const scenarioId = `dss_cli_code_run_${Date.now()}_${randomUUID().replace(/-/g, "")}`;
419
+ const scenarioId = `dss_cli_code_run_${Date.now()}_${crypto.randomUUID().replace(/-/g, "")}`;
426
420
  const base = `/public/api/projects/${pkEnc}/scenarios/${encodeURIComponent(scenarioId)}`;
427
421
  const envSelection = opts?.envName
428
422
  ? { envMode: "EXPLICIT_ENV", envName: opts.envName, }
@@ -76,31 +76,70 @@ function sqlErrorDetailFromBody(body) {
76
76
  const match = trimmed.match(/\b[A-Z_]+(?:_ERROR|_NOT_FOUND|_MISMATCH|_DENIED|_EXCEEDED)\b[:\s-]+[^\n\r]+/);
77
77
  return match?.[0] ?? (isLikelySqlErrorDetail(trimmed) ? trimmed.slice(0, 500) : undefined);
78
78
  }
79
+ /**
80
+ * JDBC/driver failures that mean the connection to the database broke or timed
81
+ * out mid-statement: a DML statement may already have committed, so these stay
82
+ * an unknown outcome even though they are SQL/JDBC-flavored.
83
+ */
84
+ const SQL_CONNECTION_LOSS_TYPE = /transient|recoverable|timeout/i;
85
+ const SQL_CONNECTION_LOSS_TEXT = /i\/o error|communication (?:link|error|failure)|connection (?:reset|closed|refused|lost|abort)|broken pipe|socket|timed out|network/i;
86
+ /**
87
+ * Whether DSS answered with a definite refusal of the statement, as opposed to
88
+ * an unknown outcome (transport reset, timeout, bare gateway error). DSS runs the
89
+ * statement before answering the start POST, so a SQL-flavored error body (JDBC
90
+ * error, or an error type from a SQL/JDBC class) or a body DSS itself classifies
91
+ * as permission/not-found means the database rejected the statement — unless
92
+ * the error reports a lost or timed-out database connection.
93
+ */
94
+ function isDefiniteSqlRejection(error, sqlDetail) {
95
+ if (error.status < 400)
96
+ return false;
97
+ let errorType;
98
+ try {
99
+ errorType = asRecord(JSON.parse(error.body))?.errorType;
100
+ }
101
+ catch {
102
+ errorType = undefined;
103
+ }
104
+ if (typeof errorType === "string" && SQL_CONNECTION_LOSS_TYPE.test(errorType))
105
+ return false;
106
+ if (SQL_CONNECTION_LOSS_TEXT.test(error.body))
107
+ return false;
108
+ if (sqlDetail)
109
+ return true;
110
+ if (error.category === "forbidden" || error.category === "not_found")
111
+ return true;
112
+ return typeof errorType === "string" && /sql|jdbc/i.test(errorType);
113
+ }
79
114
  function withSqlErrorContext(error) {
80
115
  if (error instanceof DataikuError) {
81
116
  const detail = sqlErrorDetailFromBody(error.body);
82
- if (detail) {
117
+ const definiteFailure = isDefiniteSqlRejection(error, detail);
118
+ if (detail || definiteFailure) {
83
119
  let body = error.body;
84
- try {
85
- const parsed = JSON.parse(error.body);
86
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
87
- body = JSON.stringify({
88
- ...parsed,
89
- message: `SQL query failed: ${detail}`,
90
- sqlError: detail,
91
- });
120
+ if (detail) {
121
+ try {
122
+ const parsed = JSON.parse(error.body);
123
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
124
+ body = JSON.stringify({
125
+ ...parsed,
126
+ message: `SQL query failed: ${detail}`,
127
+ sqlError: detail,
128
+ });
129
+ }
130
+ else {
131
+ body = `SQL query failed: ${detail}\n${error.body}`;
132
+ }
92
133
  }
93
- else {
134
+ catch {
94
135
  body = `SQL query failed: ${detail}\n${error.body}`;
95
136
  }
96
137
  }
97
- catch {
98
- body = `SQL query failed: ${detail}\n${error.body}`;
99
- }
100
138
  throw new DataikuError(error.status, error.statusText, body, error.retry, error.requestId, {
101
139
  target: error.trustedTarget,
102
140
  elapsedMs: error.trustedElapsedMs,
103
141
  bodyTruncated: error.bodyTruncated,
142
+ definiteFailure,
104
143
  });
105
144
  }
106
145
  }
@@ -38,6 +38,18 @@ export declare class StreamingEndpointsResource extends BaseResource {
38
38
  getSettings(id: string, projectKey?: string): Promise<StreamingEndpointSettings>;
39
39
  /** Create a streaming endpoint with type-specific params. */
40
40
  create(id: string, type: string, body: StreamingEndpointCreateParams, projectKey?: string): Promise<StreamingEndpointSettings>;
41
+ /**
42
+ * Create a managed streaming endpoint (POST /streamingendpoints/managed):
43
+ * DSS picks type and params from the connection and format option.
44
+ */
45
+ createManaged(id: string, creationSettings: {
46
+ connectionId: string;
47
+ formatOptionId?: string;
48
+ }, projectKey?: string): Promise<void>;
49
+ /** Get a streaming endpoint's schema (`{columns: [{name, type}]}`). */
50
+ getSchema(id: string, projectKey?: string): Promise<Record<string, unknown>>;
51
+ /** Replace a streaming endpoint's schema with an object obtained from `getSchema`, edited. */
52
+ setSchema(id: string, schema: Record<string, unknown>, projectKey?: string): Promise<void>;
41
53
  /** Update one streaming endpoint's raw settings. */
42
54
  updateSettings(id: string, body: Record<string, unknown>, projectKey?: string): Promise<void>;
43
55
  /** Delete a streaming endpoint from the flow without deleting underlying streaming data. */
@@ -23,6 +23,21 @@ export class StreamingEndpointsResource extends BaseResource {
23
23
  params: body,
24
24
  });
25
25
  }
26
+ /**
27
+ * Create a managed streaming endpoint (POST /streamingendpoints/managed):
28
+ * DSS picks type and params from the connection and format option.
29
+ */
30
+ async createManaged(id, creationSettings, projectKey) {
31
+ await this.client.post(`/public/api/projects/${this.enc(projectKey)}/streamingendpoints/managed`, { id, creationSettings, });
32
+ }
33
+ /** Get a streaming endpoint's schema (`{columns: [{name, type}]}`). */
34
+ async getSchema(id, projectKey) {
35
+ return this.client.get(`/public/api/projects/${this.enc(projectKey)}/streamingendpoints/${encodeURIComponent(id)}/schema`);
36
+ }
37
+ /** Replace a streaming endpoint's schema with an object obtained from `getSchema`, edited. */
38
+ async setSchema(id, schema, projectKey) {
39
+ await this.client.putVoid(`/public/api/projects/${this.enc(projectKey)}/streamingendpoints/${encodeURIComponent(id)}/schema`, schema);
40
+ }
26
41
  /** Update one streaming endpoint's raw settings. */
27
42
  async updateSettings(id, body, projectKey) {
28
43
  const endpointId = encodeURIComponent(id);
@@ -0,0 +1,60 @@
1
+ import type { DataikuClient } from "../client.js";
2
+ import { type DocumentationTemplate } from "./documentation.js";
3
+ import { type DssTask } from "./futures.js";
4
+ /** Sampling and execution settings of a diagnostics computation (dataikuapi `computationParams`). */
5
+ export interface ModelDiagnosticsComputation {
6
+ /** Records sampled from the test set (DSS default 10000). */
7
+ sampleSize?: number;
8
+ randomState?: number;
9
+ nJobs?: number;
10
+ debugMode?: boolean;
11
+ }
12
+ /** Diagnostics a trained model computes on demand, then serves as results. */
13
+ export type ComputedModelDiagnostic = "subpopulation-analyses" | "partial-dependencies" | "timeseries-residuals";
14
+ /** Every diagnostics result a trained model serves. */
15
+ export type ModelDiagnostic = ComputedModelDiagnostic | "per-timeseries-metrics" | "per-timeseries-evaluation-forecasts";
16
+ /** Diagnostics that are started with POST and return a DSS future. */
17
+ export declare const COMPUTED_MODEL_DIAGNOSTICS: Record<string, true>;
18
+ /** Diagnostics results served by GET (computed ones plus per-series results of forecasting models). */
19
+ export declare const MODEL_DIAGNOSTICS: Record<string, true>;
20
+ /**
21
+ * Request body that starts a diagnostics computation. Subpopulation analyses
22
+ * and partial dependencies take the features to analyse; timeseries
23
+ * residuals take no body.
24
+ */
25
+ export declare function modelDiagnosticsBody(kind: ComputedModelDiagnostic, features: string[], computation?: ModelDiagnosticsComputation): Record<string, unknown> | undefined;
26
+ /** Query options for the scoring JAR export (GET .../scoring-jar). */
27
+ export interface ScoringJarOptions {
28
+ fullClassName?: string;
29
+ includeLibs?: boolean;
30
+ }
31
+ /**
32
+ * One trained model: a visual-ML lab model (`mlTasks.model(...)`) or a
33
+ * saved-model version (`savedModels.version(...)`). Both expose the same
34
+ * routes (user meta, scoring exports, diagnostics, documentation) under
35
+ * different bases. Generated documents are downloaded from the owning
36
+ * resource (`mlTasks.downloadModelDocumentation`, `savedModels.downloadDocumentation`).
37
+ */
38
+ export declare class TrainedModel {
39
+ private readonly client;
40
+ /** Route of the trained model, e.g. `.../models/lab/{a}/{t}/models/{id}`. */
41
+ private readonly modelPath;
42
+ constructor(client: DataikuClient,
43
+ /** Route of the trained model, e.g. `.../models/lab/{a}/{t}/models/{id}`. */
44
+ modelPath: string);
45
+ /** Replace the user metadata: only the `userMeta` field of the model details, edited. */
46
+ setUserMeta(userMeta: Record<string, unknown>): Promise<void>;
47
+ /** GET the Java scoring JAR (license-gated, optimized-scoring models only) as a binary stream. */
48
+ downloadScoringJar(options?: ScoringJarOptions): Promise<Response>;
49
+ /** GET the PMML export (license-gated, PMML-compatible models only) as a stream. */
50
+ downloadScoringPmml(): Promise<Response>;
51
+ /**
52
+ * Start computing subpopulation analyses or partial dependencies over
53
+ * `features`, or timeseries residuals. Returns the DSS future.
54
+ */
55
+ compute(kind: ComputedModelDiagnostic, features?: string[], computation?: ModelDiagnosticsComputation): Promise<DssTask>;
56
+ /** Results computed so far (subpopulation analyses, partial dependencies, residuals, per-series metrics or forecasts). */
57
+ get(kind: ModelDiagnostic): Promise<unknown>;
58
+ /** Start generating the model documentation (docx); the finished result carries `exportId`. */
59
+ generateDocumentation(template?: DocumentationTemplate): Promise<DssTask>;
60
+ }
@@ -0,0 +1,98 @@
1
+ import { ClientValidationError, } from "../errors.js";
2
+ import { requireObject, } from "./base.js";
3
+ import { startDocumentation, } from "./documentation.js";
4
+ import { dssTask, } from "./futures.js";
5
+ /** Diagnostics that are started with POST and return a DSS future. */
6
+ export const COMPUTED_MODEL_DIAGNOSTICS = {
7
+ "subpopulation-analyses": true,
8
+ "partial-dependencies": true,
9
+ "timeseries-residuals": true,
10
+ };
11
+ /** Diagnostics results served by GET (computed ones plus per-series results of forecasting models). */
12
+ export const MODEL_DIAGNOSTICS = {
13
+ ...COMPUTED_MODEL_DIAGNOSTICS,
14
+ "per-timeseries-metrics": true,
15
+ "per-timeseries-evaluation-forecasts": true,
16
+ };
17
+ /**
18
+ * Request body that starts a diagnostics computation. Subpopulation analyses
19
+ * and partial dependencies take the features to analyse; timeseries
20
+ * residuals take no body.
21
+ */
22
+ export function modelDiagnosticsBody(kind, features, computation = {}) {
23
+ if (kind === "timeseries-residuals")
24
+ return undefined;
25
+ if (features.length === 0) {
26
+ throw new ClientValidationError(`${kind} needs at least one feature.`, "missing_required_arg");
27
+ }
28
+ return {
29
+ features,
30
+ computationParams: {
31
+ ...(computation.sampleSize !== undefined ? { sample_size: computation.sampleSize, } : {}),
32
+ ...(computation.randomState !== undefined ? { random_state: computation.randomState, } : {}),
33
+ ...(computation.nJobs !== undefined ? { n_jobs: computation.nJobs, } : {}),
34
+ ...(computation.debugMode !== undefined ? { debug_mode: computation.debugMode, } : {}),
35
+ },
36
+ };
37
+ }
38
+ /**
39
+ * One trained model: a visual-ML lab model (`mlTasks.model(...)`) or a
40
+ * saved-model version (`savedModels.version(...)`). Both expose the same
41
+ * routes (user meta, scoring exports, diagnostics, documentation) under
42
+ * different bases. Generated documents are downloaded from the owning
43
+ * resource (`mlTasks.downloadModelDocumentation`, `savedModels.downloadDocumentation`).
44
+ */
45
+ export class TrainedModel {
46
+ client;
47
+ modelPath;
48
+ constructor(client,
49
+ /** Route of the trained model, e.g. `.../models/lab/{a}/{t}/models/{id}`. */
50
+ modelPath) {
51
+ this.client = client;
52
+ this.modelPath = modelPath;
53
+ }
54
+ /** Replace the user metadata: only the `userMeta` field of the model details, edited. */
55
+ async setUserMeta(userMeta) {
56
+ await this.client.putVoid(`${this.modelPath}/user-meta`, requireObject(userMeta, "userMeta"));
57
+ }
58
+ /** GET the Java scoring JAR (license-gated, optimized-scoring models only) as a binary stream. */
59
+ async downloadScoringJar(options = {}) {
60
+ const query = new URLSearchParams();
61
+ if (options.fullClassName !== undefined)
62
+ query.set("fullClassName", options.fullClassName);
63
+ if (options.includeLibs !== undefined)
64
+ query.set("includeLibs", String(options.includeLibs));
65
+ return this.client.stream(`${this.modelPath}/scoring-jar${query.size > 0 ? `?${query}` : ""}`);
66
+ }
67
+ /** GET the PMML export (license-gated, PMML-compatible models only) as a stream. */
68
+ async downloadScoringPmml() {
69
+ return this.client.stream(`${this.modelPath}/scoring-pmml`);
70
+ }
71
+ /**
72
+ * Start computing subpopulation analyses or partial dependencies over
73
+ * `features`, or timeseries residuals. Returns the DSS future.
74
+ */
75
+ async compute(kind, features = [], computation = {}) {
76
+ if (COMPUTED_MODEL_DIAGNOSTICS[kind] !== true) {
77
+ throw new ClientValidationError(`Computed diagnostic must be one of ${Object.keys(COMPUTED_MODEL_DIAGNOSTICS).join(", ")}.`, "invalid_enum");
78
+ }
79
+ const raw = await this.client.post(`${this.modelPath}/${kind}`, modelDiagnosticsBody(kind, features, computation));
80
+ return dssTask(raw, `Computing ${kind}`);
81
+ }
82
+ /** Results computed so far (subpopulation analyses, partial dependencies, residuals, per-series metrics or forecasts). */
83
+ async get(kind) {
84
+ if (MODEL_DIAGNOSTICS[kind] !== true) {
85
+ throw new ClientValidationError(`Diagnostic must be one of ${Object.keys(MODEL_DIAGNOSTICS).join(", ")}.`, "invalid_enum");
86
+ }
87
+ return this.client.get(`${this.modelPath}/${kind}`);
88
+ }
89
+ /** Start generating the model documentation (docx); the finished result carries `exportId`. */
90
+ async generateDocumentation(template = { kind: "default", }) {
91
+ const base = `${this.modelPath}/generate-documentation-from`;
92
+ return startDocumentation(this.client, {
93
+ default: `${base}-default-template`,
94
+ file: `${base}-custom-template`,
95
+ folder: `${base}-template-in-folder`,
96
+ }, template, "Model documentation generation");
97
+ }
98
+ }
@@ -68,4 +68,12 @@ export declare class WebappsResource extends BaseResource {
68
68
  restartBackendAndWait(webappId: string, projectKey?: string, options?: WebappWaitOptions): Promise<FutureWaitResult>;
69
69
  /** Get a webapp backend's state. */
70
70
  getBackendState(webappId: string, projectKey?: string): Promise<Record<string, unknown>>;
71
+ /**
72
+ * Trust a webapp's code (POST /actions/trust): for the caller, or for every
73
+ * user with `trustForEverybody` (admin). Returns `{canAccess, trustedCodeStatus}`.
74
+ */
75
+ trust(webappId: string, opts?: {
76
+ projectKey?: string;
77
+ trustForEverybody?: boolean;
78
+ }): Promise<Record<string, unknown>>;
71
79
  }
@@ -95,4 +95,14 @@ export class WebappsResource extends BaseResource {
95
95
  const id = encodeURIComponent(webappId);
96
96
  return this.client.get(`/public/api/projects/${this.enc(projectKey)}/webapps/${id}/backend/state`);
97
97
  }
98
+ /**
99
+ * Trust a webapp's code (POST /actions/trust): for the caller, or for every
100
+ * user with `trustForEverybody` (admin). Returns `{canAccess, trustedCodeStatus}`.
101
+ */
102
+ async trust(webappId, opts = {}) {
103
+ const query = opts.trustForEverybody === undefined
104
+ ? ""
105
+ : `?trustForEverybody=${opts.trustForEverybody}`;
106
+ return this.client.post(`/public/api/projects/${this.enc(opts.projectKey)}/webapps/${encodeURIComponent(webappId)}/actions/trust${query}`);
107
+ }
98
108
  }
@@ -1,4 +1,5 @@
1
- import type { WikiArticleData, WikiSettings } from "../schemas.js";
1
+ import { ClientValidationError } from "../errors.js";
2
+ import type { WikiArticleAttachment, WikiArticleData, WikiSettings } from "../schemas.js";
2
3
  import { BaseResource } from "./base.js";
3
4
  export interface WikiArticleCreateOptions {
4
5
  name: string;
@@ -12,11 +13,61 @@ export interface WikiArticleUpdateOptions {
12
13
  data?: Record<string, unknown>;
13
14
  projectKey?: string;
14
15
  }
16
+ export interface WikiAttachResult {
17
+ articleId: string;
18
+ projectKey: string;
19
+ fileName: string;
20
+ bytes: number;
21
+ /** The attachment DSS created, as re-read from the article. */
22
+ attachment: WikiArticleAttachment;
23
+ /** All attachments of the article after the upload. */
24
+ attachments: WikiArticleAttachment[];
25
+ }
26
+ export interface WikiDetachResult {
27
+ articleId: string;
28
+ projectKey: string;
29
+ /** The attachment that was removed. */
30
+ detached: WikiArticleAttachment;
31
+ /** Attachments left on the article. */
32
+ attachments: WikiArticleAttachment[];
33
+ }
34
+ export interface LocalWikiAttachmentFile {
35
+ path: string;
36
+ fileName: string;
37
+ bytes: number;
38
+ }
39
+ /** Attachments of an article; DSS omits the field when there are none. */
40
+ export declare function wikiAttachments(data: WikiArticleData): WikiArticleAttachment[];
41
+ /** The attachment with exactly this smart id, or undefined. */
42
+ export declare function findWikiAttachment(data: WikiArticleData, smartId: string): WikiArticleAttachment | undefined;
43
+ /** Coded not_found error for a smart id that is not attached to the article. */
44
+ export declare function wikiAttachmentNotFound(data: WikiArticleData, smartId: string): ClientValidationError;
45
+ /** Check that a local path is a readable regular file before any DSS call. */
46
+ export declare function statWikiAttachmentFile(localPath: string): Promise<LocalWikiAttachmentFile>;
15
47
  export declare class WikiResource extends BaseResource {
16
48
  settings(projectKey?: string): Promise<WikiSettings>;
49
+ /**
50
+ * Replace the wiki properties (home article and taxonomy, PUT /wiki/) with
51
+ * an object obtained from `settings`, edited.
52
+ */
53
+ updateSettings(settings: Record<string, unknown>, projectKey?: string): Promise<void>;
17
54
  list(projectKey?: string): Promise<WikiArticleData[]>;
18
55
  get(articleIdOrName: string, projectKey?: string): Promise<WikiArticleData>;
19
56
  create(opts: WikiArticleCreateOptions): Promise<WikiArticleData>;
20
57
  update(articleIdOrName: string, opts: WikiArticleUpdateOptions): Promise<WikiArticleData>;
21
58
  delete(articleIdOrName: string, projectKey?: string): Promise<void>;
59
+ /**
60
+ * Upload a local file and attach it to an article (multipart POST to
61
+ * /wiki/{id}/upload). DSS names the attachment after the file's basename and
62
+ * types it from the extension. The article is re-read to identify the new
63
+ * attachment.
64
+ */
65
+ attach(articleIdOrName: string, localPath: string, projectKey?: string): Promise<WikiAttachResult>;
66
+ /**
67
+ * Remove exactly one attachment (by smart id) from an article: the article is
68
+ * read, the entry filtered out of `article.attachments`, and the whole
69
+ * article PUT back. Throws a coded `not_found` error when the smart id is not
70
+ * attached.
71
+ */
72
+ detach(articleIdOrName: string, smartId: string, projectKey?: string): Promise<WikiDetachResult>;
22
73
  }
@@ -1,6 +1,36 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { ClientValidationError, } from "../errors.js";
1
4
  import { WikiArticleDataArraySchema, WikiArticleDataSchema, WikiSettingsSchema, } from "../schemas.js";
2
5
  import { deepMerge, } from "../utils/deep-merge.js";
3
6
  import { BaseResource, } from "./base.js";
7
+ /** Attachments of an article; DSS omits the field when there are none. */
8
+ export function wikiAttachments(data) {
9
+ return data.article.attachments ?? [];
10
+ }
11
+ /** The attachment with exactly this smart id, or undefined. */
12
+ export function findWikiAttachment(data, smartId) {
13
+ return wikiAttachments(data).find((attachment) => attachment.smartId === smartId);
14
+ }
15
+ /** Coded not_found error for a smart id that is not attached to the article. */
16
+ export function wikiAttachmentNotFound(data, smartId) {
17
+ const available = wikiAttachments(data).flatMap((attachment) => attachment.smartId === undefined ? [] : [attachment.smartId,]);
18
+ return new ClientValidationError(`Attachment ${smartId} not found on wiki article ${data.article.id}.`, "not_found", "List attachments with `dss wiki get ARTICLE_ID` (article.attachments[].smartId), or pass --if-exists to treat absence as success.", { articleId: data.article.id, smartId, availableSmartIds: available, });
19
+ }
20
+ /** Check that a local path is a readable regular file before any DSS call. */
21
+ export async function statWikiAttachmentFile(localPath) {
22
+ let stat;
23
+ try {
24
+ stat = await fs.promises.stat(localPath);
25
+ }
26
+ catch (error) {
27
+ throw new ClientValidationError(`Could not read attachment file ${localPath}: ${error instanceof Error ? error.message : String(error)}.`, "not_found", "Verify the file path and that it is readable.");
28
+ }
29
+ if (!stat.isFile()) {
30
+ throw new ClientValidationError(`${localPath} is not a regular file.`);
31
+ }
32
+ return { path: localPath, fileName: path.basename(localPath), bytes: stat.size, };
33
+ }
4
34
  const WIKI_LIST_CONCURRENCY = 4;
5
35
  function taxonomyIds(nodes) {
6
36
  const ids = [];
@@ -33,6 +63,13 @@ export class WikiResource extends BaseResource {
33
63
  const raw = await this.client.get(`/public/api/projects/${this.enc(projectKey)}/wiki/`);
34
64
  return this.client.safeParse(WikiSettingsSchema, raw, "wiki.settings");
35
65
  }
66
+ /**
67
+ * Replace the wiki properties (home article and taxonomy, PUT /wiki/) with
68
+ * an object obtained from `settings`, edited.
69
+ */
70
+ async updateSettings(settings, projectKey) {
71
+ await this.client.putVoid(`/public/api/projects/${this.enc(projectKey)}/wiki/`, settings);
72
+ }
36
73
  async list(projectKey) {
37
74
  const settings = await this.settings(projectKey);
38
75
  const ids = taxonomyIds(settings.taxonomy);
@@ -70,4 +107,63 @@ export class WikiResource extends BaseResource {
70
107
  const current = await this.get(articleIdOrName, projectKey);
71
108
  await this.client.del(`/public/api/projects/${this.enc(projectKey)}/wiki/${encodeURIComponent(current.article.id)}`);
72
109
  }
110
+ /**
111
+ * Upload a local file and attach it to an article (multipart POST to
112
+ * /wiki/{id}/upload). DSS names the attachment after the file's basename and
113
+ * types it from the extension. The article is re-read to identify the new
114
+ * attachment.
115
+ */
116
+ async attach(articleIdOrName, localPath, projectKey) {
117
+ const file = await statWikiAttachmentFile(localPath);
118
+ const pk = this.resolveProjectKey(projectKey);
119
+ const before = await this.get(articleIdOrName, pk);
120
+ const articleId = before.article.id;
121
+ await this.client.uploadJson(`/public/api/projects/${encodeURIComponent(pk)}/wiki/${encodeURIComponent(articleId)}/upload`, localPath, file.fileName);
122
+ const after = await this.get(articleId, pk);
123
+ const knownIds = new Set(wikiAttachments(before).map((attachment) => attachment.smartId));
124
+ let created = wikiAttachments(after).filter((attachment) => !knownIds.has(attachment.smartId));
125
+ if (created.length > 1) {
126
+ created = created.filter((attachment) => attachment.details?.["objectDisplayName"] === file.fileName
127
+ && attachment.details?.["size"] === file.bytes);
128
+ }
129
+ const attachment = created[0];
130
+ if (created.length !== 1 || attachment === undefined || attachment.smartId === undefined) {
131
+ throw new ClientValidationError(`DSS did not verify upload of ${file.fileName} to wiki article ${articleId}.`, "ambiguous_outcome", "The upload returned successfully, but the article did not show exactly one new attachment. Inspect the article (dss wiki get) before retrying.", { articleId, expectedBytes: file.bytes, newAttachments: created, });
132
+ }
133
+ return {
134
+ articleId,
135
+ projectKey: pk,
136
+ fileName: file.fileName,
137
+ bytes: file.bytes,
138
+ attachment,
139
+ attachments: wikiAttachments(after),
140
+ };
141
+ }
142
+ /**
143
+ * Remove exactly one attachment (by smart id) from an article: the article is
144
+ * read, the entry filtered out of `article.attachments`, and the whole
145
+ * article PUT back. Throws a coded `not_found` error when the smart id is not
146
+ * attached.
147
+ */
148
+ async detach(articleIdOrName, smartId, projectKey) {
149
+ const pk = this.resolveProjectKey(projectKey);
150
+ const current = await this.get(articleIdOrName, pk);
151
+ const detached = findWikiAttachment(current, smartId);
152
+ if (detached === undefined)
153
+ throw wikiAttachmentNotFound(current, smartId);
154
+ const remaining = wikiAttachments(current).filter((attachment) => attachment.smartId !== smartId);
155
+ const raw = await this.client.put(`/public/api/projects/${encodeURIComponent(pk)}/wiki/${encodeURIComponent(current.article.id)}`, { ...current, article: { ...current.article, attachments: remaining, }, });
156
+ const saved = this.client.safeParse(WikiArticleDataSchema, raw, "wiki.detach");
157
+ const after = wikiAttachments(saved);
158
+ if (findWikiAttachment(saved, smartId) !== undefined
159
+ || after.length !== remaining.length) {
160
+ throw new ClientValidationError(`DSS did not verify removal of attachment ${smartId} from wiki article ${current.article.id}.`, "ambiguous_outcome", "The save returned successfully, but the article's attachments are not the expected remainder. Inspect the article (dss wiki get) before retrying.", {
161
+ articleId: current.article.id,
162
+ smartId,
163
+ expectedRemaining: remaining.length,
164
+ actualRemaining: after.length,
165
+ });
166
+ }
167
+ return { articleId: current.article.id, projectKey: pk, detached, attachments: after, };
168
+ }
73
169
  }
@@ -56,4 +56,6 @@ export declare class WorkspacesResource extends BaseResource {
56
56
  listObjects(workspaceKey: string): Promise<WorkspaceObject[]>;
57
57
  /** Add an object to a workspace. */
58
58
  addObject(workspaceKey: string, object: WorkspaceObjectInput): Promise<WorkspaceObject>;
59
+ /** Remove an object from a workspace by its workspace object id (from `listObjects`). */
60
+ removeObject(workspaceKey: string, workspaceObjectId: string): Promise<void>;
59
61
  }
@@ -28,4 +28,8 @@ export class WorkspacesResource extends BaseResource {
28
28
  async addObject(workspaceKey, object) {
29
29
  return this.client.post(`/public/api/workspaces/${encodeURIComponent(workspaceKey)}/objects`, object);
30
30
  }
31
+ /** Remove an object from a workspace by its workspace object id (from `listObjects`). */
32
+ async removeObject(workspaceKey, workspaceObjectId) {
33
+ await this.client.del(`/public/api/workspaces/${encodeURIComponent(workspaceKey)}/objects/${encodeURIComponent(workspaceObjectId)}`);
34
+ }
31
35
  }
@@ -345,6 +345,10 @@ function renderMermaid(map) {
345
345
  lines.push(` ${idByNode.get(nodeId)}["${mermaidLabel(readableNode(node))}"]`);
346
346
  }
347
347
  lines.push(" end");
348
+ // Zone colors come from DSS; only plain #RRGGBB values reach the style line.
349
+ if (zone.color && /^#[0-9a-fA-F]{6}$/.test(zone.color)) {
350
+ lines.push(` style z${zoneIndex} fill:${zone.color}22,stroke:${zone.color}`);
351
+ }
348
352
  }
349
353
  for (const edge of map.edges) {
350
354
  const from = idByNode.get(edge.from);