Foundational classes and utilities for building interactive widgets in Jupyter environments
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
A comprehensive set of utility functions for serialization, buffer handling, view management, promise resolution, and common operations used throughout the Jupyter widgets ecosystem.
Utilities for handling widget model references in complex nested data structures.
/**
* Recursively replace model ID strings with actual model instances
* @param value - Data structure containing model references
* @param manager - Widget manager for model resolution
* @returns Promise resolving to data with unpacked model instances
*/
function unpack_models(
value: any | Dict<unknown> | string | (Dict<unknown> | string)[],
manager?: IWidgetManager
): Promise<WidgetModel | Dict<WidgetModel> | WidgetModel[] | any>;
/**
* Recursively replace model instances with their ID strings
* @param value - Data structure containing model instances
* @param widget - Widget context for serialization
* @returns Data with model instances replaced by ID strings
*/
function pack_models(
value: WidgetModel | Dict<WidgetModel> | WidgetModel[] | any,
widget?: WidgetModel
): any | Dict<unknown> | string | (Dict<unknown> | string)[];Usage Examples:
// Unpack model references received from kernel
const rawData = {
title: "My Dashboard",
widgets: ["IPY_MODEL_abc123", "IPY_MODEL_def456"],
config: {
layout: "IPY_MODEL_layout1",
theme: "IPY_MODEL_theme1"
}
};
const processedData = await unpack_models(rawData, widgetManager);
// Now processedData.widgets contains actual WidgetModel instances
// processedData.config.layout is a LayoutModel instance
// Pack models for transmission to kernel
const dataToSend = {
selectedWidgets: [widget1, widget2],
mainLayout: layoutModel
};
const serializedData = pack_models(dataToSend);
// Becomes: { selectedWidgets: ["IPY_MODEL_abc123", "IPY_MODEL_def456"], mainLayout: "IPY_MODEL_layout1" }Functions for managing binary data within JSON-serializable state objects.
/**
* Insert binary buffers into a state object at specified paths
* @param state - State object to modify
* @param buffer_paths - Array of paths where buffers should be placed
* @param buffers - Array of binary buffers to insert
*/
function put_buffers(
state: Dict<BufferJSON>,
buffer_paths: (string | number)[][],
buffers: (DataView | ArrayBuffer | ArrayBufferView | { buffer: ArrayBuffer })[]
): void;
/**
* Extract binary buffers from a state object, replacing them with null/removed keys
* @param state - State object containing binary data
* @returns Object with separated state, buffer paths, and buffers
*/
function remove_buffers(
state: BufferJSON | ISerializeable
): ISerializedState;
/**
* Type representing JSON-serializable data that may contain binary buffers
*/
type BufferJSON =
| { [property: string]: BufferJSON }
| BufferJSON[]
| string
| number
| boolean
| null
| ArrayBuffer
| DataView;
/**
* Result of buffer extraction
*/
interface ISerializedState {
state: JSONObject;
buffers: ArrayBuffer[];
buffer_paths: (string | number)[][];
}Usage Examples:
// Prepare data with binary content for transmission
const stateWithBuffers = {
metadata: { name: "image_data", type: "png" },
imageData: new Uint8Array([137, 80, 78, 71, /* ... */]),
thumbnails: [
new Uint8Array([/* thumb1 */]),
new Uint8Array([/* thumb2 */])
]
};
// Extract buffers for separate transmission
const { state, buffer_paths, buffers } = remove_buffers(stateWithBuffers);
// state = { metadata: {...}, thumbnails: [null, null] }
// buffer_paths = [["imageData"], ["thumbnails", 0], ["thumbnails", 1]]
// buffers = [ArrayBuffer, ArrayBuffer, ArrayBuffer]
// Reconstruct on receiving end
const receivedState = { metadata: {...}, thumbnails: [null, null] };
put_buffers(receivedState, buffer_paths, buffers);
// receivedState now has binary data restoredUtility class for managing ordered collections of views with automatic synchronization.
/**
* Utility for managing ordered collections of views
*/
class ViewList<T> {
/**
* Create a view list with handlers for view creation and removal
* @param create_view - Function to create views from models
* @param remove_view - Function to clean up views (null for default removal)
* @param context - Context for handler function calls
*/
constructor(
create_view: (model: any, index: any) => T | Promise<T>,
remove_view: ((view: T) => void) | null,
context: any
);
/**
* Initialize the view list with handlers
* @param create_view - View creation function
* @param remove_view - View removal function
* @param context - Execution context
*/
initialize(
create_view: (model: any, index: any) => T | Promise<T>,
remove_view: ((view: T) => void) | null,
context: any
): void;
/**
* Update the view collection to match a new model list
* @param new_models - Array of models for the views
* @param create_view - Optional override for view creation
* @param remove_view - Optional override for view removal
* @param context - Optional override for execution context
* @returns Promise resolving to array of all views
*/
update(
new_models: any[],
create_view?: (model: any, index: any) => T | Promise<T>,
remove_view?: (view: T) => void,
context?: any
): Promise<T[]>;
/**
* Remove all views from the list
* @returns Promise that resolves when all views are removed
*/
remove(): Promise<void>;
/**
* Dispose the view list without removing views (synchronous cleanup)
*/
dispose(): void;
// Properties
views: Promise<T>[];
}Usage Examples:
// Create view list for managing child widgets
const createChildView = async (model: WidgetModel, index: number) => {
const view = await this.create_child_view(model);
this.childContainer.appendChild(view.el);
return view;
};
const removeChildView = (view: WidgetView) => {
if (view.el.parentNode) {
view.el.parentNode.removeChild(view.el);
}
view.remove();
};
const childViews = new ViewList(createChildView, removeChildView, this);
// Update views when model list changes
this.listenTo(this.model, 'change:children', () => {
childViews.update(this.model.get('children'));
});
// Get all current views
const allViews = await Promise.all(childViews.views);
// Clean up when done
await childViews.remove();Helper functions for working with promises in complex data structures.
/**
* Resolve all promises in a dictionary object
* @param d - Dictionary containing promises or values
* @returns Promise resolving to dictionary with resolved values
*/
function resolvePromisesDict<V>(
d: Dict<PromiseLike<V> | V>
): Promise<Dict<V>>;
/**
* Create a promise rejection handler with logging
* @param message - Error message to log
* @param log - Whether to log the error
* @returns Function that logs and re-throws errors
*/
function reject(message: string, log: boolean): (error: Error) => never;
/**
* Simple dictionary type
*/
type Dict<T> = { [keys: string]: T };Usage Examples:
// Resolve multiple async operations
const operations = {
loadData: fetchUserData(),
loadConfig: fetchConfiguration(),
loadTheme: fetchThemeSettings()
};
const results = await resolvePromisesDict(operations);
// results.loadData, results.loadConfig, results.loadTheme are all resolved
// Create error handler with context
const handleModelError = reject('Failed to create model', true);
try {
const model = await createComplexModel();
} catch (error) {
handleModelError(error); // Logs "Failed to create model" then re-throws
}Utility functions for common data operations.
/**
* Find elements in first array that are not in second array
* @param a - First array
* @param b - Second array
* @returns Elements in a but not in b
*/
function difference(a: string[], b: string[]): string[];
/**
* Deep equality comparison using lodash
* @param a - First value to compare
* @param b - Second value to compare
* @returns True if values are deeply equal
*/
function isEqual(a: unknown, b: unknown): boolean;
/**
* Object.assign polyfill for older environments
* @param target - Target object
* @param sources - Source objects to merge
* @returns Merged target object
*/
const assign: (target: any, ...sources: any[]) => any;
/**
* Generate a UUID using Lumino's UUID implementation
* @returns UUID string
*/
function uuid(): string;Usage Examples:
// Find differences in arrays
const oldClasses = ['widget', 'active', 'highlighted'];
const newClasses = ['widget', 'inactive'];
const toRemove = difference(oldClasses, newClasses); // ['active', 'highlighted']
const toAdd = difference(newClasses, oldClasses); // ['inactive']
// Deep equality checking
const state1 = { user: { name: 'Alice', preferences: { theme: 'dark' } } };
const state2 = { user: { name: 'Alice', preferences: { theme: 'dark' } } };
console.log(isEqual(state1, state2)); // true
// Safe object merging
const defaults = { timeout: 5000, retries: 3 };
const userOptions = { timeout: 10000 };
const config = assign({}, defaults, userOptions); // { timeout: 10000, retries: 3 }
// Generate unique identifiers
const widgetId = uuid(); // e.g., "f47ac10b-58cc-4372-a567-0e02b2c3d479"Functions for runtime type checking and validation.
/**
* Check if an object has a toJSON method
* @param object - Object to check
* @returns True if object is serializable
*/
function isSerializable(object: unknown): object is ISerializeable;
/**
* Check if data is a plain object (not array, null, etc.)
* @param data - Data to check
* @returns True if data is a plain object
*/
function isObject(data: BufferJSON): data is Dict<BufferJSON>;
/**
* Interface for objects that can be serialized to JSON
*/
interface ISerializeable {
toJSON(options?: {}): JSONObject;
}Usage Examples:
// Check serializability before processing
const processData = (data: unknown) => {
if (isSerializable(data)) {
const jsonData = data.toJSON();
// Process as JSON
} else if (isObject(data)) {
// Process as plain object
Object.keys(data).forEach(key => {
processData(data[key]);
});
}
};
// Type-safe object iteration
const handleComplexState = (state: BufferJSON) => {
if (isObject(state)) {
// TypeScript knows state is Dict<BufferJSON>
for (const [key, value] of Object.entries(state)) {
console.log(`Processing ${key}:`, value);
}
}
};// Batch model operations with error handling
const batchUpdateModels = async (updates: Array<{model: WidgetModel, changes: any}>) => {
const operations = updates.map(({model, changes}) =>
model.set(changes) && model.save_changes()
);
try {
await Promise.all(operations);
console.log('All models updated successfully');
} catch (error) {
reject('Batch update failed', true)(error);
}
};
// Batch view creation with ViewList
const createViewBatch = async (models: WidgetModel[]) => {
const viewList = new ViewList(
async (model: WidgetModel) => await createWidgetView(model),
(view: WidgetView) => view.remove(),
this
);
return await viewList.update(models);
};// Synchronize complex nested state with binary data
const syncComplexState = async (localState: any, manager: IWidgetManager) => {
// Unpack any model references
const withModels = await unpack_models(localState, manager);
// Handle binary data if present
if (withModels._buffer_paths && withModels._buffers) {
put_buffers(withModels, withModels._buffer_paths, withModels._buffers);
delete withModels._buffer_paths;
delete withModels._buffers;
}
return withModels;
};
// Prepare state for transmission
const prepareStateForSync = (state: any) => {
// Pack model instances to references
const packedState = pack_models(state);
// Extract binary buffers
const { state: cleanState, buffer_paths, buffers } = remove_buffers(packedState);
return {
state: cleanState,
buffer_paths: buffer_paths,
buffers: buffers
};
};// Robust operation with retries
const robustOperation = async <T>(
operation: () => Promise<T>,
retries = 3,
delay = 1000
): Promise<T> => {
let lastError: Error;
for (let attempt = 0; attempt <= retries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (attempt < retries) {
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, attempt)));
}
}
}
throw reject(`Operation failed after ${retries} attempts`, true)(lastError!);
};
// Safe view cleanup
const safeCleanup = async (views: Promise<WidgetView>[]) => {
const settledResults = await Promise.allSettled(views);
settledResults.forEach((result, index) => {
if (result.status === 'fulfilled') {
try {
result.value.remove();
} catch (error) {
console.warn(`Failed to remove view ${index}:`, error);
}
} else {
console.warn(`View ${index} was rejected:`, result.reason);
}
});
};/**
* SVG icon displayed in error widgets
*/
const BROKEN_FILE_SVG_ICON: string;Usage Example:
// Use in custom error display
const showErrorIcon = () => {
const errorDiv = document.createElement('div');
errorDiv.innerHTML = BROKEN_FILE_SVG_ICON;
errorDiv.className = 'widget-error-display';
return errorDiv;
};