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
| Boundary | Healthy signal | Failure signal |
|---|---|---|
| Authorization and mount | SDK creation and mounted.ready() complete | authorization.*, widget.mount, or engine.unsupported |
| Market data | History completes and realtime remains current | datafeed.*, unexpected empty results, or stale update age |
| Workers | Worker status matches the release configuration | Startup fallback, blocked asset, or unexpected main-thread execution |
| Storage | Saves and loads complete at the expected revision | storage.*, permission denial, conflict, or stale response |
| Trading | Connection and authoritative state remain current | trading.*, rejected mutation, disconnect, or stale broker state |
| Lifecycle | Teardown returns subscriptions and workers to baseline | Duplicate 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
| Phase | Typical owner | User experience | Retry policy |
|---|---|---|---|
| Authorization or licensing | Host backend | Explain that the chart is unavailable; preserve the rest of the page | Refresh a renewable lease once; never expose credentials |
| Initialization | Host application | Replace the mount area with a useful failure state | Retry only after fixing the rejected option or missing asset |
| Datafeed | Market-data adapter | Keep chart chrome usable; show loading, empty, delayed, or failed data state | Retry transient transport failures with bounded backoff |
| Rendering | Chart runtime | Preserve the last valid state where safe | Recreate the widget only after collecting the error and runtime context |
| Storage | Storage adapter | Keep the current in-memory chart; explain that save/load failed | Retry idempotent reads; require conflict resolution for writes |
| Trading | Broker adapter or bridge | Keep the draft and display the broker rejection beside the action | Never blindly repeat an order submission |
| Cleanup | Host lifecycle | Remove stale subscriptions and listeners | Make 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 family | Phase | Example codes |
|---|---|---|
authorization.* | Licensing and authorization | authorization.token-expired, authorization.entitlement-denied |
widget.* | Initialization and cleanup | widget.mount, widget.destroyed |
engine.* | Rendering | engine.unsupported, engine.unmounted |
datafeed.* | Datafeed | datafeed.loadBars, datafeed.request-failed, datafeed.session-disconnected |
history.* | Datafeed (superseded loads) | history.superseded, history.cancelled — usually benign; a newer request replaced this one |
storage.* | Storage | storage.unavailable, storage.revision-conflict, storage.permission-denied, storage.stale-response |
trading.* | Broker | trading.validation, trading.adapter-unsupported |
theme.not-found | Customization | Requested theme preset does not exist |
validation, revision_conflict | Any public call | Rejected 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
- Classify the phase. The code prefix names the boundary: widget, datafeed, storage, trading, or authorization.
- Protect user intent. Preserve an unsaved layout or order draft when the failed operation did not invalidate it.
- Retry only idempotent work. Reads and renewable leases may be safe;
order placement and conflicting writes (
storage.revision-conflict,revision_conflict) are not. - Verify the recovered state. A cleared error is not enough — confirm data updates, saved state, or broker acknowledgement at the original boundary.
Next steps
- Troubleshooting — the symptom-by-symptom investigation sequence for each boundary.
- Verify your deployment — assert on these codes in the candidate release.
- Production Authorization — the authorization failures listed above, in context.