@deepnote/blocks 3.0.1 → 4.0.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/dist/index.cjs +197 -35
- package/dist/index.d.cts +13006 -1685
- package/dist/index.d.ts +13006 -1685
- package/dist/index.js +183 -36
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,113 @@ import { z } from "zod";
|
|
|
2
2
|
import { parseDocument } from "yaml";
|
|
3
3
|
import { dedent } from "ts-dedent";
|
|
4
4
|
|
|
5
|
+
//#region src/errors.ts
|
|
6
|
+
/**
|
|
7
|
+
* Base error class for all Deepnote errors.
|
|
8
|
+
*/
|
|
9
|
+
var DeepnoteError = class extends Error {
|
|
10
|
+
constructor(message, options) {
|
|
11
|
+
super(message, options);
|
|
12
|
+
this.name = this.constructor.name;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Generic parse failure, optionally associated with a file path.
|
|
17
|
+
*/
|
|
18
|
+
var ParseError = class extends DeepnoteError {
|
|
19
|
+
filePath;
|
|
20
|
+
constructor(message, options) {
|
|
21
|
+
super(message, options);
|
|
22
|
+
this.filePath = options?.filePath;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* YAML syntax or duplicate key errors.
|
|
27
|
+
*/
|
|
28
|
+
var YamlParseError = class extends ParseError {};
|
|
29
|
+
/**
|
|
30
|
+
* BOM or invalid UTF-8 encoding errors.
|
|
31
|
+
*/
|
|
32
|
+
var EncodingError = class extends ParseError {};
|
|
33
|
+
/**
|
|
34
|
+
* Zod schema validation failures.
|
|
35
|
+
*/
|
|
36
|
+
var SchemaValidationError = class extends ParseError {};
|
|
37
|
+
/**
|
|
38
|
+
* Prohibited YAML features: anchors, aliases, merge keys, tags.
|
|
39
|
+
*/
|
|
40
|
+
var ProhibitedYamlFeatureError = class extends ParseError {
|
|
41
|
+
feature;
|
|
42
|
+
constructor(message, options) {
|
|
43
|
+
super(message, options);
|
|
44
|
+
this.feature = options.feature;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Thrown when a block type is not supported.
|
|
49
|
+
*/
|
|
50
|
+
var UnsupportedBlockTypeError = class extends DeepnoteError {};
|
|
51
|
+
/**
|
|
52
|
+
* Thrown when a value is invalid (e.g., slider value, date interval).
|
|
53
|
+
*/
|
|
54
|
+
var InvalidValueError = class extends DeepnoteError {
|
|
55
|
+
value;
|
|
56
|
+
constructor(message, options) {
|
|
57
|
+
super(message, options);
|
|
58
|
+
this.value = options.value;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/blocks/executable-blocks.ts
|
|
64
|
+
/**
|
|
65
|
+
* Block types that represent user input widgets.
|
|
66
|
+
* These blocks capture user input and define variables.
|
|
67
|
+
*/
|
|
68
|
+
const INPUT_BLOCK_TYPES = new Set([
|
|
69
|
+
"input-text",
|
|
70
|
+
"input-textarea",
|
|
71
|
+
"input-checkbox",
|
|
72
|
+
"input-select",
|
|
73
|
+
"input-slider",
|
|
74
|
+
"input-date",
|
|
75
|
+
"input-date-range",
|
|
76
|
+
"input-file"
|
|
77
|
+
]);
|
|
78
|
+
const executableBlockTypes = new Set([
|
|
79
|
+
"code",
|
|
80
|
+
"sql",
|
|
81
|
+
"notebook-function",
|
|
82
|
+
"visualization",
|
|
83
|
+
"button",
|
|
84
|
+
"big-number",
|
|
85
|
+
...INPUT_BLOCK_TYPES
|
|
86
|
+
]);
|
|
87
|
+
/**
|
|
88
|
+
* Type guard to check if a block is an executable block.
|
|
89
|
+
* Executable blocks can have outputs and be executed by the runtime.
|
|
90
|
+
*/
|
|
91
|
+
function isExecutableBlock(block) {
|
|
92
|
+
return executableBlockTypes.has(block.type);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Checks if a block type string represents an executable block.
|
|
96
|
+
* Convenience function for when you only have the type string.
|
|
97
|
+
*/
|
|
98
|
+
function isExecutableBlockType(type) {
|
|
99
|
+
return executableBlockTypes.has(type);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/blocks/sql-utils.ts
|
|
104
|
+
function convertToEnvironmentVariableName(str) {
|
|
105
|
+
return (/^\d/.test(str) ? `_${str}` : str).toUpperCase().replace(/[^\w]/g, "_");
|
|
106
|
+
}
|
|
107
|
+
function getSqlEnvVarName(integrationId) {
|
|
108
|
+
return `SQL_${integrationId}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
5
112
|
//#region src/deserialize-file/deepnote-file-schema.ts
|
|
6
113
|
/** Preprocesses any content value to empty string for blocks that don't use content */
|
|
7
114
|
const emptyContent = () => z.preprocess(() => "", z.literal("").optional());
|
|
@@ -394,6 +501,17 @@ const deepnoteFileSchema = z.object({
|
|
|
394
501
|
}),
|
|
395
502
|
version: z.string()
|
|
396
503
|
});
|
|
504
|
+
const deepnoteSnapshotSchema = deepnoteFileSchema.extend({
|
|
505
|
+
environment: environmentSchema.unwrap(),
|
|
506
|
+
execution: executionSchema.unwrap(),
|
|
507
|
+
metadata: z.object({
|
|
508
|
+
checksum: z.string().optional(),
|
|
509
|
+
createdAt: z.string(),
|
|
510
|
+
exportedAt: z.string().optional(),
|
|
511
|
+
modifiedAt: z.string().optional(),
|
|
512
|
+
snapshotHash: z.string()
|
|
513
|
+
})
|
|
514
|
+
});
|
|
397
515
|
|
|
398
516
|
//#endregion
|
|
399
517
|
//#region src/deserialize-file/parse-yaml.ts
|
|
@@ -403,7 +521,7 @@ const deepnoteFileSchema = z.object({
|
|
|
403
521
|
*
|
|
404
522
|
* @param bytes - Raw file bytes as Uint8Array
|
|
405
523
|
* @returns Decoded UTF-8 string without BOM
|
|
406
|
-
* @throws
|
|
524
|
+
* @throws EncodingError if BOM detected or invalid UTF-8 encoding
|
|
407
525
|
*
|
|
408
526
|
* @example
|
|
409
527
|
* ```typescript
|
|
@@ -413,11 +531,11 @@ const deepnoteFileSchema = z.object({
|
|
|
413
531
|
* ```
|
|
414
532
|
*/
|
|
415
533
|
function decodeUtf8NoBom(bytes) {
|
|
416
|
-
if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) throw new
|
|
534
|
+
if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) throw new EncodingError("UTF-8 BOM detected in Deepnote file - files must be UTF-8 without BOM");
|
|
417
535
|
try {
|
|
418
536
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
419
537
|
} catch {
|
|
420
|
-
throw new
|
|
538
|
+
throw new EncodingError("Invalid UTF-8 encoding detected in Deepnote file");
|
|
421
539
|
}
|
|
422
540
|
}
|
|
423
541
|
/**
|
|
@@ -427,10 +545,10 @@ function decodeUtf8NoBom(bytes) {
|
|
|
427
545
|
* For proper UTF-8 validation, use decodeUtf8NoBom() on raw bytes before decoding.
|
|
428
546
|
*
|
|
429
547
|
* @param yamlContent - Already-decoded YAML string
|
|
430
|
-
* @throws
|
|
548
|
+
* @throws EncodingError if BOM prefix detected
|
|
431
549
|
*/
|
|
432
550
|
function validateNoBomPrefix(yamlContent) {
|
|
433
|
-
if (yamlContent.charCodeAt(0) === 65279) throw new
|
|
551
|
+
if (yamlContent.charCodeAt(0) === 65279) throw new EncodingError("UTF-8 BOM detected in Deepnote file - files must be UTF-8 without BOM");
|
|
434
552
|
}
|
|
435
553
|
/**
|
|
436
554
|
* Validates that the YAML document doesn't contain prohibited features:
|
|
@@ -439,13 +557,13 @@ function validateNoBomPrefix(yamlContent) {
|
|
|
439
557
|
* - No custom tags (!tag)
|
|
440
558
|
*/
|
|
441
559
|
function validateYamlStructure(yamlContent) {
|
|
442
|
-
if (/(?:^|\n)\s*(?:-\s+|[\w-]+:\s*)&\w+/.test(yamlContent)) throw new
|
|
443
|
-
if (/(?:^|\n)\s*(?:-\s+|[\w-]+:\s*)\*\w+/.test(yamlContent)) throw new
|
|
444
|
-
if (/<<:/.test(yamlContent)) throw new
|
|
560
|
+
if (/(?:^|\n)\s*(?:-\s+|[\w-]+:\s*)&\w+/.test(yamlContent)) throw new ProhibitedYamlFeatureError("YAML anchors (&) are not allowed in Deepnote files", { feature: "anchor" });
|
|
561
|
+
if (/(?:^|\n)\s*(?:-\s+|[\w-]+:\s*)\*\w+/.test(yamlContent)) throw new ProhibitedYamlFeatureError("YAML aliases (*) are not allowed in Deepnote files", { feature: "alias" });
|
|
562
|
+
if (/<<:/.test(yamlContent)) throw new ProhibitedYamlFeatureError("YAML merge keys (<<) are not allowed in Deepnote files", { feature: "merge-key" });
|
|
445
563
|
const matches = yamlContent.match(/(?:^|\n)\s*(?:-\s+|[\w-]+:\s*)(![\w/-]+)/gm);
|
|
446
564
|
if (matches) {
|
|
447
565
|
const tags = matches.map((m) => m.match(/(![\w/-]+)/)?.[1]).filter(Boolean);
|
|
448
|
-
if (tags.length > 0) throw new
|
|
566
|
+
if (tags.length > 0) throw new ProhibitedYamlFeatureError(`YAML tags are not allowed in Deepnote files: ${tags.join(", ")}`, { feature: "tag" });
|
|
449
567
|
}
|
|
450
568
|
}
|
|
451
569
|
/**
|
|
@@ -460,8 +578,8 @@ function parseAndValidate(yamlContent) {
|
|
|
460
578
|
});
|
|
461
579
|
if (doc.errors.length > 0) {
|
|
462
580
|
const duplicateKeyError = doc.errors.find((err) => err.message.includes("duplicate") || err.message.includes("key"));
|
|
463
|
-
if (duplicateKeyError) throw new
|
|
464
|
-
throw new
|
|
581
|
+
if (duplicateKeyError) throw new YamlParseError(`Duplicate keys detected in Deepnote file: ${duplicateKeyError.message}`);
|
|
582
|
+
throw new YamlParseError(`YAML parsing error: ${doc.errors[0].message}`);
|
|
465
583
|
}
|
|
466
584
|
return doc.toJS();
|
|
467
585
|
}
|
|
@@ -480,8 +598,8 @@ function parseYaml(yamlContent) {
|
|
|
480
598
|
validateYamlStructure(yamlContent);
|
|
481
599
|
return parseAndValidate(yamlContent);
|
|
482
600
|
} catch (error) {
|
|
483
|
-
|
|
484
|
-
throw new
|
|
601
|
+
if (error instanceof DeepnoteError) throw error;
|
|
602
|
+
throw new ParseError(`Failed to parse Deepnote file: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
485
603
|
}
|
|
486
604
|
}
|
|
487
605
|
|
|
@@ -495,23 +613,13 @@ function deserializeDeepnoteFile(yamlContent) {
|
|
|
495
613
|
const result = deepnoteFileSchema.safeParse(parsed);
|
|
496
614
|
if (!result.success) {
|
|
497
615
|
const issue = result.error.issues[0];
|
|
498
|
-
if (!issue) throw new
|
|
616
|
+
if (!issue) throw new SchemaValidationError("Invalid Deepnote file.");
|
|
499
617
|
const path = issue.path.join(".");
|
|
500
|
-
|
|
501
|
-
throw new Error(`Failed to parse the Deepnote file: ${message}.`);
|
|
618
|
+
throw new SchemaValidationError(`Failed to parse the Deepnote file: ${path ? `${path}: ${issue.message}` : issue.message}.`);
|
|
502
619
|
}
|
|
503
620
|
return result.data;
|
|
504
621
|
}
|
|
505
622
|
|
|
506
|
-
//#endregion
|
|
507
|
-
//#region src/blocks.ts
|
|
508
|
-
var UnsupportedBlockTypeError = class extends Error {
|
|
509
|
-
constructor(message) {
|
|
510
|
-
super(message);
|
|
511
|
-
this.name = "UnsupportedBlockTypeError";
|
|
512
|
-
}
|
|
513
|
-
};
|
|
514
|
-
|
|
515
623
|
//#endregion
|
|
516
624
|
//#region src/blocks/image-blocks.ts
|
|
517
625
|
function escapeHtmlAttribute(value) {
|
|
@@ -559,7 +667,7 @@ function createMarkdownForTextBlock(block) {
|
|
|
559
667
|
if (block.type === "text-cell-todo") return `- ${block.metadata?.checked ? "[x]" : "[ ]"} ${escapeMarkdown(content)}`;
|
|
560
668
|
if (block.type === "text-cell-callout") return `> ${escapeMarkdown(content)}`;
|
|
561
669
|
if (block.type === "text-cell-p") return escapeMarkdown(content);
|
|
562
|
-
throw new
|
|
670
|
+
throw new UnsupportedBlockTypeError("Unhandled block type.");
|
|
563
671
|
}
|
|
564
672
|
function stripMarkdownFromTextBlock(block) {
|
|
565
673
|
const content = block.content ?? "";
|
|
@@ -570,7 +678,7 @@ function stripMarkdownFromTextBlock(block) {
|
|
|
570
678
|
if (block.type === "text-cell-todo") return content.replace(/^-+\s+\[.\]\s+/, "").trim();
|
|
571
679
|
if (block.type === "text-cell-callout") return content.replace(/^>\s+/, "").trim();
|
|
572
680
|
if (block.type === "text-cell-p") return content.trim();
|
|
573
|
-
throw new
|
|
681
|
+
throw new UnsupportedBlockTypeError("Unhandled block type.");
|
|
574
682
|
}
|
|
575
683
|
function createMarkdownForSeparatorBlock(_block) {
|
|
576
684
|
return "<hr>";
|
|
@@ -844,9 +952,9 @@ function createPythonCodeForInputSelectBlock(block) {
|
|
|
844
952
|
function createPythonCodeForInputSliderBlock(block) {
|
|
845
953
|
const sanitizedPythonVariableName = sanitizePythonVariableName(block.metadata.deepnote_variable_name);
|
|
846
954
|
const value = block.metadata.deepnote_variable_value;
|
|
847
|
-
if (!/^-?\d+\.?\d*$|^-?\d*\.\d+$/.test(value)) throw new
|
|
955
|
+
if (!/^-?\d+\.?\d*$|^-?\d*\.\d+$/.test(value)) throw new InvalidValueError(`Invalid numeric value for slider input: "${value}". Expected a valid number (integer or float).`, { value });
|
|
848
956
|
const numericValue = Number(value);
|
|
849
|
-
if (!Number.isFinite(numericValue)) throw new
|
|
957
|
+
if (!Number.isFinite(numericValue)) throw new InvalidValueError(`Invalid numeric value for slider input: "${value}". Value must be finite.`, { value });
|
|
850
958
|
return `${sanitizedPythonVariableName} = ${numericValue}`;
|
|
851
959
|
}
|
|
852
960
|
function createPythonCodeForInputFileBlock(block) {
|
|
@@ -880,7 +988,7 @@ function createPythonCodeForInputDateRangeBlock(block) {
|
|
|
880
988
|
return pythonCode.dateRangeCustomDays(sanitizedPythonVariableName, Number(customDays));
|
|
881
989
|
} else if (isValidRelativeDateInterval(block.metadata.deepnote_variable_value)) {
|
|
882
990
|
const range = DATE_RANGE_INPUT_RELATIVE_RANGES.find((range$1) => range$1.value === block.metadata.deepnote_variable_value);
|
|
883
|
-
if (!range) throw new
|
|
991
|
+
if (!range) throw new InvalidValueError(`Invalid relative date interval: "${block.metadata.deepnote_variable_value}". Expected one of: ${DATE_RANGE_INPUT_RELATIVE_RANGES.map((r) => r.value).join(", ")}.`, { value: block.metadata.deepnote_variable_value });
|
|
884
992
|
return dedent`
|
|
885
993
|
${range.pythonCode(sanitizedPythonVariableName)}`;
|
|
886
994
|
} else return dedent`
|
|
@@ -913,12 +1021,50 @@ function isInputDateRangeBlock(block) {
|
|
|
913
1021
|
}
|
|
914
1022
|
|
|
915
1023
|
//#endregion
|
|
916
|
-
//#region src/blocks/
|
|
917
|
-
function
|
|
918
|
-
return
|
|
1024
|
+
//#region src/blocks/notebook-function-blocks.ts
|
|
1025
|
+
function isNotebookFunctionBlock(block) {
|
|
1026
|
+
return block.type === "notebook-function";
|
|
919
1027
|
}
|
|
920
|
-
function
|
|
921
|
-
|
|
1028
|
+
function createPythonCodeForNotebookFunctionBlock(block) {
|
|
1029
|
+
const notebookId = block.metadata?.function_notebook_id;
|
|
1030
|
+
const inputs = block.metadata?.function_notebook_inputs ?? {};
|
|
1031
|
+
const exportMappings = block.metadata?.function_notebook_export_mappings ?? {};
|
|
1032
|
+
if (!notebookId) return dedent`
|
|
1033
|
+
# Notebook Function: Not configured
|
|
1034
|
+
pass
|
|
1035
|
+
`;
|
|
1036
|
+
const enabledExports = Object.entries(exportMappings).filter(([, mapping]) => mapping.enabled);
|
|
1037
|
+
const inputsAsString = JSON.stringify(inputs);
|
|
1038
|
+
const exportMappingsDict = {};
|
|
1039
|
+
for (const [exportName, mapping] of enabledExports) exportMappingsDict[exportName] = mapping.variable_name;
|
|
1040
|
+
const exportMappingsAsString = JSON.stringify(exportMappingsDict);
|
|
1041
|
+
const inputsComment = `Inputs: ${inputsAsString}`;
|
|
1042
|
+
const exportsComment = enabledExports.length > 0 ? `Exports: ${enabledExports.map(([name, mapping]) => `${name} -> ${mapping.variable_name}`).join(", ")}` : "Exports: (none)";
|
|
1043
|
+
const functionCall = dedent`
|
|
1044
|
+
_dntk.run_notebook_function(
|
|
1045
|
+
${escapePythonString(notebookId)},
|
|
1046
|
+
inputs=${inputsAsString},
|
|
1047
|
+
export_mappings=${exportMappingsAsString}
|
|
1048
|
+
)
|
|
1049
|
+
`;
|
|
1050
|
+
if (enabledExports.length === 0) return dedent`
|
|
1051
|
+
# Notebook Function: ${notebookId}
|
|
1052
|
+
# ${inputsComment}
|
|
1053
|
+
# ${exportsComment}
|
|
1054
|
+
${functionCall}
|
|
1055
|
+
`;
|
|
1056
|
+
if (enabledExports.length === 1) return dedent`
|
|
1057
|
+
# Notebook Function: ${notebookId}
|
|
1058
|
+
# ${inputsComment}
|
|
1059
|
+
# ${exportsComment}
|
|
1060
|
+
${enabledExports[0][1]?.variable_name} = ${functionCall}
|
|
1061
|
+
`;
|
|
1062
|
+
return dedent`
|
|
1063
|
+
# Notebook Function: ${notebookId}
|
|
1064
|
+
# ${inputsComment}
|
|
1065
|
+
# ${exportsComment}
|
|
1066
|
+
${enabledExports.map(([, mapping]) => mapping.variable_name).join(", ")} = ${functionCall}
|
|
1067
|
+
`;
|
|
922
1068
|
}
|
|
923
1069
|
|
|
924
1070
|
//#endregion
|
|
@@ -987,8 +1133,9 @@ function createPythonCode(block, executionContext) {
|
|
|
987
1133
|
if (isVisualizationBlock(block)) return createPythonCodeForVisualizationBlock(block);
|
|
988
1134
|
if (isButtonBlock(block)) return createPythonCodeForButtonBlock(block, executionContext);
|
|
989
1135
|
if (isBigNumberBlock(block)) return createPythonCodeForBigNumberBlock(block);
|
|
1136
|
+
if (isNotebookFunctionBlock(block)) return createPythonCodeForNotebookFunctionBlock(block);
|
|
990
1137
|
throw new UnsupportedBlockTypeError(`Creating python code from block type ${block.type} is not supported yet.`);
|
|
991
1138
|
}
|
|
992
1139
|
|
|
993
1140
|
//#endregion
|
|
994
|
-
export { createMarkdown, createPythonCode, decodeUtf8NoBom, deepnoteBlockSchema, deepnoteFileSchema, deserializeDeepnoteFile, environmentSchema, executionErrorSchema, executionSchema, executionSummarySchema, stripMarkdown };
|
|
1141
|
+
export { DeepnoteError, EncodingError, INPUT_BLOCK_TYPES, InvalidValueError, ParseError, ProhibitedYamlFeatureError, SchemaValidationError, UnsupportedBlockTypeError, YamlParseError, convertToEnvironmentVariableName, createMarkdown, createPythonCode, decodeUtf8NoBom, deepnoteBlockSchema, deepnoteFileSchema, deepnoteSnapshotSchema, deserializeDeepnoteFile, environmentSchema, executionErrorSchema, executionSchema, executionSummarySchema, getSqlEnvVarName, isExecutableBlock, isExecutableBlockType, parseYaml, stripMarkdown };
|