dataiku-sdk 0.12.4 → 0.12.6

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 (86) hide show
  1. package/README.md +119 -7
  2. package/dist/packages/types/src/index.d.ts +5 -0
  3. package/dist/packages/types/src/index.js +5 -0
  4. package/dist/src/cli/coerce.d.ts +1 -3
  5. package/dist/src/cli/coerce.js +1 -17
  6. package/dist/src/cli/commands/app.js +709 -14
  7. package/dist/src/cli/commands/code-env.js +2 -2
  8. package/dist/src/cli/commands/dashboard.js +1 -1
  9. package/dist/src/cli/commands/dataset.js +2 -2
  10. package/dist/src/cli/commands/folder.js +1 -1
  11. package/dist/src/cli/commands/insight.js +2 -2
  12. package/dist/src/cli/commands/notebook.js +2 -2
  13. package/dist/src/cli/commands/recipe.js +3 -3
  14. package/dist/src/cli/commands/scenario.js +1 -1
  15. package/dist/src/cli/commands/sql.js +17 -2
  16. package/dist/src/cli/commands/variable.js +1 -1
  17. package/dist/src/cli/commands/wiki.js +1 -1
  18. package/dist/src/cli/contract.d.ts +12 -3
  19. package/dist/src/cli/contract.js +805 -49
  20. package/dist/src/cli/flags.js +9 -0
  21. package/dist/src/cli/helpers/app-permissions.d.ts +63 -0
  22. package/dist/src/cli/helpers/app-permissions.js +239 -0
  23. package/dist/src/cli/helpers/app-successor.d.ts +106 -0
  24. package/dist/src/cli/helpers/app-successor.js +625 -0
  25. package/dist/src/cli/helpers/cleanup.js +46 -12
  26. package/dist/src/cli/output.d.ts +12 -1
  27. package/dist/src/cli/output.js +53 -6
  28. package/dist/src/cli/usage.d.ts +1 -1
  29. package/dist/src/cli/usage.js +5 -2
  30. package/dist/src/cli.js +329 -32
  31. package/dist/src/client.d.ts +13 -1
  32. package/dist/src/client.js +46 -6
  33. package/dist/src/errors.d.ts +1 -1
  34. package/dist/src/resources/applications.d.ts +191 -1
  35. package/dist/src/resources/applications.js +549 -10
  36. package/dist/src/resources/datasets.js +8 -0
  37. package/dist/src/resources/futures.js +7 -2
  38. package/dist/src/resources/sql.d.ts +5 -0
  39. package/dist/src/resources/sql.js +5 -4
  40. package/dist/src/skill.js +78 -19
  41. package/dist/src/utils/cleanup-ledger.d.ts +34 -1
  42. package/dist/src/utils/cleanup-ledger.js +118 -7
  43. package/dist/src/utils/dss-url.d.ts +6 -0
  44. package/dist/src/utils/dss-url.js +17 -0
  45. package/dist/src/utils/project-incarnation.d.ts +6 -0
  46. package/dist/src/utils/project-incarnation.js +27 -0
  47. package/dist/src/utils/stable-hash.d.ts +11 -0
  48. package/dist/src/utils/stable-hash.js +27 -0
  49. package/node_modules/@sinclair/typebox/build/cjs/compiler/compiler.js +26 -46
  50. package/node_modules/@sinclair/typebox/build/cjs/parser/runtime/guard.d.ts +1 -1
  51. package/node_modules/@sinclair/typebox/build/cjs/parser/runtime/token.d.ts +2 -2
  52. package/node_modules/@sinclair/typebox/build/cjs/syntax/mapping.d.ts +1 -1
  53. package/node_modules/@sinclair/typebox/build/cjs/system/evaluate.d.ts +5 -0
  54. package/node_modules/@sinclair/typebox/build/cjs/system/evaluate.js +11 -0
  55. package/node_modules/@sinclair/typebox/build/cjs/system/index.d.ts +1 -0
  56. package/node_modules/@sinclair/typebox/build/cjs/system/index.js +1 -0
  57. package/node_modules/@sinclair/typebox/build/cjs/type/object/object.d.ts +2 -2
  58. package/node_modules/@sinclair/typebox/build/cjs/type/object/object.js +18 -2
  59. package/node_modules/@sinclair/typebox/build/cjs/value/parse/parse.d.ts +1 -1
  60. package/node_modules/@sinclair/typebox/build/esm/compiler/compiler.mjs +27 -47
  61. package/node_modules/@sinclair/typebox/build/esm/parser/runtime/guard.d.mts +1 -1
  62. package/node_modules/@sinclair/typebox/build/esm/parser/runtime/token.d.mts +2 -2
  63. package/node_modules/@sinclair/typebox/build/esm/syntax/mapping.d.mts +1 -1
  64. package/node_modules/@sinclair/typebox/build/esm/system/evaluate.d.mts +5 -0
  65. package/node_modules/@sinclair/typebox/build/esm/system/evaluate.mjs +7 -0
  66. package/node_modules/@sinclair/typebox/build/esm/system/index.d.mts +1 -0
  67. package/node_modules/@sinclair/typebox/build/esm/system/index.mjs +1 -0
  68. package/node_modules/@sinclair/typebox/build/esm/type/composite/composite.mjs +2 -2
  69. package/node_modules/@sinclair/typebox/build/esm/type/const/const.mjs +3 -3
  70. package/node_modules/@sinclair/typebox/build/esm/type/instantiate/instantiate.mjs +2 -2
  71. package/node_modules/@sinclair/typebox/build/esm/type/mapped/mapped.mjs +3 -3
  72. package/node_modules/@sinclair/typebox/build/esm/type/module/compute.mjs +2 -2
  73. package/node_modules/@sinclair/typebox/build/esm/type/object/object.d.mts +2 -2
  74. package/node_modules/@sinclair/typebox/build/esm/type/object/object.mjs +18 -2
  75. package/node_modules/@sinclair/typebox/build/esm/type/omit/omit.mjs +3 -3
  76. package/node_modules/@sinclair/typebox/build/esm/type/partial/partial.mjs +3 -3
  77. package/node_modules/@sinclair/typebox/build/esm/type/pick/pick.mjs +3 -3
  78. package/node_modules/@sinclair/typebox/build/esm/type/record/record.mjs +3 -3
  79. package/node_modules/@sinclair/typebox/build/esm/type/required/required.mjs +3 -3
  80. package/node_modules/@sinclair/typebox/build/esm/type/type/json.mjs +2 -2
  81. package/node_modules/@sinclair/typebox/build/esm/value/delta/delta.mjs +4 -4
  82. package/node_modules/@sinclair/typebox/build/esm/value/parse/parse.d.mts +1 -1
  83. package/node_modules/@sinclair/typebox/package.json +1 -1
  84. package/package.json +9 -5
  85. package/packages/types/dist/index.d.ts +5 -0
  86. package/packages/types/dist/index.js +5 -0
@@ -2,7 +2,7 @@ import { readFileSync, } from "node:fs";
2
2
  import { getCACertificates, } from "node:tls";
3
3
  import { Value, } from "@sinclair/typebox/value";
4
4
  import { safeParseSchema, } from "./schemas.js";
5
- import { classifyDataikuError, DataikuError, } from "./errors.js";
5
+ import { classifyDataikuError, ClientValidationError, DataikuError, } from "./errors.js";
6
6
  import { AnalysesResource, } from "./resources/analyses.js";
7
7
  import { ApiDeployerResource, } from "./resources/api-deployer.js";
8
8
  import { ApiServicesResource, } from "./resources/api-services.js";
@@ -135,6 +135,22 @@ function buildFetchTlsOptions(config) {
135
135
  }
136
136
  return tls;
137
137
  }
138
+ /**
139
+ * True when the URL embeds userinfo (`https://user:password@host`). Embedded
140
+ * credentials are rejected up front so they can never reach the canonical base
141
+ * URL that gets persisted in recorded artifacts (cleanup ledgers, permission
142
+ * snapshots) or echoed in diagnostics. Malformed URLs fall through to the
143
+ * existing request-time failure.
144
+ */
145
+ function hasEmbeddedUserinfo(url) {
146
+ try {
147
+ const parsed = new URL(url);
148
+ return parsed.username !== "" || parsed.password !== "";
149
+ }
150
+ catch {
151
+ return false;
152
+ }
153
+ }
138
154
  /* ------------------------------------------------------------------ */
139
155
  /* Client */
140
156
  /* ------------------------------------------------------------------ */
@@ -297,6 +313,9 @@ export class DataikuClient {
297
313
  if (!url || !apiKey) {
298
314
  throw new Error("Dataiku URL and API key are required: pass {url, apiKey} or set DATAIKU_URL/DATAIKU_API_KEY");
299
315
  }
316
+ if (hasEmbeddedUserinfo(url)) {
317
+ throw new ClientValidationError("Dataiku URL must not contain embedded credentials (userinfo). Authenticate with an API key instead.", "validation_failed", "Pass the DSS base URL without a username or password.", { urlHasEmbeddedUserinfo: true, });
318
+ }
300
319
  this.baseUrl = url.replace(/\/+$/, "");
301
320
  this.apiKey = apiKey;
302
321
  this.defaultProjectKey = config?.projectKey?.trim() || undefined;
@@ -308,6 +327,14 @@ export class DataikuClient {
308
327
  this.onTrace = config?.onTrace ?? defaultTrace;
309
328
  this.onValidationWarning = config?.onValidationWarning ?? defaultValidationWarning;
310
329
  }
330
+ /**
331
+ * Canonical request base URL: the exact prefix every HTTP verb concatenates
332
+ * paths onto (trimmed, trailing slashes stripped). Callers that must bind a
333
+ * recorded artifact to its originating DSS server compare against this.
334
+ */
335
+ getBaseUrl() {
336
+ return this.baseUrl;
337
+ }
311
338
  getRequestTimeoutMs() {
312
339
  return this.requestTimeoutMs;
313
340
  }
@@ -379,12 +406,21 @@ export class DataikuClient {
379
406
  const usable = truncated ? trimToUtf8Boundary(collected) : collected;
380
407
  return { text: new TextDecoder().decode(usable), truncated, };
381
408
  }
382
- async post(path, body) {
409
+ /**
410
+ * POST with optional transient retry. Only opt in when repeating the request
411
+ * is safe: the server may have accepted an attempt whose response was lost.
412
+ */
413
+ async post(path, body, options) {
414
+ const retryMaxAttempts = options?.retryMaxAttempts;
415
+ if (retryMaxAttempts !== undefined
416
+ && (!Number.isInteger(retryMaxAttempts) || retryMaxAttempts < 1)) {
417
+ throw new ClientValidationError("retryMaxAttempts must be a positive integer.");
418
+ }
383
419
  const res = await this.fetchWithRetry(`${this.baseUrl}${path}`, {
384
420
  method: "POST",
385
421
  headers: this.getHeaders(),
386
422
  body: body !== undefined ? JSON.stringify(body) : undefined,
387
- });
423
+ }, retryMaxAttempts);
388
424
  return this.parseJsonResponse(res);
389
425
  }
390
426
  async postText(path, body) {
@@ -511,10 +547,14 @@ export class DataikuClient {
511
547
  }
512
548
  }
513
549
  /* ---- private: retry loop ---- */
514
- async fetchWithRetry(url, init) {
550
+ async fetchWithRetry(url, init, retryMaxAttempts) {
515
551
  const method = (init.method ?? "GET").toUpperCase();
516
- const retryEnabled = shouldRetryMethod(method);
517
- const maxAttempts = retryEnabled ? this.retryMaxAttempts : 1;
552
+ const retryEnabled = shouldRetryMethod(method) || retryMaxAttempts !== undefined;
553
+ const maxAttempts = retryMaxAttempts === undefined
554
+ ? retryEnabled
555
+ ? this.retryMaxAttempts
556
+ : 1
557
+ : Math.min(retryMaxAttempts, MAX_RETRY_ATTEMPTS_CAP);
518
558
  const delaysMs = [];
519
559
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
520
560
  let timedOut = false;
@@ -1,5 +1,5 @@
1
1
  export type DataikuErrorCategory = "not_found" | "forbidden" | "validation" | "transient" | "unknown";
2
- export type StableErrorCode = "usage_error" | "unknown_flag" | "missing_required_arg" | "missing_required_flag" | "invalid_enum" | "not_found" | "permission_denied" | "validation_failed" | "transient" | "long_running_failure" | "internal_error";
2
+ export type StableErrorCode = "usage_error" | "unknown_flag" | "missing_required_arg" | "missing_required_flag" | "invalid_enum" | "not_found" | "permission_denied" | "validation_failed" | "transient" | "long_running_failure" | "command_result_failure" | "assertion_failed" | "ambiguous_outcome" | "internal_error";
3
3
  export declare function dataikuErrorCode(category: DataikuErrorCategory): StableErrorCode;
4
4
  export interface DataikuErrorTaxonomy {
5
5
  category: DataikuErrorCategory;
@@ -1,3 +1,4 @@
1
+ import { type DataikuErrorCategory } from "../errors.js";
1
2
  import { BaseResource } from "./base.js";
2
3
  export interface AppListItem extends Record<string, unknown> {
3
4
  appId?: string;
@@ -20,6 +21,141 @@ export interface BusinessAppInstanceUserPermissions extends Record<string, unkno
20
21
  readProjectContent?: boolean;
21
22
  writeProjectContent?: boolean;
22
23
  }
24
+ export interface DeleteAppInstanceOptions {
25
+ /** Refuse deletion unless the current creationTag hashes to this value. */
26
+ expectedProjectIncarnationHash?: string;
27
+ }
28
+ export type AppManifestReferenceKind = "scenario" | "folder" | "variable";
29
+ /**
30
+ * Source-verifiable app-manifest reference locations. Validation is scoped to
31
+ * homepage tiles so identically named keys in arbitrary custom-form `config`
32
+ * objects are never treated as references.
33
+ */
34
+ export declare const APP_MANIFEST_REFERENCE_KEYS: Record<AppManifestReferenceKind, string[]>;
35
+ export interface AppManifestReference {
36
+ kind: AppManifestReferenceKind;
37
+ path: string;
38
+ value: string;
39
+ exists: boolean;
40
+ }
41
+ export interface AppManifestCheck {
42
+ kind: AppManifestReferenceKind;
43
+ status: "ok" | "failed" | "skipped";
44
+ checked: number;
45
+ malformed: number;
46
+ missing: number;
47
+ }
48
+ export interface AppManifestIssue {
49
+ code: "INVALID_MANIFEST_ROOT" | "INVALID_REFERENCE_VALUE" | "MISSING_SCENARIO" | "MISSING_FOLDER" | "MISSING_VARIABLE";
50
+ path: string;
51
+ message: string;
52
+ }
53
+ export interface AppManifestValidationResult {
54
+ valid: boolean;
55
+ projectKey: string;
56
+ manifestHash: string;
57
+ references: AppManifestReference[];
58
+ checks: AppManifestCheck[];
59
+ errors: AppManifestIssue[];
60
+ }
61
+ export interface AppManifestPathDifference {
62
+ path: string;
63
+ kind: "added" | "removed" | "changed";
64
+ template?: unknown;
65
+ instance?: unknown;
66
+ }
67
+ export interface AppManifestComparisonResult {
68
+ appId: string;
69
+ projectKey: string;
70
+ templateHash: string;
71
+ instanceHash: string;
72
+ identical: boolean;
73
+ omittedFields: string[];
74
+ differences: AppManifestPathDifference[];
75
+ }
76
+ /**
77
+ * Raw app-manifest version markers. DSS renders the Application header version
78
+ * from the manifest's own `version` string, so that field (with its optional
79
+ * `versionNotes`) is the authoritative state; no separate publish/release
80
+ * transaction exists on the public API.
81
+ */
82
+ export interface AppManifestVersionState {
83
+ projectKey: string;
84
+ projectAppType: string | null;
85
+ version: string | null;
86
+ versionNotes: string | null;
87
+ manifestHash: string;
88
+ }
89
+ export interface AppManifestVersionUpdate {
90
+ /** New manifest `version` string. Must be non-empty when supplied. */
91
+ version?: string;
92
+ /** New manifest `versionNotes` string. May be empty to clear existing notes. */
93
+ versionNotes?: string;
94
+ /**
95
+ * 64-hex manifest hash the caller last read, used as a non-atomic stale-read
96
+ * guard: the write is refused when the manifest already differs at the
97
+ * verification read. It cannot prevent a concurrent write that lands between
98
+ * that read and this write.
99
+ */
100
+ expectHash?: string;
101
+ /** Compute and report the change without issuing a write. */
102
+ dryRun?: boolean;
103
+ }
104
+ /**
105
+ * The only concurrency control the public app-manifest endpoint permits. DSS
106
+ * exposes no conditional write (no ETag, no If-Match, no version token), so
107
+ * `expectHash` can only detect a hash that is already stale when it is checked;
108
+ * it can never turn the subsequent PUT into a conditional write. Every
109
+ * manifest-version write
110
+ * result reports this literal so callers can machine-check the guarantee they
111
+ * actually get instead of inferring one from prose.
112
+ */
113
+ export declare const APP_MANIFEST_CONCURRENCY_CONTROL = "client-side-non-atomic-stale-read-check";
114
+ export type AppManifestConcurrencyControl = typeof APP_MANIFEST_CONCURRENCY_CONTROL;
115
+ export type AppManifestVersionWriteOutcome = "persisted" | "dry-run" | "unchanged" | "indeterminate";
116
+ export interface AppManifestVersionWriteFailure {
117
+ /** Sanitized summary; never echoes response bodies or secrets. */
118
+ error: string;
119
+ errorStatus: number | null;
120
+ errorCategory: DataikuErrorCategory | null;
121
+ remediation: string;
122
+ }
123
+ export interface AppManifestVersionWriteResult {
124
+ projectKey: string;
125
+ projectAppType: string | null;
126
+ /** Fields the caller asked to set; null means "left untouched". */
127
+ requested: {
128
+ version: string | null;
129
+ versionNotes: string | null;
130
+ };
131
+ /** True when the desired manifest differs from the manifest that was read. */
132
+ changed: boolean;
133
+ dryRun: boolean;
134
+ /**
135
+ * True only after a write was accepted and re-read as exactly `desired`.
136
+ * False for unchanged/dry-run results. Null when the write outcome could
137
+ * not be observed (transport loss, transient/server failure, or failed
138
+ * verification read) — the write may still have landed.
139
+ */
140
+ persisted: boolean | null;
141
+ outcome: AppManifestVersionWriteOutcome;
142
+ before: AppManifestVersionState;
143
+ desired: AppManifestVersionState;
144
+ /**
145
+ * Last state observed on the server; equals `before` when nothing was
146
+ * written. Null when the write outcome could not be observed.
147
+ */
148
+ after: AppManifestVersionState | null;
149
+ /**
150
+ * Always `"client-side-non-atomic-stale-read-check"`. The GET→PUT window is
151
+ * unguarded: this client can overwrite a competing write that lands after its
152
+ * GET, and that lost update is not detectable when this client's PUT wins.
153
+ * The final read only verifies the state observed after this client's PUT.
154
+ */
155
+ concurrencyControl: AppManifestConcurrencyControl;
156
+ /** Present only when outcome is "indeterminate". */
157
+ error?: AppManifestVersionWriteFailure;
158
+ }
23
159
  export declare class ApplicationsResource extends BaseResource {
24
160
  /** List all Dataiku Apps. */
25
161
  listApps(): Promise<AppListItem[]>;
@@ -31,10 +167,64 @@ export declare class ApplicationsResource extends BaseResource {
31
167
  createInstance(appId: string, body: Record<string, unknown>): Promise<AppInstanceRef>;
32
168
  /** Get the manifest for an app instance project. */
33
169
  getInstanceManifest(projectKey?: string): Promise<Record<string, unknown>>;
170
+ private getProjectDetails;
171
+ private getProjectAppType;
172
+ /**
173
+ * Shared app-manifest write guard: DSS only accepts manifest saves for app
174
+ * template projects, so every mutation path rejects instances and ordinary
175
+ * projects before issuing a PUT.
176
+ */
177
+ private requireAppTemplateForManifestSave;
34
178
  /** Save the manifest for a Dataiku App template project (rejects classic app-instance projects). */
35
179
  saveInstanceManifest(manifest: Record<string, unknown>, projectKey?: string): Promise<void>;
180
+ /**
181
+ * Read the manifest plus its derived version state. The project type comes
182
+ * from the manifest when DSS includes it and from project details otherwise,
183
+ * so ordinary reads cost a single request.
184
+ */
185
+ private readManifestVersion;
186
+ /**
187
+ * Read the authoritative app-manifest version markers. Allowed for app
188
+ * templates and app instances alike: instance verification compares an
189
+ * instance's inherited `version` against its template.
190
+ */
191
+ getManifestVersion(projectKey?: string): Promise<AppManifestVersionState>;
192
+ /**
193
+ * Set the manifest `version`/`versionNotes` strings on an app template.
194
+ *
195
+ * This is a manifest save, not a publish transaction: DSS exposes no release
196
+ * endpoint, and new instances simply inherit whatever version string the
197
+ * template carries at creation time.
198
+ *
199
+ * The write is not atomic, and `expectHash` cannot change that: the
200
+ * endpoint takes every PUT unconditionally, so `expectHash` only rejects a
201
+ * caller whose hash is already stale at the verification read. This client
202
+ * can overwrite a writer that commits after that read but before this PUT,
203
+ * and a final state equal to this client's desired manifest cannot reveal
204
+ * that lost update. The final read still prevents a different state observed
205
+ * after the PUT from being reported as persisted.
206
+ */
207
+ setManifestVersion(update: AppManifestVersionUpdate, projectKey?: string): Promise<AppManifestVersionWriteResult>;
208
+ /**
209
+ * Validate source-verifiable references in app homepage tiles:
210
+ * SCENARIO_RUN scenario IDs, DOWNLOAD_FILE managed-folder IDs, and runtime
211
+ * form parameter names (which DSS maps to project variables). Arbitrary
212
+ * custom-form `config` objects remain opaque. The manifest is never mutated.
213
+ * Missing/malformed references are returned deterministically; a reference
214
+ * kind with no applicable tiles does not trigger its project API.
215
+ */
216
+ validateAppManifest(manifest: unknown, projectKey?: string): Promise<AppManifestValidationResult>;
217
+ /**
218
+ * Compare a Dataiku App template manifest with the app-instance manifest of
219
+ * the target project. Root-level project identity fields in
220
+ * APP_MANIFEST_COMPARE_OMITTED_FIELDS are omitted from both sides; every
221
+ * other field — including ones the SDK does not model — is compared.
222
+ * Hashes cover the omitted-field-normalized states; differences are emitted
223
+ * as deterministic, path-ordered entries.
224
+ */
225
+ compareAppManifest(appId: string, projectKey?: string): Promise<AppManifestComparisonResult>;
36
226
  /** Delete an app instance project. */
37
- deleteInstance(projectKey?: string): Promise<void>;
227
+ deleteInstance(projectKey?: string, options?: DeleteAppInstanceOptions): Promise<void>;
38
228
  /** List all Business Apps. */
39
229
  listBusinessApps(): Promise<BusinessAppListItem[]>;
40
230
  /** Get Business App details. */