@dxfeed/dxlink-console-core 0.0.0-stage → 0.10.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/build/index.d.mts +524 -0
- package/build/index.d.ts +524 -0
- package/build/index.js +1031 -0
- package/build/index.js.map +1 -0
- package/build/index.mjs +979 -0
- package/build/index.mjs.map +1 -0
- package/package.json +81 -3
- package/README.md +0 -3
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ComponentType, Provider, Context, Component, ErrorInfo } from 'react';
|
|
3
|
+
import { Theme, ThemeOptions } from '@mui/material/styles';
|
|
4
|
+
import { DXLinkError, DXLinkConnectionState, DXLinkAuthState, DXLinkConnectionDetails, DXLinkClient, DXLinkChannel } from '@dxfeed/dxlink-api';
|
|
5
|
+
import * as zustand from 'zustand';
|
|
6
|
+
import { StoreApi } from 'zustand';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* One channel service, as the channels area sees it.
|
|
10
|
+
*
|
|
11
|
+
* The area knows nothing about FEED, DOM, INDICHART or RPC: it renders an add-button per
|
|
12
|
+
* registered plugin, holds one request value per plugin, and asks the plugin to turn that
|
|
13
|
+
* request into a channel config and then to render the channel. Everything that used to be
|
|
14
|
+
* a four-way switch is a lookup on this descriptor.
|
|
15
|
+
*
|
|
16
|
+
* Plugins reach the connection the same way the channel components always have —
|
|
17
|
+
* `useConnectionVM()` for the view model, `useVM` to read its state. Those two are the
|
|
18
|
+
* whole host API; there is no plugin-specific context.
|
|
19
|
+
*/
|
|
20
|
+
interface ChannelPlugin<Config, Request> {
|
|
21
|
+
/**
|
|
22
|
+
* Stable identifier for this service, unique among the registered plugins.
|
|
23
|
+
*
|
|
24
|
+
* Any string: core deliberately holds no list of known kinds, so a host can register a
|
|
25
|
+
* service this package has never heard of. Whoever reads a configured kind name is the one
|
|
26
|
+
* who validates it, against the plugins actually registered.
|
|
27
|
+
*/
|
|
28
|
+
kind: string;
|
|
29
|
+
/** Add-button label; also the channel title and the error-boundary name. */
|
|
30
|
+
label: string;
|
|
31
|
+
icon: ReactNode;
|
|
32
|
+
dialogTitle: string;
|
|
33
|
+
/** Widened for forms that need the room — the RPC form carries three panes. */
|
|
34
|
+
dialogMaxWidth?: 'sm' | 'md';
|
|
35
|
+
/**
|
|
36
|
+
* The value the request form starts from.
|
|
37
|
+
*
|
|
38
|
+
* Called once per plugin when the area mounts, never again: request values are kept
|
|
39
|
+
* between dialog opens so several similar channels are quick to create.
|
|
40
|
+
*/
|
|
41
|
+
createRequest: () => Request;
|
|
42
|
+
RequestForm: ComponentType<{
|
|
43
|
+
value: Request;
|
|
44
|
+
onChange: (next: Request) => void;
|
|
45
|
+
}>;
|
|
46
|
+
/** Whether "Open channel" is enabled. Omitted means always. */
|
|
47
|
+
canOpen?: (request: Request) => boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The config to open a channel with, or null when this request cannot produce one.
|
|
50
|
+
*
|
|
51
|
+
* Only RPC can fail here: its service, method and message are resolved out of a
|
|
52
|
+
* descriptor set, and a stale selection must not open a broken channel.
|
|
53
|
+
*/
|
|
54
|
+
buildConfig: (request: Request) => Config | null;
|
|
55
|
+
Channel: ComponentType<{
|
|
56
|
+
title: string;
|
|
57
|
+
config: Config;
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A plugin with its `Request` and `Config` types erased, as the registry stores them.
|
|
62
|
+
*
|
|
63
|
+
* The erasure is sound because of one invariant the area upholds: a plugin is only ever
|
|
64
|
+
* handed values that came from that same plugin. A request starts at its own
|
|
65
|
+
* `createRequest()` and is only ever replaced by its own `RequestForm`; a config comes from
|
|
66
|
+
* its own `buildConfig()` and goes to its own `Channel`. Neither type crosses a plugin
|
|
67
|
+
* boundary, so the area can treat both as `unknown` without ever being wrong.
|
|
68
|
+
*
|
|
69
|
+
* The cost is that the area cannot type-check the pairing — that is what
|
|
70
|
+
* {@link defineChannelPlugin} is for, and it is the trade a registry makes in exchange for
|
|
71
|
+
* not naming its plugins.
|
|
72
|
+
*/
|
|
73
|
+
type ErasedChannelPlugin = ChannelPlugin<unknown, unknown>;
|
|
74
|
+
/**
|
|
75
|
+
* Declare a channel plugin.
|
|
76
|
+
*
|
|
77
|
+
* Type-checks the descriptor against its own `Config` and `Request` — the form, the
|
|
78
|
+
* builder and the channel component all have to agree — and then erases them for the
|
|
79
|
+
* registry. This is the only place the erasure happens.
|
|
80
|
+
*/
|
|
81
|
+
declare const defineChannelPlugin: <Config, Request>(plugin: ChannelPlugin<Config, Request>) => ErasedChannelPlugin;
|
|
82
|
+
|
|
83
|
+
/** Connection keepalive timings, in seconds. */
|
|
84
|
+
interface KeepaliveConfig {
|
|
85
|
+
interval: number;
|
|
86
|
+
timeout: number;
|
|
87
|
+
acceptTimeout: number;
|
|
88
|
+
}
|
|
89
|
+
/** A group of fields a host can pin, addressed as one. */
|
|
90
|
+
type ConsoleConfigLock = 'wsUrl' | 'keepalive';
|
|
91
|
+
/** Whether a value names a field group this console knows how to pin. */
|
|
92
|
+
declare const isConsoleConfigLock: (value: unknown) => value is ConsoleConfigLock;
|
|
93
|
+
/**
|
|
94
|
+
* What a host can decide about a console before anyone opens it.
|
|
95
|
+
*
|
|
96
|
+
* These are the values forms **start** with, not the values they hold: everything here
|
|
97
|
+
* seeds local draft state once and is never written back, so the user stays free to edit.
|
|
98
|
+
* The exception is {@link ConsoleConfig.locked}, which is how a host says a field is fixed
|
|
99
|
+
* rather than merely suggested.
|
|
100
|
+
*
|
|
101
|
+
* Deliberately small. Anything a single channel service needs — the RPC descriptor-set URL,
|
|
102
|
+
* say — is that plugin's option rather than a field here, so core does not grow a vocabulary
|
|
103
|
+
* for services it knows nothing about.
|
|
104
|
+
*/
|
|
105
|
+
interface ConsoleConfig {
|
|
106
|
+
/** Seeds the connection form's WebSocket URL. Empty means the user supplies one. */
|
|
107
|
+
wsUrl: string;
|
|
108
|
+
keepalive: KeepaliveConfig;
|
|
109
|
+
/**
|
|
110
|
+
* Which channel kinds the add-channel row offers.
|
|
111
|
+
*
|
|
112
|
+
* A set rather than a sequence: button order follows the registered plugins — the list
|
|
113
|
+
* `ChannelsArea` filters — so ordering this list has no effect on the buttons.
|
|
114
|
+
*
|
|
115
|
+
* `null` means every registered plugin, which is the default — with an open kind
|
|
116
|
+
* vocabulary there is no fixed list to enumerate, so "unrestricted" has to be its own
|
|
117
|
+
* value rather than a list that happens to name everything. An empty array means none.
|
|
118
|
+
*/
|
|
119
|
+
channelKinds: readonly string[] | null;
|
|
120
|
+
/**
|
|
121
|
+
* Fields the host has pinned. They render read-only rather than hidden — this is a debug
|
|
122
|
+
* console, so which endpoint you are talking to is worth seeing even when you cannot
|
|
123
|
+
* change it.
|
|
124
|
+
*/
|
|
125
|
+
locked: readonly ConsoleConfigLock[];
|
|
126
|
+
}
|
|
127
|
+
/** A partial profile, as each configuration source produces it. */
|
|
128
|
+
interface ConsoleConfigInput {
|
|
129
|
+
wsUrl?: string;
|
|
130
|
+
keepalive?: Partial<KeepaliveConfig>;
|
|
131
|
+
channelKinds?: readonly string[] | null;
|
|
132
|
+
locked?: readonly ConsoleConfigLock[];
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* The profile a console falls back to when nothing is supplied.
|
|
136
|
+
*
|
|
137
|
+
* There is no WebSocket URL here, and that is the point: deriving one means knowing how this
|
|
138
|
+
* console is deployed — whether it sits next to its endpoint, whether a development build
|
|
139
|
+
* should reach for a shared relay — and core has no business knowing either. A host supplies
|
|
140
|
+
* it as a source (see {@link resolveConsoleConfig}); until one does, the field starts empty
|
|
141
|
+
* and the user types it.
|
|
142
|
+
*/
|
|
143
|
+
declare const builtinConsoleConfig: () => ConsoleConfig;
|
|
144
|
+
interface ConsoleConfigSources {
|
|
145
|
+
/**
|
|
146
|
+
* Defaults the serving application itself carries — the one place a build-time choice
|
|
147
|
+
* (say, a development relay) belongs, so nothing below has to know about bundlers.
|
|
148
|
+
*/
|
|
149
|
+
app?: ConsoleConfigInput;
|
|
150
|
+
/** What the host injected into the page. The only source allowed to lock a field. */
|
|
151
|
+
injected?: ConsoleConfigInput;
|
|
152
|
+
/** What the page URL carried, already parsed. */
|
|
153
|
+
search?: ConsoleConfigInput;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Resolve one profile from every source, later sources winning:
|
|
157
|
+
*
|
|
158
|
+
* ```
|
|
159
|
+
* built-in defaults ← app defaults ← injected config ← query string
|
|
160
|
+
* ```
|
|
161
|
+
*
|
|
162
|
+
* Two rules make locking mean something:
|
|
163
|
+
* - **only the injected config can lock a field.** A query parameter is written by
|
|
164
|
+
* whoever opened the link, so letting it pin a field would be pointless and letting it
|
|
165
|
+
* unpin one would make locking a lie.
|
|
166
|
+
* - **a locked field ignores its query parameter**, otherwise the pin would not hold.
|
|
167
|
+
*
|
|
168
|
+
* Locks deliberately do not gate the app layer: that is the same deployment speaking.
|
|
169
|
+
*
|
|
170
|
+
* Every source arrives already parsed. Reading an injected global or a query string is a
|
|
171
|
+
* standalone-deployment concern and belongs to whoever owns the page; what lives here is the
|
|
172
|
+
* merge order and the lock rules, which are the same wherever the values came from.
|
|
173
|
+
*/
|
|
174
|
+
declare const resolveConsoleConfig: (sources: ConsoleConfigSources) => ConsoleConfig;
|
|
175
|
+
|
|
176
|
+
interface ConsolePageProps {
|
|
177
|
+
/**
|
|
178
|
+
* Initial values for the console's forms; see {@link ConsoleConfig}.
|
|
179
|
+
*
|
|
180
|
+
* Required, like {@link ConsolePageProps.channels}: a profile is a decision about how this
|
|
181
|
+
* console is deployed, and core reads no globals to guess one. `builtinConsoleConfig()` is
|
|
182
|
+
* the do-nothing answer if a host genuinely has no preferences.
|
|
183
|
+
*/
|
|
184
|
+
config: ConsoleConfig;
|
|
185
|
+
/**
|
|
186
|
+
* The channel services this console offers.
|
|
187
|
+
*
|
|
188
|
+
* Required: which services exist is a composition decision, and a host that leaves the
|
|
189
|
+
* market-data plugins out never imports their code.
|
|
190
|
+
*/
|
|
191
|
+
channels: readonly ErasedChannelPlugin[];
|
|
192
|
+
/**
|
|
193
|
+
* The theme the console renders with, from `createConsoleTheme()`.
|
|
194
|
+
*
|
|
195
|
+
* Optional, and it is the one prop with two right answers. A host that already has a MUI
|
|
196
|
+
* `ThemeProvider` above this page — the standalone app does — leaves it out, so there is
|
|
197
|
+
* one theme in the tree rather than a second nested inside the first. A host embedding the
|
|
198
|
+
* console in a page that is not MUI's passes one, and the console is then self-contained.
|
|
199
|
+
*
|
|
200
|
+
* Left out with no provider above, MUI falls back to its own default theme: the console
|
|
201
|
+
* still works, but looks like stock MUI rather than the console.
|
|
202
|
+
*
|
|
203
|
+
* Passing one also hands the host sole ownership of light/dark — see the note on the
|
|
204
|
+
* component below.
|
|
205
|
+
*/
|
|
206
|
+
theme?: Theme;
|
|
207
|
+
}
|
|
208
|
+
declare const ConsolePage: ({ config, channels, theme }: ConsolePageProps) => react.JSX.Element;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Build the console theme, optionally layered with a host's own options.
|
|
212
|
+
*
|
|
213
|
+
* Overrides are deep-merged on top, later arguments winning — so a host adds a font or
|
|
214
|
+
* retints the palette without restating the rest. Pass the result to
|
|
215
|
+
* {@link ConsolePageProps.theme}, or to a `ThemeProvider` above the page.
|
|
216
|
+
*
|
|
217
|
+
* **The merge happens on the options, not on the theme**, and it has to. `createTheme`'s own
|
|
218
|
+
* variadic form (`createTheme(options, ...args)`) merges its extra arguments into the theme it
|
|
219
|
+
* has already computed, which silently strips anything derived: a `typography.fontFamily`
|
|
220
|
+
* supplied that way lands on the theme, but `body1`, `h1` and the rest were built from the
|
|
221
|
+
* default stack a step earlier and keep it. That surfaced as a console rendering in Roboto
|
|
222
|
+
* while `body` rendered in Inter. Palette entries derive the same way.
|
|
223
|
+
*/
|
|
224
|
+
declare const createConsoleTheme: (...overrides: readonly ThemeOptions[]) => Theme;
|
|
225
|
+
|
|
226
|
+
interface ChannelsAreaProps {
|
|
227
|
+
/**
|
|
228
|
+
* The channel services this console offers, in the order their add-buttons appear.
|
|
229
|
+
*
|
|
230
|
+
* Required, and deliberately so: which services exist is a composition decision, not a
|
|
231
|
+
* default. A console that should not offer market data simply does not register those
|
|
232
|
+
* plugins — and then never imports their code either.
|
|
233
|
+
*/
|
|
234
|
+
channels: readonly ErasedChannelPlugin[];
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Channels area (draft / presentational only). Owns the open channels and the
|
|
238
|
+
* per-service channel-request dialog. Request forms keep their values between
|
|
239
|
+
* opens so the user can quickly open several channels; each opened channel
|
|
240
|
+
* manages its own state.
|
|
241
|
+
*
|
|
242
|
+
* Knows nothing about any particular service. Add-buttons, dialog titles, request forms,
|
|
243
|
+
* validation and the channel bodies all come from the registered {@link ChannelPlugin}s, so
|
|
244
|
+
* adding or removing a service touches only the composition site.
|
|
245
|
+
*
|
|
246
|
+
* Two filters, doing different jobs: the registered plugins say which services exist in
|
|
247
|
+
* this build, and the profile's `channelKinds` says which of those this deployment offers
|
|
248
|
+
* (`null` — the default — meaning all of them). The second filters buttons only — an
|
|
249
|
+
* already-open channel keeps rendering, so a profile that disagrees with what is on screen
|
|
250
|
+
* degrades instead of crashing.
|
|
251
|
+
*/
|
|
252
|
+
declare const ChannelsArea: ({ channels }: ChannelsAreaProps) => react.JSX.Element;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* One open channel.
|
|
256
|
+
*
|
|
257
|
+
* `config` is opaque. It was produced by the plugin named by `kind` and is only ever handed
|
|
258
|
+
* back to that same plugin, so the channels area never needs to know what is in it — which
|
|
259
|
+
* is what keeps the four services' types (and their dependencies) out of this feature.
|
|
260
|
+
*/
|
|
261
|
+
interface DraftChannel {
|
|
262
|
+
id: string;
|
|
263
|
+
kind: string;
|
|
264
|
+
config: unknown;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* A protocol error captured for display, stamped with the time it arrived.
|
|
269
|
+
*
|
|
270
|
+
* Used by both scopes: connection errors aggregate on the `ConnectionViewModel`,
|
|
271
|
+
* channel errors stay on the channel VM that owns them.
|
|
272
|
+
*/
|
|
273
|
+
interface TimestampedError {
|
|
274
|
+
/** Stable identity for React keys — errors are prepended, so an index is not stable. */
|
|
275
|
+
id: number;
|
|
276
|
+
type: string;
|
|
277
|
+
message: string;
|
|
278
|
+
time: string;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* How many errors one scope retains.
|
|
282
|
+
*
|
|
283
|
+
* A server that rejects every subscription in a large batch can produce thousands, and a
|
|
284
|
+
* debug console is left open for a long time. Only the most recent are useful, and an
|
|
285
|
+
* unbounded list would grow memory for the session and render one alert per entry.
|
|
286
|
+
*/
|
|
287
|
+
declare const MAX_ERRORS = 200;
|
|
288
|
+
/** Prepend an error to a list, newest first, capped at {@link MAX_ERRORS}. */
|
|
289
|
+
declare const prependError: (errors: readonly TimestampedError[], error: DXLinkError) => TimestampedError[];
|
|
290
|
+
|
|
291
|
+
interface ChannelWidgetProps {
|
|
292
|
+
icon: React.ReactNode;
|
|
293
|
+
title: string;
|
|
294
|
+
subtitle?: string;
|
|
295
|
+
/** Status slot (e.g. a Chip) shown before the collapse/close actions. */
|
|
296
|
+
status?: React.ReactNode;
|
|
297
|
+
/**
|
|
298
|
+
* Protocol channel id, shown next to the subtitle so a card can be correlated with
|
|
299
|
+
* a protocol log. Distinct from the card's own sequential number in `title`.
|
|
300
|
+
*/
|
|
301
|
+
channelId?: number | null;
|
|
302
|
+
/** Parameters the channel was opened with, listed as chips under the header. */
|
|
303
|
+
parameters?: Readonly<Record<string, unknown>> | null;
|
|
304
|
+
/**
|
|
305
|
+
* Errors scoped to this channel. Surfaced here rather than in the connection-level
|
|
306
|
+
* error center, so it stays obvious which channel failed.
|
|
307
|
+
*/
|
|
308
|
+
errors?: readonly TimestampedError[];
|
|
309
|
+
onClearErrors?: () => void;
|
|
310
|
+
/**
|
|
311
|
+
* Fired once when the user closes the channel. The card stays (closed state is
|
|
312
|
+
* owned here) — use this to release the channel's resources (e.g. close the
|
|
313
|
+
* underlying dxlink channel).
|
|
314
|
+
*/
|
|
315
|
+
onClose?: () => void;
|
|
316
|
+
defaultExpanded?: boolean;
|
|
317
|
+
children: React.ReactNode;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Generic collapsible channel card shell (draft / presentational). Every channel
|
|
321
|
+
* kind (feed, dom, indichart) renders its content inside this shell.
|
|
322
|
+
*
|
|
323
|
+
* Closing is a channel-view concern: it does NOT remove the card from the area —
|
|
324
|
+
* the widget owns its own closed state. Closing is terminal (the dxlink channel
|
|
325
|
+
* CLOSED state): the card stays as a header-only record marked "closed" — its
|
|
326
|
+
* content is no longer rendered — and it cannot be reopened.
|
|
327
|
+
*/
|
|
328
|
+
declare const ChannelWidget: ({ icon, title, subtitle, status, channelId, parameters, errors, onClearErrors, onClose, defaultExpanded, children, }: ChannelWidgetProps) => react.JSX.Element;
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Base contract for every ViewModel. A ViewModel owns its UI state in a vanilla
|
|
332
|
+
* Zustand store and exposes `dispose()` to release resources (listeners, sockets).
|
|
333
|
+
*
|
|
334
|
+
* `dispose()` MUST be idempotent: React 19 StrictMode mounts → unmounts → remounts
|
|
335
|
+
* components, so a VM may be disposed and re-created; double-dispose must be a no-op.
|
|
336
|
+
*/
|
|
337
|
+
interface ViewModel<State = unknown> {
|
|
338
|
+
readonly store: StoreApi<State>;
|
|
339
|
+
dispose(): void;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Bind a React component to a slice of a ViewModel's store. Re-renders only when
|
|
343
|
+
* the selected slice changes. This is the single binding primitive for the app —
|
|
344
|
+
* no manual `useEffect` listener plumbing in views.
|
|
345
|
+
*/
|
|
346
|
+
declare const useVM: <State, Slice>(vm: {
|
|
347
|
+
readonly store: StoreApi<State>;
|
|
348
|
+
}, selector: (state: State) => Slice) => Slice;
|
|
349
|
+
/**
|
|
350
|
+
* Own a ViewModel for the lifetime of the calling component.
|
|
351
|
+
*
|
|
352
|
+
* StrictMode-safe: the VM is constructed exactly once via lazy `useState` init
|
|
353
|
+
* (never `new VM()` inline in render), and `dispose()` runs on unmount. Combined
|
|
354
|
+
* with an idempotent `dispose()`, this prevents leaking resources across React's
|
|
355
|
+
* mount → unmount → remount double-invoke in development.
|
|
356
|
+
*/
|
|
357
|
+
declare const useOwnedViewModel: <VM extends {
|
|
358
|
+
dispose(): void;
|
|
359
|
+
}>(factory: () => VM) => VM;
|
|
360
|
+
/**
|
|
361
|
+
* Create a typed React context for sharing a ViewModel down a page subtree
|
|
362
|
+
* (avoids prop-drilling). The returned `useContextValue` hook throws if used
|
|
363
|
+
* outside its provider.
|
|
364
|
+
*/
|
|
365
|
+
declare const createViewModelContext: <VM>(displayName: string) => {
|
|
366
|
+
Provider: Provider<VM | null>;
|
|
367
|
+
Context: Context<VM | null>;
|
|
368
|
+
useContextValue: () => VM;
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
/** Connection parameters entered in the form and passed to the client config. */
|
|
372
|
+
interface ConnectionParams {
|
|
373
|
+
keepaliveInterval: number;
|
|
374
|
+
keepaliveTimeout: number;
|
|
375
|
+
acceptKeepaliveTimeout: number;
|
|
376
|
+
}
|
|
377
|
+
interface ConnectionVMState {
|
|
378
|
+
connection: DXLinkConnectionState;
|
|
379
|
+
/**
|
|
380
|
+
* Tri-state auth gating: `undefined` until we know (not connected / connecting),
|
|
381
|
+
* then the server-reported `DXLinkAuthState`. Never seeded to UNAUTHORIZED so a
|
|
382
|
+
* no-auth server doesn't flash a token form.
|
|
383
|
+
*/
|
|
384
|
+
auth: DXLinkAuthState | undefined;
|
|
385
|
+
details: DXLinkConnectionDetails | null;
|
|
386
|
+
errors: TimestampedError[];
|
|
387
|
+
/**
|
|
388
|
+
* Identifies the current client instance. Bumped on each fresh {@link connect}
|
|
389
|
+
* (new client) but NOT on {@link reconnect} (same client). The channels area uses
|
|
390
|
+
* it as a React remount key, so a brand-new connection starts with no channels
|
|
391
|
+
* while a reconnect preserves the open ones.
|
|
392
|
+
*/
|
|
393
|
+
sessionId: number;
|
|
394
|
+
/**
|
|
395
|
+
* `true` once the current client has reached AUTHORIZED. Stays `true` through a
|
|
396
|
+
* reconnect (the same client re-opens its channels itself), so the channels area
|
|
397
|
+
* survives the connection / auth flicker. Reset on disconnect / fresh connect.
|
|
398
|
+
*/
|
|
399
|
+
everAuthorized: boolean;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Page-scoped ViewModel wrapping a single {@link DXLinkWebSocketClient}. Owns the
|
|
403
|
+
* client as a private field (kept off the store), wires its listeners once, and
|
|
404
|
+
* maps connection / auth / error events into the store. Commands: connect,
|
|
405
|
+
* reconnect, disconnect, setAuthToken, clearErrors.
|
|
406
|
+
*
|
|
407
|
+
* Debug-console client opts are preserved from the legacy console: `logLevel:
|
|
408
|
+
* DEBUG` and `maxReconnectAttempts: 1` (a debug console deliberately limits
|
|
409
|
+
* reconnect).
|
|
410
|
+
*/
|
|
411
|
+
declare class ConnectionViewModel implements ViewModel<ConnectionVMState> {
|
|
412
|
+
readonly store: zustand.StoreApi<ConnectionVMState>;
|
|
413
|
+
private client;
|
|
414
|
+
connect: (url: string, params: ConnectionParams) => void;
|
|
415
|
+
reconnect: () => void;
|
|
416
|
+
disconnect: () => void;
|
|
417
|
+
setAuthToken: (token: string) => void;
|
|
418
|
+
/**
|
|
419
|
+
* The live client, for opening channels (e.g. building a FeedViewModel). Non-null
|
|
420
|
+
* only while connected — channel views are gated behind the AUTHORIZED state.
|
|
421
|
+
*/
|
|
422
|
+
getClient: () => DXLinkClient | null;
|
|
423
|
+
clearErrors: () => void;
|
|
424
|
+
/**
|
|
425
|
+
* Release the client + listeners. Idempotent and NON-permanent: after a dispose
|
|
426
|
+
* (e.g. React StrictMode's setup→cleanup→setup, which reuses the same VM via
|
|
427
|
+
* useState) the VM stays usable — the next `connect()` simply builds a fresh
|
|
428
|
+
* client. A real page unmount disposes it for good because nothing calls it again.
|
|
429
|
+
*/
|
|
430
|
+
dispose: () => void;
|
|
431
|
+
private handleConnectionState;
|
|
432
|
+
private handleAuthState;
|
|
433
|
+
private handleError;
|
|
434
|
+
/** Pull the current state straight off the client (e.g. right after connect()). */
|
|
435
|
+
private syncFromClient;
|
|
436
|
+
private teardownClient;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Provides the page-scoped {@link ConnectionViewModel} to the console subtree. */
|
|
440
|
+
declare const ConnectionProvider: react.Provider<ConnectionViewModel | null>;
|
|
441
|
+
/** Read the page's {@link ConnectionViewModel} (throws if used outside the provider). */
|
|
442
|
+
declare const useConnectionVM: () => ConnectionViewModel;
|
|
443
|
+
|
|
444
|
+
interface ErrorBoundaryProps {
|
|
445
|
+
children: ReactNode;
|
|
446
|
+
/** Heading for the fallback alert — name the part that failed. */
|
|
447
|
+
title?: string;
|
|
448
|
+
}
|
|
449
|
+
interface ErrorBoundaryState {
|
|
450
|
+
error: Error | null;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Contains a render-time failure to one subtree instead of blanking the app.
|
|
454
|
+
*
|
|
455
|
+
* Channel cards are the motivating case: they construct ViewModels (which throw if the
|
|
456
|
+
* connection vanished between opening the dialog and mounting) and they host a
|
|
457
|
+
* third-party chart component. Without a boundary either one takes down the whole
|
|
458
|
+
* console, losing every other open channel too.
|
|
459
|
+
*/
|
|
460
|
+
declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
461
|
+
state: ErrorBoundaryState;
|
|
462
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState;
|
|
463
|
+
componentDidCatch(error: Error, info: ErrorInfo): void;
|
|
464
|
+
render(): ReactNode;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* The slice of a channel ViewModel's state that describes its channel and what went
|
|
469
|
+
* wrong on it. Every channel VM's state extends this.
|
|
470
|
+
*
|
|
471
|
+
* `channelParameters` is optional: INDICHART's parameters carry the full source of every
|
|
472
|
+
* indicator, so that VM omits the field rather than retaining a third copy of them.
|
|
473
|
+
*/
|
|
474
|
+
interface ChannelErrorState {
|
|
475
|
+
/** Protocol channel id, for correlating with a protocol log. Null until opened. */
|
|
476
|
+
channelId: number | null;
|
|
477
|
+
/** Errors scoped to THIS channel — connection errors live on the connection VM. */
|
|
478
|
+
errors: TimestampedError[];
|
|
479
|
+
/** Parameters this channel was actually opened with. Null until opened. */
|
|
480
|
+
channelParameters?: Readonly<Record<string, unknown>> | null;
|
|
481
|
+
}
|
|
482
|
+
/** Initial values for the slice, for a VM's store initializer. */
|
|
483
|
+
declare const initialChannelErrorState: () => Required<ChannelErrorState>;
|
|
484
|
+
/**
|
|
485
|
+
* Tracks one channel's identity and errors on behalf of a ViewModel.
|
|
486
|
+
*
|
|
487
|
+
* Every channel VM needs the same three things — register an error listener, record the
|
|
488
|
+
* channel id and parameters on open, and expose a clear command. Keeping it here means
|
|
489
|
+
* one place to change, instead of four copies that drift.
|
|
490
|
+
*
|
|
491
|
+
* The `Partial<S>` casts are needed because TypeScript cannot prove a literal is a
|
|
492
|
+
* partial of the generic `S`; the constraint on `S` guarantees the keys exist.
|
|
493
|
+
*/
|
|
494
|
+
declare class ChannelErrorTracker<S extends ChannelErrorState> {
|
|
495
|
+
private readonly store;
|
|
496
|
+
constructor(store: StoreApi<S>);
|
|
497
|
+
/**
|
|
498
|
+
* Start collecting errors from a freshly opened channel and record its identity.
|
|
499
|
+
*
|
|
500
|
+
* `withParameters: false` skips recording the parameters, for services whose
|
|
501
|
+
* parameters are large and already available elsewhere (INDICHART).
|
|
502
|
+
*/
|
|
503
|
+
attach: (channel: DXLinkChannel, { withParameters }?: {
|
|
504
|
+
withParameters?: boolean | undefined;
|
|
505
|
+
}) => void;
|
|
506
|
+
/** Stop collecting. Errors already recorded are kept — they are a log. */
|
|
507
|
+
detach: (channel: DXLinkChannel) => void;
|
|
508
|
+
clear: () => void;
|
|
509
|
+
private handleError;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Provides the resolved {@link ConsoleConfig} to the console subtree.
|
|
514
|
+
*
|
|
515
|
+
* A context rather than props because the values are read in two places at different
|
|
516
|
+
* depths — the connection panel and the channels area — and threading a profile through
|
|
517
|
+
* every layer between would be noise. Same reasoning as the connection ViewModel context;
|
|
518
|
+
* this one carries plain data, so it is not built on the ViewModel helper.
|
|
519
|
+
*/
|
|
520
|
+
declare const ConsoleConfigProvider: react.Provider<ConsoleConfig | null>;
|
|
521
|
+
/** Read the console's configuration profile (throws if used outside the provider). */
|
|
522
|
+
declare const useConsoleConfig: () => ConsoleConfig;
|
|
523
|
+
|
|
524
|
+
export { type ChannelErrorState, ChannelErrorTracker, type ChannelPlugin, ChannelWidget, ChannelsArea, type ChannelsAreaProps, ConnectionProvider, ConnectionViewModel, type ConsoleConfig, type ConsoleConfigInput, type ConsoleConfigLock, ConsoleConfigProvider, type ConsoleConfigSources, ConsolePage, type ConsolePageProps, type DraftChannel, type ErasedChannelPlugin, ErrorBoundary, type KeepaliveConfig, MAX_ERRORS, type TimestampedError, type ViewModel, builtinConsoleConfig, createConsoleTheme, createViewModelContext, defineChannelPlugin, initialChannelErrorState, isConsoleConfigLock, prependError, resolveConsoleConfig, useConnectionVM, useConsoleConfig, useOwnedViewModel, useVM };
|