@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.cjs
CHANGED
|
@@ -28,6 +28,113 @@ yaml = __toESM(yaml);
|
|
|
28
28
|
let ts_dedent = require("ts-dedent");
|
|
29
29
|
ts_dedent = __toESM(ts_dedent);
|
|
30
30
|
|
|
31
|
+
//#region src/errors.ts
|
|
32
|
+
/**
|
|
33
|
+
* Base error class for all Deepnote errors.
|
|
34
|
+
*/
|
|
35
|
+
var DeepnoteError = class extends Error {
|
|
36
|
+
constructor(message, options) {
|
|
37
|
+
super(message, options);
|
|
38
|
+
this.name = this.constructor.name;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Generic parse failure, optionally associated with a file path.
|
|
43
|
+
*/
|
|
44
|
+
var ParseError = class extends DeepnoteError {
|
|
45
|
+
filePath;
|
|
46
|
+
constructor(message, options) {
|
|
47
|
+
super(message, options);
|
|
48
|
+
this.filePath = options?.filePath;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* YAML syntax or duplicate key errors.
|
|
53
|
+
*/
|
|
54
|
+
var YamlParseError = class extends ParseError {};
|
|
55
|
+
/**
|
|
56
|
+
* BOM or invalid UTF-8 encoding errors.
|
|
57
|
+
*/
|
|
58
|
+
var EncodingError = class extends ParseError {};
|
|
59
|
+
/**
|
|
60
|
+
* Zod schema validation failures.
|
|
61
|
+
*/
|
|
62
|
+
var SchemaValidationError = class extends ParseError {};
|
|
63
|
+
/**
|
|
64
|
+
* Prohibited YAML features: anchors, aliases, merge keys, tags.
|
|
65
|
+
*/
|
|
66
|
+
var ProhibitedYamlFeatureError = class extends ParseError {
|
|
67
|
+
feature;
|
|
68
|
+
constructor(message, options) {
|
|
69
|
+
super(message, options);
|
|
70
|
+
this.feature = options.feature;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Thrown when a block type is not supported.
|
|
75
|
+
*/
|
|
76
|
+
var UnsupportedBlockTypeError = class extends DeepnoteError {};
|
|
77
|
+
/**
|
|
78
|
+
* Thrown when a value is invalid (e.g., slider value, date interval).
|
|
79
|
+
*/
|
|
80
|
+
var InvalidValueError = class extends DeepnoteError {
|
|
81
|
+
value;
|
|
82
|
+
constructor(message, options) {
|
|
83
|
+
super(message, options);
|
|
84
|
+
this.value = options.value;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/blocks/executable-blocks.ts
|
|
90
|
+
/**
|
|
91
|
+
* Block types that represent user input widgets.
|
|
92
|
+
* These blocks capture user input and define variables.
|
|
93
|
+
*/
|
|
94
|
+
const INPUT_BLOCK_TYPES = new Set([
|
|
95
|
+
"input-text",
|
|
96
|
+
"input-textarea",
|
|
97
|
+
"input-checkbox",
|
|
98
|
+
"input-select",
|
|
99
|
+
"input-slider",
|
|
100
|
+
"input-date",
|
|
101
|
+
"input-date-range",
|
|
102
|
+
"input-file"
|
|
103
|
+
]);
|
|
104
|
+
const executableBlockTypes = new Set([
|
|
105
|
+
"code",
|
|
106
|
+
"sql",
|
|
107
|
+
"notebook-function",
|
|
108
|
+
"visualization",
|
|
109
|
+
"button",
|
|
110
|
+
"big-number",
|
|
111
|
+
...INPUT_BLOCK_TYPES
|
|
112
|
+
]);
|
|
113
|
+
/**
|
|
114
|
+
* Type guard to check if a block is an executable block.
|
|
115
|
+
* Executable blocks can have outputs and be executed by the runtime.
|
|
116
|
+
*/
|
|
117
|
+
function isExecutableBlock(block) {
|
|
118
|
+
return executableBlockTypes.has(block.type);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Checks if a block type string represents an executable block.
|
|
122
|
+
* Convenience function for when you only have the type string.
|
|
123
|
+
*/
|
|
124
|
+
function isExecutableBlockType(type) {
|
|
125
|
+
return executableBlockTypes.has(type);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/blocks/sql-utils.ts
|
|
130
|
+
function convertToEnvironmentVariableName(str) {
|
|
131
|
+
return (/^\d/.test(str) ? `_${str}` : str).toUpperCase().replace(/[^\w]/g, "_");
|
|
132
|
+
}
|
|
133
|
+
function getSqlEnvVarName(integrationId) {
|
|
134
|
+
return `SQL_${integrationId}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
31
138
|
//#region src/deserialize-file/deepnote-file-schema.ts
|
|
32
139
|
/** Preprocesses any content value to empty string for blocks that don't use content */
|
|
33
140
|
const emptyContent = () => zod.z.preprocess(() => "", zod.z.literal("").optional());
|
|
@@ -420,6 +527,17 @@ const deepnoteFileSchema = zod.z.object({
|
|
|
420
527
|
}),
|
|
421
528
|
version: zod.z.string()
|
|
422
529
|
});
|
|
530
|
+
const deepnoteSnapshotSchema = deepnoteFileSchema.extend({
|
|
531
|
+
environment: environmentSchema.unwrap(),
|
|
532
|
+
execution: executionSchema.unwrap(),
|
|
533
|
+
metadata: zod.z.object({
|
|
534
|
+
checksum: zod.z.string().optional(),
|
|
535
|
+
createdAt: zod.z.string(),
|
|
536
|
+
exportedAt: zod.z.string().optional(),
|
|
537
|
+
modifiedAt: zod.z.string().optional(),
|
|
538
|
+
snapshotHash: zod.z.string()
|
|
539
|
+
})
|
|
540
|
+
});
|
|
423
541
|
|
|
424
542
|
//#endregion
|
|
425
543
|
//#region src/deserialize-file/parse-yaml.ts
|
|
@@ -429,7 +547,7 @@ const deepnoteFileSchema = zod.z.object({
|
|
|
429
547
|
*
|
|
430
548
|
* @param bytes - Raw file bytes as Uint8Array
|
|
431
549
|
* @returns Decoded UTF-8 string without BOM
|
|
432
|
-
* @throws
|
|
550
|
+
* @throws EncodingError if BOM detected or invalid UTF-8 encoding
|
|
433
551
|
*
|
|
434
552
|
* @example
|
|
435
553
|
* ```typescript
|
|
@@ -439,11 +557,11 @@ const deepnoteFileSchema = zod.z.object({
|
|
|
439
557
|
* ```
|
|
440
558
|
*/
|
|
441
559
|
function decodeUtf8NoBom(bytes) {
|
|
442
|
-
if (bytes.length >= 3 && bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191) throw new
|
|
560
|
+
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");
|
|
443
561
|
try {
|
|
444
562
|
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
445
563
|
} catch {
|
|
446
|
-
throw new
|
|
564
|
+
throw new EncodingError("Invalid UTF-8 encoding detected in Deepnote file");
|
|
447
565
|
}
|
|
448
566
|
}
|
|
449
567
|
/**
|
|
@@ -453,10 +571,10 @@ function decodeUtf8NoBom(bytes) {
|
|
|
453
571
|
* For proper UTF-8 validation, use decodeUtf8NoBom() on raw bytes before decoding.
|
|
454
572
|
*
|
|
455
573
|
* @param yamlContent - Already-decoded YAML string
|
|
456
|
-
* @throws
|
|
574
|
+
* @throws EncodingError if BOM prefix detected
|
|
457
575
|
*/
|
|
458
576
|
function validateNoBomPrefix(yamlContent) {
|
|
459
|
-
if (yamlContent.charCodeAt(0) === 65279) throw new
|
|
577
|
+
if (yamlContent.charCodeAt(0) === 65279) throw new EncodingError("UTF-8 BOM detected in Deepnote file - files must be UTF-8 without BOM");
|
|
460
578
|
}
|
|
461
579
|
/**
|
|
462
580
|
* Validates that the YAML document doesn't contain prohibited features:
|
|
@@ -465,13 +583,13 @@ function validateNoBomPrefix(yamlContent) {
|
|
|
465
583
|
* - No custom tags (!tag)
|
|
466
584
|
*/
|
|
467
585
|
function validateYamlStructure(yamlContent) {
|
|
468
|
-
if (/(?:^|\n)\s*(?:-\s+|[\w-]+:\s*)&\w+/.test(yamlContent)) throw new
|
|
469
|
-
if (/(?:^|\n)\s*(?:-\s+|[\w-]+:\s*)\*\w+/.test(yamlContent)) throw new
|
|
470
|
-
if (/<<:/.test(yamlContent)) throw new
|
|
586
|
+
if (/(?:^|\n)\s*(?:-\s+|[\w-]+:\s*)&\w+/.test(yamlContent)) throw new ProhibitedYamlFeatureError("YAML anchors (&) are not allowed in Deepnote files", { feature: "anchor" });
|
|
587
|
+
if (/(?:^|\n)\s*(?:-\s+|[\w-]+:\s*)\*\w+/.test(yamlContent)) throw new ProhibitedYamlFeatureError("YAML aliases (*) are not allowed in Deepnote files", { feature: "alias" });
|
|
588
|
+
if (/<<:/.test(yamlContent)) throw new ProhibitedYamlFeatureError("YAML merge keys (<<) are not allowed in Deepnote files", { feature: "merge-key" });
|
|
471
589
|
const matches = yamlContent.match(/(?:^|\n)\s*(?:-\s+|[\w-]+:\s*)(![\w/-]+)/gm);
|
|
472
590
|
if (matches) {
|
|
473
591
|
const tags = matches.map((m) => m.match(/(![\w/-]+)/)?.[1]).filter(Boolean);
|
|
474
|
-
if (tags.length > 0) throw new
|
|
592
|
+
if (tags.length > 0) throw new ProhibitedYamlFeatureError(`YAML tags are not allowed in Deepnote files: ${tags.join(", ")}`, { feature: "tag" });
|
|
475
593
|
}
|
|
476
594
|
}
|
|
477
595
|
/**
|
|
@@ -486,8 +604,8 @@ function parseAndValidate(yamlContent) {
|
|
|
486
604
|
});
|
|
487
605
|
if (doc.errors.length > 0) {
|
|
488
606
|
const duplicateKeyError = doc.errors.find((err) => err.message.includes("duplicate") || err.message.includes("key"));
|
|
489
|
-
if (duplicateKeyError) throw new
|
|
490
|
-
throw new
|
|
607
|
+
if (duplicateKeyError) throw new YamlParseError(`Duplicate keys detected in Deepnote file: ${duplicateKeyError.message}`);
|
|
608
|
+
throw new YamlParseError(`YAML parsing error: ${doc.errors[0].message}`);
|
|
491
609
|
}
|
|
492
610
|
return doc.toJS();
|
|
493
611
|
}
|
|
@@ -506,8 +624,8 @@ function parseYaml(yamlContent) {
|
|
|
506
624
|
validateYamlStructure(yamlContent);
|
|
507
625
|
return parseAndValidate(yamlContent);
|
|
508
626
|
} catch (error) {
|
|
509
|
-
|
|
510
|
-
throw new
|
|
627
|
+
if (error instanceof DeepnoteError) throw error;
|
|
628
|
+
throw new ParseError(`Failed to parse Deepnote file: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
511
629
|
}
|
|
512
630
|
}
|
|
513
631
|
|
|
@@ -521,23 +639,13 @@ function deserializeDeepnoteFile(yamlContent) {
|
|
|
521
639
|
const result = deepnoteFileSchema.safeParse(parsed);
|
|
522
640
|
if (!result.success) {
|
|
523
641
|
const issue = result.error.issues[0];
|
|
524
|
-
if (!issue) throw new
|
|
642
|
+
if (!issue) throw new SchemaValidationError("Invalid Deepnote file.");
|
|
525
643
|
const path = issue.path.join(".");
|
|
526
|
-
|
|
527
|
-
throw new Error(`Failed to parse the Deepnote file: ${message}.`);
|
|
644
|
+
throw new SchemaValidationError(`Failed to parse the Deepnote file: ${path ? `${path}: ${issue.message}` : issue.message}.`);
|
|
528
645
|
}
|
|
529
646
|
return result.data;
|
|
530
647
|
}
|
|
531
648
|
|
|
532
|
-
//#endregion
|
|
533
|
-
//#region src/blocks.ts
|
|
534
|
-
var UnsupportedBlockTypeError = class extends Error {
|
|
535
|
-
constructor(message) {
|
|
536
|
-
super(message);
|
|
537
|
-
this.name = "UnsupportedBlockTypeError";
|
|
538
|
-
}
|
|
539
|
-
};
|
|
540
|
-
|
|
541
649
|
//#endregion
|
|
542
650
|
//#region src/blocks/image-blocks.ts
|
|
543
651
|
function escapeHtmlAttribute(value) {
|
|
@@ -585,7 +693,7 @@ function createMarkdownForTextBlock(block) {
|
|
|
585
693
|
if (block.type === "text-cell-todo") return `- ${block.metadata?.checked ? "[x]" : "[ ]"} ${escapeMarkdown(content)}`;
|
|
586
694
|
if (block.type === "text-cell-callout") return `> ${escapeMarkdown(content)}`;
|
|
587
695
|
if (block.type === "text-cell-p") return escapeMarkdown(content);
|
|
588
|
-
throw new
|
|
696
|
+
throw new UnsupportedBlockTypeError("Unhandled block type.");
|
|
589
697
|
}
|
|
590
698
|
function stripMarkdownFromTextBlock(block) {
|
|
591
699
|
const content = block.content ?? "";
|
|
@@ -596,7 +704,7 @@ function stripMarkdownFromTextBlock(block) {
|
|
|
596
704
|
if (block.type === "text-cell-todo") return content.replace(/^-+\s+\[.\]\s+/, "").trim();
|
|
597
705
|
if (block.type === "text-cell-callout") return content.replace(/^>\s+/, "").trim();
|
|
598
706
|
if (block.type === "text-cell-p") return content.trim();
|
|
599
|
-
throw new
|
|
707
|
+
throw new UnsupportedBlockTypeError("Unhandled block type.");
|
|
600
708
|
}
|
|
601
709
|
function createMarkdownForSeparatorBlock(_block) {
|
|
602
710
|
return "<hr>";
|
|
@@ -870,9 +978,9 @@ function createPythonCodeForInputSelectBlock(block) {
|
|
|
870
978
|
function createPythonCodeForInputSliderBlock(block) {
|
|
871
979
|
const sanitizedPythonVariableName = sanitizePythonVariableName(block.metadata.deepnote_variable_name);
|
|
872
980
|
const value = block.metadata.deepnote_variable_value;
|
|
873
|
-
if (!/^-?\d+\.?\d*$|^-?\d*\.\d+$/.test(value)) throw new
|
|
981
|
+
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 });
|
|
874
982
|
const numericValue = Number(value);
|
|
875
|
-
if (!Number.isFinite(numericValue)) throw new
|
|
983
|
+
if (!Number.isFinite(numericValue)) throw new InvalidValueError(`Invalid numeric value for slider input: "${value}". Value must be finite.`, { value });
|
|
876
984
|
return `${sanitizedPythonVariableName} = ${numericValue}`;
|
|
877
985
|
}
|
|
878
986
|
function createPythonCodeForInputFileBlock(block) {
|
|
@@ -906,7 +1014,7 @@ function createPythonCodeForInputDateRangeBlock(block) {
|
|
|
906
1014
|
return pythonCode.dateRangeCustomDays(sanitizedPythonVariableName, Number(customDays));
|
|
907
1015
|
} else if (isValidRelativeDateInterval(block.metadata.deepnote_variable_value)) {
|
|
908
1016
|
const range = DATE_RANGE_INPUT_RELATIVE_RANGES.find((range$1) => range$1.value === block.metadata.deepnote_variable_value);
|
|
909
|
-
if (!range) throw new
|
|
1017
|
+
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 });
|
|
910
1018
|
return ts_dedent.dedent`
|
|
911
1019
|
${range.pythonCode(sanitizedPythonVariableName)}`;
|
|
912
1020
|
} else return ts_dedent.dedent`
|
|
@@ -939,12 +1047,50 @@ function isInputDateRangeBlock(block) {
|
|
|
939
1047
|
}
|
|
940
1048
|
|
|
941
1049
|
//#endregion
|
|
942
|
-
//#region src/blocks/
|
|
943
|
-
function
|
|
944
|
-
return
|
|
1050
|
+
//#region src/blocks/notebook-function-blocks.ts
|
|
1051
|
+
function isNotebookFunctionBlock(block) {
|
|
1052
|
+
return block.type === "notebook-function";
|
|
945
1053
|
}
|
|
946
|
-
function
|
|
947
|
-
|
|
1054
|
+
function createPythonCodeForNotebookFunctionBlock(block) {
|
|
1055
|
+
const notebookId = block.metadata?.function_notebook_id;
|
|
1056
|
+
const inputs = block.metadata?.function_notebook_inputs ?? {};
|
|
1057
|
+
const exportMappings = block.metadata?.function_notebook_export_mappings ?? {};
|
|
1058
|
+
if (!notebookId) return ts_dedent.dedent`
|
|
1059
|
+
# Notebook Function: Not configured
|
|
1060
|
+
pass
|
|
1061
|
+
`;
|
|
1062
|
+
const enabledExports = Object.entries(exportMappings).filter(([, mapping]) => mapping.enabled);
|
|
1063
|
+
const inputsAsString = JSON.stringify(inputs);
|
|
1064
|
+
const exportMappingsDict = {};
|
|
1065
|
+
for (const [exportName, mapping] of enabledExports) exportMappingsDict[exportName] = mapping.variable_name;
|
|
1066
|
+
const exportMappingsAsString = JSON.stringify(exportMappingsDict);
|
|
1067
|
+
const inputsComment = `Inputs: ${inputsAsString}`;
|
|
1068
|
+
const exportsComment = enabledExports.length > 0 ? `Exports: ${enabledExports.map(([name, mapping]) => `${name} -> ${mapping.variable_name}`).join(", ")}` : "Exports: (none)";
|
|
1069
|
+
const functionCall = ts_dedent.dedent`
|
|
1070
|
+
_dntk.run_notebook_function(
|
|
1071
|
+
${escapePythonString(notebookId)},
|
|
1072
|
+
inputs=${inputsAsString},
|
|
1073
|
+
export_mappings=${exportMappingsAsString}
|
|
1074
|
+
)
|
|
1075
|
+
`;
|
|
1076
|
+
if (enabledExports.length === 0) return ts_dedent.dedent`
|
|
1077
|
+
# Notebook Function: ${notebookId}
|
|
1078
|
+
# ${inputsComment}
|
|
1079
|
+
# ${exportsComment}
|
|
1080
|
+
${functionCall}
|
|
1081
|
+
`;
|
|
1082
|
+
if (enabledExports.length === 1) return ts_dedent.dedent`
|
|
1083
|
+
# Notebook Function: ${notebookId}
|
|
1084
|
+
# ${inputsComment}
|
|
1085
|
+
# ${exportsComment}
|
|
1086
|
+
${enabledExports[0][1]?.variable_name} = ${functionCall}
|
|
1087
|
+
`;
|
|
1088
|
+
return ts_dedent.dedent`
|
|
1089
|
+
# Notebook Function: ${notebookId}
|
|
1090
|
+
# ${inputsComment}
|
|
1091
|
+
# ${exportsComment}
|
|
1092
|
+
${enabledExports.map(([, mapping]) => mapping.variable_name).join(", ")} = ${functionCall}
|
|
1093
|
+
`;
|
|
948
1094
|
}
|
|
949
1095
|
|
|
950
1096
|
//#endregion
|
|
@@ -1013,18 +1159,34 @@ function createPythonCode(block, executionContext) {
|
|
|
1013
1159
|
if (isVisualizationBlock(block)) return createPythonCodeForVisualizationBlock(block);
|
|
1014
1160
|
if (isButtonBlock(block)) return createPythonCodeForButtonBlock(block, executionContext);
|
|
1015
1161
|
if (isBigNumberBlock(block)) return createPythonCodeForBigNumberBlock(block);
|
|
1162
|
+
if (isNotebookFunctionBlock(block)) return createPythonCodeForNotebookFunctionBlock(block);
|
|
1016
1163
|
throw new UnsupportedBlockTypeError(`Creating python code from block type ${block.type} is not supported yet.`);
|
|
1017
1164
|
}
|
|
1018
1165
|
|
|
1019
1166
|
//#endregion
|
|
1167
|
+
exports.DeepnoteError = DeepnoteError;
|
|
1168
|
+
exports.EncodingError = EncodingError;
|
|
1169
|
+
exports.INPUT_BLOCK_TYPES = INPUT_BLOCK_TYPES;
|
|
1170
|
+
exports.InvalidValueError = InvalidValueError;
|
|
1171
|
+
exports.ParseError = ParseError;
|
|
1172
|
+
exports.ProhibitedYamlFeatureError = ProhibitedYamlFeatureError;
|
|
1173
|
+
exports.SchemaValidationError = SchemaValidationError;
|
|
1174
|
+
exports.UnsupportedBlockTypeError = UnsupportedBlockTypeError;
|
|
1175
|
+
exports.YamlParseError = YamlParseError;
|
|
1176
|
+
exports.convertToEnvironmentVariableName = convertToEnvironmentVariableName;
|
|
1020
1177
|
exports.createMarkdown = createMarkdown;
|
|
1021
1178
|
exports.createPythonCode = createPythonCode;
|
|
1022
1179
|
exports.decodeUtf8NoBom = decodeUtf8NoBom;
|
|
1023
1180
|
exports.deepnoteBlockSchema = deepnoteBlockSchema;
|
|
1024
1181
|
exports.deepnoteFileSchema = deepnoteFileSchema;
|
|
1182
|
+
exports.deepnoteSnapshotSchema = deepnoteSnapshotSchema;
|
|
1025
1183
|
exports.deserializeDeepnoteFile = deserializeDeepnoteFile;
|
|
1026
1184
|
exports.environmentSchema = environmentSchema;
|
|
1027
1185
|
exports.executionErrorSchema = executionErrorSchema;
|
|
1028
1186
|
exports.executionSchema = executionSchema;
|
|
1029
1187
|
exports.executionSummarySchema = executionSummarySchema;
|
|
1188
|
+
exports.getSqlEnvVarName = getSqlEnvVarName;
|
|
1189
|
+
exports.isExecutableBlock = isExecutableBlock;
|
|
1190
|
+
exports.isExecutableBlockType = isExecutableBlockType;
|
|
1191
|
+
exports.parseYaml = parseYaml;
|
|
1030
1192
|
exports.stripMarkdown = stripMarkdown;
|