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.
- package/README.md +6 -4
- package/dist/build-metadata.json +1 -1
- package/dist/src/cli/command-syntax.json +14524 -0
- package/dist/src/cli/commands/analysis.js +6 -9
- package/dist/src/cli/commands/api-deployer.js +15 -33
- package/dist/src/cli/commands/api-service.js +17 -27
- package/dist/src/cli/commands/app.js +22 -39
- package/dist/src/cli/commands/bundle.js +15 -24
- package/dist/src/cli/commands/business-app.js +10 -17
- package/dist/src/cli/commands/code-env.js +23 -36
- package/dist/src/cli/commands/code.js +5 -5
- package/dist/src/cli/commands/connection.js +12 -22
- package/dist/src/cli/commands/continuous-activity.js +6 -9
- package/dist/src/cli/commands/dashboard.js +8 -13
- package/dist/src/cli/commands/data-collection.js +9 -16
- package/dist/src/cli/commands/data-quality.js +14 -26
- package/dist/src/cli/commands/dataset.js +28 -51
- package/dist/src/cli/commands/discussion.js +7 -10
- package/dist/src/cli/commands/doctor.js +3 -3
- package/dist/src/cli/commands/flow-zone.js +8 -17
- package/dist/src/cli/commands/folder.js +12 -20
- package/dist/src/cli/commands/future.js +7 -10
- package/dist/src/cli/commands/group.js +7 -11
- package/dist/src/cli/commands/insight.js +7 -11
- package/dist/src/cli/commands/job.js +17 -25
- package/dist/src/cli/commands/knowledge-bank.js +5 -6
- package/dist/src/cli/commands/llm.js +5 -8
- package/dist/src/cli/commands/macro.js +11 -18
- package/dist/src/cli/commands/meaning.js +7 -11
- package/dist/src/cli/commands/metrics.js +7 -10
- package/dist/src/cli/commands/ml-task.js +12 -20
- package/dist/src/cli/commands/model-evaluation-store.js +7 -11
- package/dist/src/cli/commands/notebook.js +15 -27
- package/dist/src/cli/commands/plugin.js +35 -68
- package/dist/src/cli/commands/project-deployer.js +10 -22
- package/dist/src/cli/commands/project-folder.js +10 -17
- package/dist/src/cli/commands/project-git.js +39 -71
- package/dist/src/cli/commands/project-library.js +15 -23
- package/dist/src/cli/commands/project.js +9 -26
- package/dist/src/cli/commands/recipe.d.ts +16 -0
- package/dist/src/cli/commands/recipe.js +83 -94
- package/dist/src/cli/commands/saved-model.js +23 -39
- package/dist/src/cli/commands/scenario.js +21 -35
- package/dist/src/cli/commands/sql.js +4 -4
- package/dist/src/cli/commands/statistics.js +11 -18
- package/dist/src/cli/commands/streaming-endpoint.js +7 -11
- package/dist/src/cli/commands/user.js +11 -22
- package/dist/src/cli/commands/variable.js +3 -4
- package/dist/src/cli/commands/webapp.js +10 -16
- package/dist/src/cli/commands/wiki.js +9 -13
- package/dist/src/cli/commands/workspace.js +8 -14
- package/dist/src/cli/contract.d.ts +5 -15
- package/dist/src/cli/contract.js +179 -461
- package/dist/src/cli/doctor.js +1 -0
- package/dist/src/cli/env.d.ts +2 -0
- package/dist/src/cli/env.js +15 -1
- package/dist/src/cli/helpers/app-permissions.js +2 -1
- package/dist/src/cli/helpers/flow-zone.js +2 -1
- package/dist/src/cli/helpers/recipe.js +2 -1
- package/dist/src/cli/output-schemas.d.ts +24 -0
- package/dist/src/cli/output-schemas.js +233 -0
- package/dist/src/cli/syntax.d.ts +83 -0
- package/dist/src/cli/syntax.js +217 -0
- package/dist/src/cli/types.d.ts +3 -0
- package/dist/src/cli/usage.d.ts +1 -1
- package/dist/src/cli/usage.js +4 -2
- package/dist/src/cli/version.js +2 -0
- package/dist/src/cli.js +37 -64
- package/dist/src/client.d.ts +10 -1
- package/dist/src/client.js +27 -11
- package/dist/src/errors.d.ts +12 -1
- package/dist/src/errors.js +34 -7
- package/dist/src/generated/action-output-schemas.json +1735 -0
- package/dist/src/index.d.ts +1 -3
- package/dist/src/index.js +2 -2
- package/dist/src/resources/api-services.js +2 -3
- package/dist/src/resources/applications.js +2 -4
- package/dist/src/resources/code-envs.js +2 -2
- package/dist/src/resources/data-quality.js +4 -3
- package/dist/src/resources/datasets.d.ts +13 -0
- package/dist/src/resources/datasets.js +42 -8
- package/dist/src/resources/flow-zones.d.ts +6 -2
- package/dist/src/resources/flow-zones.js +10 -5
- package/dist/src/resources/folders.js +4 -2
- package/dist/src/resources/futures.d.ts +2 -0
- package/dist/src/resources/futures.js +39 -27
- package/dist/src/resources/insights.js +3 -2
- package/dist/src/resources/jobs.js +41 -31
- package/dist/src/resources/macros.js +2 -4
- package/dist/src/resources/ml-tasks.js +28 -19
- package/dist/src/resources/notebooks.js +2 -3
- package/dist/src/resources/plugins.d.ts +0 -2
- package/dist/src/resources/plugins.js +0 -3
- package/dist/src/resources/project-git.d.ts +6 -12
- package/dist/src/resources/project-git.js +31 -58
- package/dist/src/resources/project-library.d.ts +0 -2
- package/dist/src/resources/project-library.js +2 -7
- package/dist/src/resources/projects.d.ts +4 -2
- package/dist/src/resources/projects.js +37 -36
- package/dist/src/resources/recipes.d.ts +28 -0
- package/dist/src/resources/recipes.js +116 -84
- package/dist/src/resources/sql.js +8 -3
- package/dist/src/resources/statistics.js +2 -1
- package/dist/src/resources/variables.js +2 -1
- package/dist/src/resources/webapps.js +2 -3
- package/dist/src/schemas.d.ts +1 -2
- package/dist/src/schemas.js +1 -1
- package/dist/src/skill.js +6 -7
- package/dist/src/utils/cleanup-ledger.js +9 -8
- package/dist/src/utils/git-reference.d.ts +12 -0
- package/dist/src/utils/git-reference.js +38 -0
- package/dist/src/utils/polling.d.ts +3 -0
- package/dist/src/utils/polling.js +3 -0
- package/dist/src/utils/project-archive.js +2 -2
- package/dist/src/utils/stable-hash.d.ts +3 -1
- package/dist/src/utils/stable-hash.js +2 -0
- package/package.json +8 -8
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +3 -2
- package/skills/dataiku-dss/references/app-releases.md +2 -3
- package/skills/dataiku-dss/references/authentication.md +7 -7
- package/skills/dataiku-dss/references/coding.md +1 -1
- package/skills/dataiku-dss/references/mutations.md +1 -1
- package/skills/dataiku-dss/references/troubleshooting.md +2 -2
- package/dist/src/utils/pagination.d.ts +0 -8
- package/dist/src/utils/pagination.js +0 -23
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { writeFile, } from "node:fs/promises";
|
|
2
2
|
import { resolve, } from "node:path";
|
|
3
|
-
import { DataikuError, } from "../errors.js";
|
|
3
|
+
import { ClientValidationError, DataikuError, nonJsonResponseBody, unexpectedResponseError, } from "../errors.js";
|
|
4
4
|
import { ProjectMetadataSchema, RecipeSummaryArraySchema, } from "../schemas.js";
|
|
5
5
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
6
6
|
import { sanitizeFileName, } from "../utils/sanitize.js";
|
|
@@ -25,7 +25,13 @@ function asRecord(value) {
|
|
|
25
25
|
function parseRecipePayload(payload) {
|
|
26
26
|
if (!payload)
|
|
27
27
|
return {};
|
|
28
|
-
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = JSON.parse(payload);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
throw new DataikuError(200, "Invalid JSON response", nonJsonResponseBody(payload));
|
|
34
|
+
}
|
|
29
35
|
return asRecord(parsed) ?? {};
|
|
30
36
|
}
|
|
31
37
|
function parseJoinKeyPairs(values, optionName = "joinOn") {
|
|
@@ -41,8 +47,9 @@ function parseJoinKeyPairs(values, optionName = "joinOn") {
|
|
|
41
47
|
}
|
|
42
48
|
const left = token.slice(0, eq).trim();
|
|
43
49
|
const right = token.slice(eq + 1).trim();
|
|
44
|
-
if (!left || !right)
|
|
45
|
-
throw new
|
|
50
|
+
if (!left || !right) {
|
|
51
|
+
throw new ClientValidationError(`${optionName} values must use COL or LEFT=RIGHT.`, "validation_failed");
|
|
52
|
+
}
|
|
46
53
|
pairs.push({ left, right, });
|
|
47
54
|
}
|
|
48
55
|
return pairs;
|
|
@@ -57,7 +64,7 @@ function normalizeFuzzyDistance(value) {
|
|
|
57
64
|
|| normalized === "EUCLIDEAN") {
|
|
58
65
|
return normalized;
|
|
59
66
|
}
|
|
60
|
-
throw new
|
|
67
|
+
throw new ClientValidationError("fuzzyDistance must be one of DAMERAU_LEVENSHTEIN, HAMMING, JACCARD, COSINE, or EUCLIDEAN.", "validation_failed");
|
|
61
68
|
}
|
|
62
69
|
function normalizeJoinType(value) {
|
|
63
70
|
const normalized = typeof value === "string" && value.trim().length > 0
|
|
@@ -67,7 +74,7 @@ function normalizeJoinType(value) {
|
|
|
67
74
|
|| normalized === "FULL") {
|
|
68
75
|
return normalized;
|
|
69
76
|
}
|
|
70
|
-
throw new
|
|
77
|
+
throw new ClientValidationError("joinType must be one of LEFT, INNER, RIGHT, or FULL.", "validation_failed");
|
|
71
78
|
}
|
|
72
79
|
function recipeVirtualInputs(payload, inputCount) {
|
|
73
80
|
const current = Array.isArray(payload.virtualInputs) ? payload.virtualInputs : [];
|
|
@@ -208,7 +215,7 @@ function rewriteSqlTableReferences(payload, rewrites) {
|
|
|
208
215
|
return `${keyword}${space}[${escapedTo}]`;
|
|
209
216
|
}
|
|
210
217
|
if (!bareIdentifierPattern.test(to)) {
|
|
211
|
-
throw new
|
|
218
|
+
throw new ClientValidationError(`Unsafe SQL rewrite target for ${from}: ${to}`, "validation_failed");
|
|
212
219
|
}
|
|
213
220
|
return `${keyword}${space}${to}`;
|
|
214
221
|
});
|
|
@@ -292,6 +299,96 @@ function shouldRetryRecipeCreateWithOutputProvisioning(error) {
|
|
|
292
299
|
// ---------------------------------------------------------------------------
|
|
293
300
|
// Resource
|
|
294
301
|
// ---------------------------------------------------------------------------
|
|
302
|
+
/**
|
|
303
|
+
* Pure request construction for POST /recipes/ (no DSS calls). Shared with
|
|
304
|
+
* `recipe create --plan` so the plan equals the request the command sends.
|
|
305
|
+
*/
|
|
306
|
+
export function buildRecipeCreateRequest(opts, pk) {
|
|
307
|
+
const { type, payload, outputConnection: rawConnection, joinType: rawJoinType, } = opts;
|
|
308
|
+
const outputFolder = asString(opts.outputFolder);
|
|
309
|
+
// Build inputs/outputs from simple form (inputDatasets + outputDataset) or
|
|
310
|
+
// advanced form (inputs + outputs); both may coexist — simple form wins when
|
|
311
|
+
// the advanced form is absent.
|
|
312
|
+
const inputDatasets = asStringArray(opts.inputDatasets);
|
|
313
|
+
const requestedOutputDataset = asString(opts.outputDataset);
|
|
314
|
+
let inputs = asRecord(opts.inputs);
|
|
315
|
+
let outputs = asRecord(opts.outputs);
|
|
316
|
+
if (!inputs) {
|
|
317
|
+
inputs = {
|
|
318
|
+
main: {
|
|
319
|
+
items: inputDatasets?.map((ref) => ({ ref, deps: [], })) ?? [],
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
// Auto-generate name if not provided
|
|
324
|
+
const outputNameForDefaultRecipe = requestedOutputDataset ?? outputFolder;
|
|
325
|
+
const name = opts.name ?? (type && outputNameForDefaultRecipe
|
|
326
|
+
? `${type}_${outputNameForDefaultRecipe}`
|
|
327
|
+
: undefined);
|
|
328
|
+
const temporaryOutputDataset = outputFolder && !requestedOutputDataset && name
|
|
329
|
+
? `${name}_folder_output_marker`
|
|
330
|
+
: undefined;
|
|
331
|
+
const outputDataset = requestedOutputDataset ?? temporaryOutputDataset;
|
|
332
|
+
if (!outputs && outputDataset) {
|
|
333
|
+
outputs = {
|
|
334
|
+
main: {
|
|
335
|
+
items: [{ ref: outputDataset, appendMode: false, },],
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (!type || !name || !outputs) {
|
|
340
|
+
throw new ClientValidationError("type and outputDataset/outputFolder or (name + outputs) are required for create.", "validation_failed");
|
|
341
|
+
}
|
|
342
|
+
const joinCols = typeof opts.joinOn === "string" ? [opts.joinOn,] : asStringArray(opts.joinOn);
|
|
343
|
+
const joinKeys = type === "join" && joinCols?.length
|
|
344
|
+
? parseJoinKeyPairs(joinCols)
|
|
345
|
+
: undefined;
|
|
346
|
+
const fuzzyOn = opts.fuzzyOn ?? (type === "fuzzyjoin" ? opts.joinOn : undefined);
|
|
347
|
+
const fuzzyCols = typeof fuzzyOn === "string" ? [fuzzyOn,] : asStringArray(fuzzyOn);
|
|
348
|
+
const fuzzyKeys = type === "fuzzyjoin" && fuzzyCols?.length
|
|
349
|
+
? parseJoinKeyPairs(fuzzyCols, "fuzzyOn")
|
|
350
|
+
: undefined;
|
|
351
|
+
const normalizedJoinType = joinKeys?.length || fuzzyKeys?.length
|
|
352
|
+
? normalizeJoinType(rawJoinType)
|
|
353
|
+
: undefined;
|
|
354
|
+
const fuzzyDistance = fuzzyKeys?.length ? normalizeFuzzyDistance(opts.fuzzyDistance) : undefined;
|
|
355
|
+
const fuzzyThreshold = opts.fuzzyThreshold ?? 1;
|
|
356
|
+
if (!Number.isFinite(fuzzyThreshold)) {
|
|
357
|
+
throw new ClientValidationError("fuzzyThreshold must be a finite number.", "validation_failed");
|
|
358
|
+
}
|
|
359
|
+
const recipePrototype = {
|
|
360
|
+
type,
|
|
361
|
+
name,
|
|
362
|
+
projectKey: pk,
|
|
363
|
+
...(type === "fuzzyjoin" ? {} : { inputs, }),
|
|
364
|
+
outputs,
|
|
365
|
+
};
|
|
366
|
+
const creationSettings = {};
|
|
367
|
+
if (payload !== undefined) {
|
|
368
|
+
creationSettings.script = payload;
|
|
369
|
+
}
|
|
370
|
+
if (type === "fuzzyjoin") {
|
|
371
|
+
creationSettings.virtualInputs = recipeInputItems({ inputs, }).map((item) => item.ref);
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
creationSettings,
|
|
375
|
+
fuzzyDistance,
|
|
376
|
+
fuzzyKeys,
|
|
377
|
+
fuzzyThreshold,
|
|
378
|
+
inputDatasets,
|
|
379
|
+
inputs,
|
|
380
|
+
joinKeys,
|
|
381
|
+
name,
|
|
382
|
+
normalizedJoinType,
|
|
383
|
+
outputFolder,
|
|
384
|
+
outputs,
|
|
385
|
+
payload,
|
|
386
|
+
rawConnection,
|
|
387
|
+
recipePrototype,
|
|
388
|
+
temporaryOutputDataset,
|
|
389
|
+
type,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
295
392
|
export class RecipesResource extends BaseResource {
|
|
296
393
|
/** List all recipes in a project. */
|
|
297
394
|
async list(projectKey) {
|
|
@@ -433,7 +530,7 @@ export class RecipesResource extends BaseResource {
|
|
|
433
530
|
const { recipe, } = await this.get(recipeName, { projectKey: pk, });
|
|
434
531
|
const outputItems = recipeOutputItems(recipe);
|
|
435
532
|
if (outputItems.length === 0) {
|
|
436
|
-
throw new
|
|
533
|
+
throw new ClientValidationError(`Recipe "${recipeName}" has no output items to build.`, "validation_failed");
|
|
437
534
|
}
|
|
438
535
|
const [datasets, folders,] = await Promise.all([
|
|
439
536
|
this.client.datasets.list(pk),
|
|
@@ -483,7 +580,7 @@ export class RecipesResource extends BaseResource {
|
|
|
483
580
|
}
|
|
484
581
|
const isDataset = datasetNames.has(item.ref);
|
|
485
582
|
if (isDataset && folderId) {
|
|
486
|
-
throw new
|
|
583
|
+
throw new ClientValidationError(`Recipe "${recipeName}" output "${item.ref}" matches both a dataset and a managed folder. Add an explicit output type to the recipe definition or build the target directly with --target-type.`, "validation_failed");
|
|
487
584
|
}
|
|
488
585
|
if (folderId) {
|
|
489
586
|
return {
|
|
@@ -505,7 +602,7 @@ export class RecipesResource extends BaseResource {
|
|
|
505
602
|
partition: opts?.partition,
|
|
506
603
|
};
|
|
507
604
|
}
|
|
508
|
-
throw new
|
|
605
|
+
throw new ClientValidationError(`Recipe "${recipeName}" output "${item.ref}" was not found as a dataset or managed folder in project "${pk}".`, "validation_failed");
|
|
509
606
|
});
|
|
510
607
|
}
|
|
511
608
|
/** Run a recipe by building its resolved outputs. */
|
|
@@ -543,72 +640,7 @@ export class RecipesResource extends BaseResource {
|
|
|
543
640
|
async create(opts) {
|
|
544
641
|
const pk = this.resolveProjectKey(opts.projectKey);
|
|
545
642
|
const enc = encodeURIComponent(pk);
|
|
546
|
-
const {
|
|
547
|
-
const outputFolder = asString(opts.outputFolder);
|
|
548
|
-
// Build inputs/outputs from simple form (inputDatasets + outputDataset) or
|
|
549
|
-
// advanced form (inputs + outputs); both may coexist — simple form wins when
|
|
550
|
-
// the advanced form is absent.
|
|
551
|
-
const inputDatasets = asStringArray(opts.inputDatasets);
|
|
552
|
-
const requestedOutputDataset = asString(opts.outputDataset);
|
|
553
|
-
let inputs = asRecord(opts.inputs);
|
|
554
|
-
let outputs = asRecord(opts.outputs);
|
|
555
|
-
if (!inputs) {
|
|
556
|
-
inputs = {
|
|
557
|
-
main: {
|
|
558
|
-
items: inputDatasets?.map((ref) => ({ ref, deps: [], })) ?? [],
|
|
559
|
-
},
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
// Auto-generate name if not provided
|
|
563
|
-
const outputNameForDefaultRecipe = requestedOutputDataset ?? outputFolder;
|
|
564
|
-
const name = opts.name ?? (type && outputNameForDefaultRecipe
|
|
565
|
-
? `${type}_${outputNameForDefaultRecipe}`
|
|
566
|
-
: undefined);
|
|
567
|
-
const temporaryOutputDataset = outputFolder && !requestedOutputDataset && name
|
|
568
|
-
? `${name}_folder_output_marker`
|
|
569
|
-
: undefined;
|
|
570
|
-
const outputDataset = requestedOutputDataset ?? temporaryOutputDataset;
|
|
571
|
-
if (!outputs && outputDataset) {
|
|
572
|
-
outputs = {
|
|
573
|
-
main: {
|
|
574
|
-
items: [{ ref: outputDataset, appendMode: false, },],
|
|
575
|
-
},
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
if (!type || !name || !outputs) {
|
|
579
|
-
throw new Error("type and outputDataset/outputFolder or (name + outputs) are required for create.");
|
|
580
|
-
}
|
|
581
|
-
const joinCols = typeof opts.joinOn === "string" ? [opts.joinOn,] : asStringArray(opts.joinOn);
|
|
582
|
-
const joinKeys = type === "join" && joinCols?.length
|
|
583
|
-
? parseJoinKeyPairs(joinCols)
|
|
584
|
-
: undefined;
|
|
585
|
-
const fuzzyOn = opts.fuzzyOn ?? (type === "fuzzyjoin" ? opts.joinOn : undefined);
|
|
586
|
-
const fuzzyCols = typeof fuzzyOn === "string" ? [fuzzyOn,] : asStringArray(fuzzyOn);
|
|
587
|
-
const fuzzyKeys = type === "fuzzyjoin" && fuzzyCols?.length
|
|
588
|
-
? parseJoinKeyPairs(fuzzyCols, "fuzzyOn")
|
|
589
|
-
: undefined;
|
|
590
|
-
const normalizedJoinType = joinKeys?.length || fuzzyKeys?.length
|
|
591
|
-
? normalizeJoinType(rawJoinType)
|
|
592
|
-
: undefined;
|
|
593
|
-
const fuzzyDistance = fuzzyKeys?.length ? normalizeFuzzyDistance(opts.fuzzyDistance) : undefined;
|
|
594
|
-
const fuzzyThreshold = opts.fuzzyThreshold ?? 1;
|
|
595
|
-
if (!Number.isFinite(fuzzyThreshold)) {
|
|
596
|
-
throw new Error("fuzzyThreshold must be a finite number.");
|
|
597
|
-
}
|
|
598
|
-
const recipePrototype = {
|
|
599
|
-
type,
|
|
600
|
-
name,
|
|
601
|
-
projectKey: pk,
|
|
602
|
-
...(type === "fuzzyjoin" ? {} : { inputs, }),
|
|
603
|
-
outputs,
|
|
604
|
-
};
|
|
605
|
-
const creationSettings = {};
|
|
606
|
-
if (payload !== undefined) {
|
|
607
|
-
creationSettings.script = payload;
|
|
608
|
-
}
|
|
609
|
-
if (type === "fuzzyjoin") {
|
|
610
|
-
creationSettings.virtualInputs = recipeInputItems({ inputs, }).map((item) => item.ref);
|
|
611
|
-
}
|
|
643
|
+
const { creationSettings, fuzzyDistance, fuzzyKeys, fuzzyThreshold, inputDatasets, inputs, joinKeys, name, normalizedJoinType, outputFolder, outputs, rawConnection, recipePrototype, temporaryOutputDataset, type, } = buildRecipeCreateRequest(opts, pk);
|
|
612
644
|
const createRecipe = () => this.client.post(`/public/api/projects/${enc}/recipes/`, {
|
|
613
645
|
recipePrototype,
|
|
614
646
|
creationSettings,
|
|
@@ -695,7 +727,7 @@ export class RecipesResource extends BaseResource {
|
|
|
695
727
|
const created = await createRecipe();
|
|
696
728
|
const receivedName = asString(created?.["name"]);
|
|
697
729
|
if (!receivedName) {
|
|
698
|
-
throw
|
|
730
|
+
throw unexpectedResponseError('DSS create-recipe response did not include the final recipe name (documented body: {"name": ...}).');
|
|
699
731
|
}
|
|
700
732
|
finalRecipeName = receivedName;
|
|
701
733
|
}
|
|
@@ -708,7 +740,7 @@ export class RecipesResource extends BaseResource {
|
|
|
708
740
|
const retryCreated = await createRecipe();
|
|
709
741
|
const retryName = asString(retryCreated?.["name"]);
|
|
710
742
|
if (!retryName) {
|
|
711
|
-
throw
|
|
743
|
+
throw unexpectedResponseError('DSS create-recipe response did not include the final recipe name (documented body: {"name": ...}).');
|
|
712
744
|
}
|
|
713
745
|
finalRecipeName = retryName;
|
|
714
746
|
}
|
|
@@ -869,11 +901,11 @@ export class RecipesResource extends BaseResource {
|
|
|
869
901
|
const current = await this.client.get(`/public/api/projects/${enc}/recipes/${rnEnc}`);
|
|
870
902
|
const currentRecipe = asRecord(current.recipe);
|
|
871
903
|
if (!currentRecipe) {
|
|
872
|
-
throw new
|
|
904
|
+
throw new ClientValidationError(`Recipe "${recipeName}" was not found or returned an empty definition.`, "validation_failed");
|
|
873
905
|
}
|
|
874
906
|
const misplacedRecipeFields = rootRecipeDefinitionFields(data);
|
|
875
907
|
if (misplacedRecipeFields.length > 0) {
|
|
876
|
-
throw new
|
|
908
|
+
throw new ClientValidationError(`Recipe fields ${misplacedRecipeFields.join(", ")} must be nested under "recipe". Example: {"recipe":{"outputs":{...},"params":{...}}}`, "validation_failed");
|
|
877
909
|
}
|
|
878
910
|
const mergedRecipe = deepMerge(currentRecipe, asRecord(data.recipe) ?? {});
|
|
879
911
|
const merged = { ...current, ...data, recipe: mergedRecipe, };
|
|
@@ -895,7 +927,7 @@ export class RecipesResource extends BaseResource {
|
|
|
895
927
|
if (opts.outputDataset !== undefined) {
|
|
896
928
|
const outputs = recipeOutputItems(source.recipe).filter((item) => item.type !== "MANAGED_FOLDER");
|
|
897
929
|
if (outputs.length !== 1 && Object.keys(outputRewrites).length === 0) {
|
|
898
|
-
throw new
|
|
930
|
+
throw new ClientValidationError(`Recipe "${sourceName}" has ${outputs.length} dataset outputs; pass explicit outputRewrites instead of outputDataset.`, "validation_failed");
|
|
899
931
|
}
|
|
900
932
|
if (outputs[0])
|
|
901
933
|
outputRewrites[outputs[0].ref] = opts.outputDataset;
|
|
@@ -910,7 +942,7 @@ export class RecipesResource extends BaseResource {
|
|
|
910
942
|
if (opts.copyOutputSettings === true
|
|
911
943
|
&& Object.keys(outputRewrites).length > 1
|
|
912
944
|
&& (opts.outputPath !== undefined || opts.metastoreTableName !== undefined)) {
|
|
913
|
-
throw new
|
|
945
|
+
throw new ClientValidationError("Cannot reuse --path or --metastore-table for multiple cloned output datasets; pass per-output settings in a separate step.", "validation_failed");
|
|
914
946
|
}
|
|
915
947
|
const payloadTextRewrites = {};
|
|
916
948
|
if (opts.payloadTextRewrites)
|
|
@@ -956,7 +988,7 @@ export class RecipesResource extends BaseResource {
|
|
|
956
988
|
projectKey: opts?.projectKey,
|
|
957
989
|
});
|
|
958
990
|
if (!result.payload) {
|
|
959
|
-
throw new
|
|
991
|
+
throw new ClientValidationError(`Recipe "${recipeName}" has no code payload.`, "validation_failed");
|
|
960
992
|
}
|
|
961
993
|
const safeRecipeName = sanitizeFileName(recipeName, "recipe");
|
|
962
994
|
const filePath = opts?.outputPath ?? resolve(process.cwd(), `${safeRecipeName}${inferRecipeCodeExtension(result.recipe.type)}`);
|
|
@@ -970,7 +1002,7 @@ export class RecipesResource extends BaseResource {
|
|
|
970
1002
|
projectKey: opts?.projectKey,
|
|
971
1003
|
});
|
|
972
1004
|
if (!result.payload) {
|
|
973
|
-
throw new
|
|
1005
|
+
throw new ClientValidationError(`Recipe "${recipeName}" has no code payload.`, "validation_failed");
|
|
974
1006
|
}
|
|
975
1007
|
return result.payload;
|
|
976
1008
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ClientValidationError, DataikuError, } from "../errors.js";
|
|
1
|
+
import { ClientValidationError, DataikuError, nonJsonResponseBody, SQL_QUERY_FAILED_MARKER, } from "../errors.js";
|
|
2
2
|
import { BaseResource, } from "./base.js";
|
|
3
3
|
const UNSUPPORTED_SQL_DATASET_CONNECTION_DETAIL = "neither of sql nor hdfs type";
|
|
4
4
|
/**
|
|
@@ -159,7 +159,12 @@ export class SqlResource extends BaseResource {
|
|
|
159
159
|
if (truncated) {
|
|
160
160
|
throw new DataikuError(0, "Response Too Large", `SQL stream results exceeded the ${maxBytes}-byte response limit and were not parsed.`);
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
try {
|
|
163
|
+
return JSON.parse(text);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
throw new DataikuError(200, "Invalid JSON response", nonJsonResponseBody(text));
|
|
167
|
+
}
|
|
163
168
|
}
|
|
164
169
|
/**
|
|
165
170
|
* Verify that a query finished successfully server-side.
|
|
@@ -173,7 +178,7 @@ export class SqlResource extends BaseResource {
|
|
|
173
178
|
throw new DataikuError(0, "Response Too Large", `SQL finish-streaming response exceeded the ${maxBytes}-byte response limit.`);
|
|
174
179
|
}
|
|
175
180
|
if (text.length > 0) {
|
|
176
|
-
throw new
|
|
181
|
+
throw new DataikuError(200, "SQL Query Failed", `${SQL_QUERY_FAILED_MARKER} (${queryId}): ${text}`);
|
|
177
182
|
}
|
|
178
183
|
}
|
|
179
184
|
async executeQuery(opts) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { UsageError, } from "../cli/usage.js";
|
|
2
|
+
import { ClientValidationError, } from "../errors.js";
|
|
2
3
|
import { BaseResource, } from "./base.js";
|
|
3
4
|
function isRecord(value) {
|
|
4
5
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -36,7 +37,7 @@ export class StatisticsResource extends BaseResource {
|
|
|
36
37
|
async runWorksheet(datasetName, worksheetId, projectKey) {
|
|
37
38
|
const worksheet = await this.getWorksheet(datasetName, worksheetId, projectKey);
|
|
38
39
|
if (!isRecord(worksheet.rootCard)) {
|
|
39
|
-
throw new
|
|
40
|
+
throw new ClientValidationError("statistics.runWorksheet requires worksheet.rootCard", "validation_failed");
|
|
40
41
|
}
|
|
41
42
|
return this.runCard(datasetName, worksheetId, worksheet.rootCard, projectKey);
|
|
42
43
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ClientValidationError, } from "../errors.js";
|
|
1
2
|
import { ProjectVariablesSchema, } from "../schemas.js";
|
|
2
3
|
import { BaseResource, } from "./base.js";
|
|
3
4
|
export class VariablesResource extends BaseResource {
|
|
@@ -17,7 +18,7 @@ export class VariablesResource extends BaseResource {
|
|
|
17
18
|
return replaced;
|
|
18
19
|
}
|
|
19
20
|
if (opts.standard === undefined && opts.local === undefined) {
|
|
20
|
-
throw new
|
|
21
|
+
throw new ClientValidationError("At least one of standard or local must be provided", "validation_failed");
|
|
21
22
|
}
|
|
22
23
|
const existing = await this.get(opts.projectKey);
|
|
23
24
|
const merged = {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { ClientValidationError, } from "../errors.js";
|
|
2
2
|
import { deepMerge, } from "../utils/deep-merge.js";
|
|
3
|
-
import { stableHash, } from "../utils/stable-hash.js";
|
|
3
|
+
import { SHA256_HEX_PATTERN, stableHash, } from "../utils/stable-hash.js";
|
|
4
4
|
import { BaseResource, } from "./base.js";
|
|
5
|
-
const EXPECT_HASH_PATTERN = /^[0-9a-fA-F]{64}$/;
|
|
6
5
|
function assertExpectedHash(id, expectHash, current, projectKey) {
|
|
7
|
-
if (!
|
|
6
|
+
if (!SHA256_HEX_PATTERN.test(expectHash)) {
|
|
8
7
|
throw new ClientValidationError("Expected webapp hash must be a 64-character SHA-256 hex digest.", "validation_failed", "Use the hash value returned by a previous read.", { projectKey, id, });
|
|
9
8
|
}
|
|
10
9
|
const actual = stableHash(current);
|
package/dist/src/schemas.d.ts
CHANGED
|
@@ -3,5 +3,4 @@
|
|
|
3
3
|
* The types package (packages/types/) owns the TypeBox schema definitions.
|
|
4
4
|
* SDK consumers get everything through this re-export.
|
|
5
5
|
*/
|
|
6
|
-
export
|
|
7
|
-
export type { BuildMode, CodeEnvActionResult, CodeEnvCreateOptions, CodeEnvDetails, CodeEnvGetLogOptions, CodeEnvLogResult, CodeEnvLogSummary, CodeEnvPackageList, CodeEnvSetPackagesOptions, CodeEnvSummary, CodeEnvUpdateImagesOptions, CodeEnvUpdatePackagesOptions, CodeEnvUsage, CodeEnvVersionForProject, CodeEnvWaitOptions, ConnectionSummary, DashboardDetails, DashboardSummary, DataQualityComputeResult, DataQualityProjectStatus, DataQualityRule, DataQualityRuleResult, DataQualityRules, DataQualityStatus, DataQualityStatusByPartition, DataQualityTimeline, DataQualityTimelineEntry, DatasetCreateOptions, DatasetDetails, DatasetSchema, DatasetSummary, FlowMapOptions, FlowZone, FlowZoneCreateOptions, FlowZoneItem, FlowZoneObjectType, FlowZonePosition, FlowZoneUpdateOptions, FolderCreateOptions, FolderDetails, FolderItem, FolderSummary, FutureState, FutureWaitResult, InsightDetails, InsightSummary, JobSummary, JobWaitResult, JupyterCell, JupyterNotebookContent, JupyterNotebookSummary, MacroDefinition, MacroRunStart, MacroState, MacroSummary, MacroSummaryArray, MacroWaitResult, NotebookSession, ProjectDetails, ProjectGitActionResult, ProjectGitDiffResult, ProjectGitFutureResponse, ProjectGitFutureState, ProjectGitLibraries, ProjectGitLibrary, ProjectGitLogResult, ProjectGitRemote, ProjectGitStatus, ProjectGitTag, ProjectGitTags, ProjectMetadata, ProjectSummary, ProjectTags, ProjectVariables, RecipeCreateOptions, RecipeCreateResult, RecipeDetails, RecipeSummary, SafeParseResult, ScenarioDetails, ScenarioStatus, ScenarioStepRun, ScenarioSummary, ScenarioWaitResult, SqlNotebookCell, SqlNotebookContent, SqlNotebookHistory, SqlNotebookSummary, SqlQueryResponse, SqlQueryResult, SqlQuerySchema, WikiArticleData, WikiArticleMetadata, WikiSettings, WikiTaxonomyNode, } from "../packages/types/src/index.js";
|
|
6
|
+
export * from "../packages/types/src/index.js";
|
package/dist/src/schemas.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* The types package (packages/types/) owns the TypeBox schema definitions.
|
|
4
4
|
* SDK consumers get everything through this re-export.
|
|
5
5
|
*/
|
|
6
|
-
export
|
|
6
|
+
export * from "../packages/types/src/index.js";
|
package/dist/src/skill.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { createHash, } from "node:crypto";
|
|
2
1
|
import * as fs from "node:fs";
|
|
3
2
|
import * as os from "node:os";
|
|
4
3
|
import * as path from "node:path";
|
|
5
4
|
import { fileURLToPath, } from "node:url";
|
|
5
|
+
import { ClientValidationError, } from "./errors.js";
|
|
6
|
+
import { sha256Hex, } from "./utils/stable-hash.js";
|
|
6
7
|
const SKILL_URLS = [
|
|
7
8
|
new URL("../skills/dataiku-dss/SKILL.md", import.meta.url),
|
|
8
9
|
new URL("../../skills/dataiku-dss/SKILL.md", import.meta.url),
|
|
@@ -12,7 +13,7 @@ function skillDirectory() {
|
|
|
12
13
|
if (fs.existsSync(skillUrl))
|
|
13
14
|
return new URL("./", skillUrl);
|
|
14
15
|
}
|
|
15
|
-
throw new
|
|
16
|
+
throw new ClientValidationError(`Bundled Dataiku skill not found. Checked: ${SKILL_URLS.map((url) => url.pathname).join(", ")}`, "internal_error");
|
|
16
17
|
}
|
|
17
18
|
function skillContent() {
|
|
18
19
|
return fs.readFileSync(new URL("SKILL.md", skillDirectory()), "utf-8");
|
|
@@ -102,9 +103,6 @@ export function findWorkspaceRoot(startDir) {
|
|
|
102
103
|
}
|
|
103
104
|
return startDir;
|
|
104
105
|
}
|
|
105
|
-
function sha256Hex(value) {
|
|
106
|
-
return createHash("sha256").update(value).digest("hex");
|
|
107
|
-
}
|
|
108
106
|
function skillState(target, expectedSha256) {
|
|
109
107
|
if (!fs.existsSync(target))
|
|
110
108
|
return { status: "missing", };
|
|
@@ -127,8 +125,9 @@ function skillFiles() {
|
|
|
127
125
|
else if (entry.isFile()) {
|
|
128
126
|
files.set(relativePath, fs.readFileSync(path.join(root, relativePath)));
|
|
129
127
|
}
|
|
130
|
-
else
|
|
131
|
-
throw new
|
|
128
|
+
else {
|
|
129
|
+
throw new ClientValidationError(`Unsupported bundled skill entry: ${relativePath}`, "internal_error");
|
|
130
|
+
}
|
|
132
131
|
}
|
|
133
132
|
}
|
|
134
133
|
visit("");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { ClientValidationError, } from "../errors.js";
|
|
3
4
|
import { canonicalDssUrl, } from "./dss-url.js";
|
|
4
5
|
function isCleanupLedgerEntry(value) {
|
|
5
6
|
if (!value || typeof value !== "object")
|
|
@@ -35,11 +36,11 @@ export async function preflightCleanupLedgerPath(filePath) {
|
|
|
35
36
|
}
|
|
36
37
|
catch (error) {
|
|
37
38
|
if (error.code !== "ENOENT") {
|
|
38
|
-
throw new
|
|
39
|
+
throw new ClientValidationError(`Could not preflight cleanup ledger ${resolved}: ${error instanceof Error ? error.message : String(error)}`, "validation_failed", undefined, undefined, { cause: error, });
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
if (stats?.isDirectory()) {
|
|
42
|
-
throw new
|
|
43
|
+
throw new ClientValidationError(`Cleanup ledger path is a directory, not a file: ${resolved}`, "validation_failed");
|
|
43
44
|
}
|
|
44
45
|
if (stats !== undefined) {
|
|
45
46
|
try {
|
|
@@ -48,9 +49,9 @@ export async function preflightCleanupLedgerPath(filePath) {
|
|
|
48
49
|
}
|
|
49
50
|
catch (error) {
|
|
50
51
|
const code = error.code;
|
|
51
|
-
throw new
|
|
52
|
+
throw new ClientValidationError(code === "EACCES" || code === "EPERM"
|
|
52
53
|
? `Cleanup ledger file is not writable: ${resolved}`
|
|
53
|
-
: `Could not preflight cleanup ledger ${resolved}: ${error instanceof Error ? error.message : String(error)}`, { cause: error, });
|
|
54
|
+
: `Could not preflight cleanup ledger ${resolved}: ${error instanceof Error ? error.message : String(error)}`, "validation_failed", undefined, undefined, { cause: error, });
|
|
54
55
|
}
|
|
55
56
|
return;
|
|
56
57
|
}
|
|
@@ -60,7 +61,7 @@ export async function preflightCleanupLedgerPath(filePath) {
|
|
|
60
61
|
await fs.rm(probe, { recursive: true, force: true, });
|
|
61
62
|
}
|
|
62
63
|
catch (error) {
|
|
63
|
-
throw new
|
|
64
|
+
throw new ClientValidationError(`Cleanup ledger directory is not writable: ${parent}${error instanceof Error ? ` (${error.message})` : ""}`, "validation_failed", undefined, undefined, { cause: error, });
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
/**
|
|
@@ -94,10 +95,10 @@ export async function reserveCleanupLedgerDssUrl(filePath, dssUrl) {
|
|
|
94
95
|
found = canonicalDssUrl(raw.trim());
|
|
95
96
|
}
|
|
96
97
|
catch (error) {
|
|
97
|
-
throw new
|
|
98
|
+
throw new ClientValidationError("Cleanup ledger DSS binding is invalid.", "validation_failed", undefined, undefined, { cause: error, });
|
|
98
99
|
}
|
|
99
100
|
if (found !== expected) {
|
|
100
|
-
throw new
|
|
101
|
+
throw new ClientValidationError("Cleanup ledger is reserved for a different DSS server.", "validation_failed");
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
/**
|
|
@@ -140,7 +141,7 @@ export async function readCleanupLedger(filePath) {
|
|
|
140
141
|
.map((line, index) => {
|
|
141
142
|
const parsed = JSON.parse(line);
|
|
142
143
|
if (!isCleanupLedgerEntry(parsed)) {
|
|
143
|
-
throw new
|
|
144
|
+
throw new ClientValidationError(`Invalid cleanup ledger entry at line ${index + 1}`, "validation_failed");
|
|
144
145
|
}
|
|
145
146
|
return parsed;
|
|
146
147
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate and canonicalize a Git library reference path. This is the single
|
|
3
|
+
* validator used by both SDK dispatch and CLI mutation planning.
|
|
4
|
+
*
|
|
5
|
+
* Strip only the permitted outer '/' separators, then require nonempty
|
|
6
|
+
* ordinary segments: no empty segments and no '.' or '..' segments. A hostile
|
|
7
|
+
* path therefore cannot change which API resource a dispatcher's URL
|
|
8
|
+
* normalization would otherwise select.
|
|
9
|
+
*/
|
|
10
|
+
export declare function validateGitReferencePath(path: string): string;
|
|
11
|
+
/** Validate a Git library reference path and URL-encode it per segment. */
|
|
12
|
+
export declare function encodeGitReferencePath(path: string): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ClientValidationError, } from "../errors.js";
|
|
2
|
+
function gitReferencePathError(path, message) {
|
|
3
|
+
return new ClientValidationError(message, "validation_failed", "Use a Git library reference path relative to the project library root: '/' -separated segment names, with no '.' or '..' segments.", { path, });
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Validate and canonicalize a Git library reference path. This is the single
|
|
7
|
+
* validator used by both SDK dispatch and CLI mutation planning.
|
|
8
|
+
*
|
|
9
|
+
* Strip only the permitted outer '/' separators, then require nonempty
|
|
10
|
+
* ordinary segments: no empty segments and no '.' or '..' segments. A hostile
|
|
11
|
+
* path therefore cannot change which API resource a dispatcher's URL
|
|
12
|
+
* normalization would otherwise select.
|
|
13
|
+
*/
|
|
14
|
+
export function validateGitReferencePath(path) {
|
|
15
|
+
if (typeof path !== "string" || path.trim() === "") {
|
|
16
|
+
throw gitReferencePathError(path, "A Git library reference path is required.");
|
|
17
|
+
}
|
|
18
|
+
const normalized = path.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
19
|
+
if (normalized === "") {
|
|
20
|
+
throw gitReferencePathError(path, "A Git library reference path is required.");
|
|
21
|
+
}
|
|
22
|
+
for (const segment of normalized.split("/")) {
|
|
23
|
+
if (segment === "") {
|
|
24
|
+
throw gitReferencePathError(path, "A Git library reference path must not contain empty segments (consecutive or repeated '/').");
|
|
25
|
+
}
|
|
26
|
+
if (segment === "." || segment === "..") {
|
|
27
|
+
throw gitReferencePathError(path, "A Git library reference path must not contain '.' or '..' segments.");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return normalized;
|
|
31
|
+
}
|
|
32
|
+
/** Validate a Git library reference path and URL-encode it per segment. */
|
|
33
|
+
export function encodeGitReferencePath(path) {
|
|
34
|
+
return validateGitReferencePath(path)
|
|
35
|
+
.split("/")
|
|
36
|
+
.map((segment) => encodeURIComponent(segment))
|
|
37
|
+
.join("/");
|
|
38
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/** Default fixed poll interval and overall wait budget for long-running DSS operations. */
|
|
2
|
+
export declare const DEFAULT_POLL_INTERVAL_MS = 2000;
|
|
3
|
+
export declare const DEFAULT_TIMEOUT_MS = 120000;
|
|
1
4
|
/**
|
|
2
5
|
* Shared polling helpers used by every long-running wait loop (jobs, futures,
|
|
3
6
|
* scenarios, macros, Visual ML training, Project Git futures).
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/** Default fixed poll interval and overall wait budget for long-running DSS operations. */
|
|
2
|
+
export const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
3
|
+
export const DEFAULT_TIMEOUT_MS = 120_000;
|
|
1
4
|
import { DataikuError, } from "../errors.js";
|
|
2
5
|
/**
|
|
3
6
|
* Shared polling helpers used by every long-running wait loop (jobs, futures,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import StreamZip from "node-stream-zip";
|
|
2
2
|
import { stat, } from "node:fs/promises";
|
|
3
|
-
import { ClientValidationError, } from "../errors.js";
|
|
3
|
+
import { ClientValidationError, unexpectedResponseError, } from "../errors.js";
|
|
4
4
|
const MANIFEST_MEMBER = "export-manifest.json";
|
|
5
5
|
const DATASETS_ROOT = "project_config/datasets";
|
|
6
6
|
const RECIPES_ROOT = "project_config/recipes";
|
|
@@ -144,7 +144,7 @@ function streamMember(zip, entry, accumulate, bufferMaxBytes, limits) {
|
|
|
144
144
|
return reject(err);
|
|
145
145
|
}
|
|
146
146
|
if (!stm) {
|
|
147
|
-
return reject(
|
|
147
|
+
return reject(unexpectedResponseError("archive produced no member stream"));
|
|
148
148
|
}
|
|
149
149
|
const stream = stm;
|
|
150
150
|
let bytes = 0;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export declare function sha256Hex(value: string): string;
|
|
1
|
+
export declare function sha256Hex(value: string | Uint8Array): string;
|
|
2
|
+
/** Lowercase or uppercase SHA-256 hex digest accepted by every `--expect-hash` guard. */
|
|
3
|
+
export declare const SHA256_HEX_PATTERN: RegExp;
|
|
2
4
|
/** Locale-independent UTF-16 code-unit order for deterministic machine output. */
|
|
3
5
|
export declare function compareStrings(a: string, b: string): number;
|
|
4
6
|
/**
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export function sha256Hex(value) {
|
|
2
2
|
return new Bun.CryptoHasher("sha256").update(value).digest("hex");
|
|
3
3
|
}
|
|
4
|
+
/** Lowercase or uppercase SHA-256 hex digest accepted by every `--expect-hash` guard. */
|
|
5
|
+
export const SHA256_HEX_PATTERN = /^[0-9a-fA-F]{64}$/;
|
|
4
6
|
/** Locale-independent UTF-16 code-unit order for deterministic machine output. */
|
|
5
7
|
export function compareStrings(a, b) {
|
|
6
8
|
return a < b ? -1 : a > b ? 1 : 0;
|