Skip to main content

Monitoring and Error Handling

Monitor the public boundaries that make the deployed product work, then handle SDK-reported failures at the boundary that owns them. ChartError and SdkError carry stable machine-readable codes; use the code—not message matching—to choose the user response, retry policy, and diagnostic owner.

Monitor user-visible boundaries

BoundaryHealthy signalFailure signal
Authorization and mountSDK creation and mounted.ready() completeauthorization.*, widget.mount, or engine.unsupported
Market dataHistory completes and realtime remains currentdatafeed.*, unexpected empty results, or stale update age
WorkersWorker status matches the release configurationStartup fallback, blocked asset, or unexpected main-thread execution
StorageSaves and loads complete at the expected revisionstorage.*, permission denial, conflict, or stale response
TradingConnection and authoritative state remain currenttrading.*, rejected mutation, disconnect, or stale broker state
LifecycleTeardown returns subscriptions and workers to baselineDuplicate mounts, leaked listeners, or updates after destruction

Collect rates and timings at your application boundary. Do not send complete leases, credentials, orders, or market-data payloads to telemetry.

Two error surfaces

Reported errors arrive as ChartError objects through the mount-level onError callback and the chart-level data-error event. They describe failures the chart has already contained — a rejected mount, a failed history load, an unsupported engine — and tell you whether the chart is still usable:

interface ChartError {
code: ChartErrorCode | (string & {}); // e.g. 'datafeed.loadBars', 'widget.mount'
message: string;
source?: 'datafeed' | 'engine' | 'widget' | 'controller' | 'adapter' | 'customer';
severity?: 'info' | 'warning' | 'error' | 'fatal';
recoverable?: boolean; // true when the chart keeps operating and retry may help
timestamp?: number; // ms since the Unix epoch
details?: Record<string, unknown>;
cause?: unknown;
}

Thrown errors are SdkError instances raised by public API calls — layout saves, trading operations, authorization. Narrow unknown caught values with the exported isSdkError type guard and branch on error.code:

import { isSdkError, DrawingRevisionConflictError } from '@tradescript/pro/sdk';

try {
await widget.chartLayouts().saveAs('my-layout', 'My layout');
} catch (error) {
if (error instanceof DrawingRevisionConflictError) {
// code 'storage.revision-conflict': reload the newer server state, then let the user re-save.
} else if (isSdkError(error)) {
logger.error('chart_error', { code: error.code, details: error.details });
}
throw error;
}

Typed subclasses exist where a code deserves dedicated handling: ChartAuthorizationError carries the authorization.* codes, DrawingRevisionConflictError always carries storage.revision-conflict, and DrawingPermissionError always carries storage.permission-denied.

Error phases

PhaseTypical ownerUser experienceRetry policy
Authorization or licensingHost backendExplain that the chart is unavailable; preserve the rest of the pageRefresh a renewable lease once; never expose credentials
InitializationHost applicationReplace the mount area with a useful failure stateRetry only after fixing the rejected option or missing asset
DatafeedMarket-data adapterKeep chart chrome usable; show loading, empty, delayed, or failed data stateRetry transient transport failures with bounded backoff
RenderingChart runtimePreserve the last valid state where safeRecreate the widget only after collecting the error and runtime context
StorageStorage adapterKeep the current in-memory chart; explain that save/load failedRetry idempotent reads; require conflict resolution for writes
TradingBroker adapter or bridgeKeep the draft and display the broker rejection beside the actionNever blindly repeat an order submission
CleanupHost lifecycleRemove stale subscriptions and listenersMake cleanup idempotent; do not create a replacement until teardown completes

Code families

SdkError.code is namespaced by subsystem, so the prefix identifies the phase and owner before you read the message:

Code familyPhaseExample codes
authorization.*Licensing and authorizationauthorization.token-expired, authorization.entitlement-denied
widget.*Initialization and cleanupwidget.mount, widget.destroyed
engine.*Renderingengine.unsupported, engine.unmounted
datafeed.*Datafeeddatafeed.loadBars, datafeed.request-failed, datafeed.session-disconnected
history.*Datafeed (superseded loads)history.superseded, history.cancelled — usually benign; a newer request replaced this one
storage.*Storagestorage.unavailable, storage.revision-conflict, storage.permission-denied, storage.stale-response
trading.*Brokertrading.validation, trading.adapter-unsupported
theme.not-foundCustomizationRequested theme preset does not exist
validation, revision_conflictAny public callRejected input or a write that lost a revision race

Catch errors at the owning boundary

const mounted = sdk.chart.mount({
mount: '#chart',
symbol: 'AAPL',
interval: '1D',
datafeed,
onError(error) {
showChartFailure(error.message, { canRetry: error.recoverable === true });
logger.error('chart_error', {
code: error.code,
source: error.source,
severity: error.severity,
recoverable: error.recoverable ?? false,
chartId: 'primary-chart',
});
},
});

Show the user the outcome, not the raw code or SDK message: a failed data state for datafeed.*, an unavailable-chart state for authorization.* and widget.mount, and the broker's own rejection message beside the order action for trading.*.

Log the code, source, severity, recoverable, the active symbol and interval, failed public operation, application version, SDK version, customer build fingerprint, deployed origin, and a correlation ID. Do not log complete leases, permanent credentials, broker secrets, full customer orders, or unbounded market-data payloads.

When to retry

  1. Classify the phase. The code prefix names the boundary: widget, datafeed, storage, trading, or authorization.
  2. Protect user intent. Preserve an unsaved layout or order draft when the failed operation did not invalidate it.
  3. Retry only idempotent work. Reads and renewable leases may be safe; order placement and conflicting writes (storage.revision-conflict, revision_conflict) are not.
  4. Verify the recovered state. A cleared error is not enough — confirm data updates, saved state, or broker acknowledgement at the original boundary.

Next steps