@invinite-org/chartlang-adapter-kit 1.1.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/CHANGELOG.md +1380 -0
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/base/bufferingAdapter.d.ts +52 -0
- package/dist/base/bufferingAdapter.d.ts.map +1 -0
- package/dist/base/bufferingAdapter.js +68 -0
- package/dist/base/bufferingAdapter.js.map +1 -0
- package/dist/base/index.d.ts +3 -0
- package/dist/base/index.d.ts.map +1 -0
- package/dist/base/index.js +5 -0
- package/dist/base/index.js.map +1 -0
- package/dist/base/passThroughAdapter.d.ts +49 -0
- package/dist/base/passThroughAdapter.d.ts.map +1 -0
- package/dist/base/passThroughAdapter.js +61 -0
- package/dist/base/passThroughAdapter.js.map +1 -0
- package/dist/capabilities/capabilities.d.ts +336 -0
- package/dist/capabilities/capabilities.d.ts.map +1 -0
- package/dist/capabilities/capabilities.js +616 -0
- package/dist/capabilities/capabilities.js.map +1 -0
- package/dist/capabilities/index.d.ts +2 -0
- package/dist/capabilities/index.d.ts.map +1 -0
- package/dist/capabilities/index.js +4 -0
- package/dist/capabilities/index.js.map +1 -0
- package/dist/defineAdapter.d.ts +74 -0
- package/dist/defineAdapter.d.ts.map +1 -0
- package/dist/defineAdapter.js +55 -0
- package/dist/defineAdapter.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/mocks/index.d.ts +3 -0
- package/dist/mocks/index.d.ts.map +1 -0
- package/dist/mocks/index.js +4 -0
- package/dist/mocks/index.js.map +1 -0
- package/dist/mocks/mockCandleSource.d.ts +68 -0
- package/dist/mocks/mockCandleSource.d.ts.map +1 -0
- package/dist/mocks/mockCandleSource.js +61 -0
- package/dist/mocks/mockCandleSource.js.map +1 -0
- package/dist/types.d.ts +655 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/validation/decodeDrawing.d.ts +29 -0
- package/dist/validation/decodeDrawing.d.ts.map +1 -0
- package/dist/validation/decodeDrawing.js +35 -0
- package/dist/validation/decodeDrawing.js.map +1 -0
- package/dist/validation/index.d.ts +4 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +5 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/validateEmission.d.ts +70 -0
- package/dist/validation/validateEmission.d.ts.map +1 -0
- package/dist/validation/validateEmission.js +1481 -0
- package/dist/validation/validateEmission.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,1481 @@
|
|
|
1
|
+
// Copyright (c) 2026 Invinite. Licensed under the MIT License.
|
|
2
|
+
// See the LICENSE file in the repo root for full license text.
|
|
3
|
+
import { DRAWING_KINDS } from "@invinite-org/chartlang-core";
|
|
4
|
+
const VALID_PLOT_STYLE_KINDS = new Set([
|
|
5
|
+
"line",
|
|
6
|
+
"step-line",
|
|
7
|
+
"horizontal-line",
|
|
8
|
+
"histogram",
|
|
9
|
+
"bars",
|
|
10
|
+
"area",
|
|
11
|
+
"filled-band",
|
|
12
|
+
"label",
|
|
13
|
+
"marker",
|
|
14
|
+
"shape",
|
|
15
|
+
"character",
|
|
16
|
+
"arrow",
|
|
17
|
+
"candle-override",
|
|
18
|
+
"bar-override",
|
|
19
|
+
"bg-color",
|
|
20
|
+
"bar-color",
|
|
21
|
+
"horizontal-histogram",
|
|
22
|
+
]);
|
|
23
|
+
const VALID_LINE_STYLES = new Set(["solid", "dashed", "dotted"]);
|
|
24
|
+
const VALID_MARKER_SHAPES = new Set([
|
|
25
|
+
"circle",
|
|
26
|
+
"triangle-up",
|
|
27
|
+
"triangle-down",
|
|
28
|
+
"square",
|
|
29
|
+
"diamond",
|
|
30
|
+
]);
|
|
31
|
+
const VALID_SHAPE_GLYPHS = new Set([
|
|
32
|
+
"circle",
|
|
33
|
+
"triangle-up",
|
|
34
|
+
"triangle-down",
|
|
35
|
+
"square",
|
|
36
|
+
"diamond",
|
|
37
|
+
"cross",
|
|
38
|
+
"xcross",
|
|
39
|
+
"flag",
|
|
40
|
+
]);
|
|
41
|
+
const VALID_PLOT_LOCATIONS = new Set(["above", "below", "absolute"]);
|
|
42
|
+
const VALID_ARROW_DIRECTIONS = new Set(["up", "down"]);
|
|
43
|
+
const VALID_LABEL_POSITIONS = new Set(["above", "below", "anchor"]);
|
|
44
|
+
const MAX_LABEL_LENGTH = 128;
|
|
45
|
+
const VALID_ALERT_SEVERITIES = new Set(["info", "warning", "critical"]);
|
|
46
|
+
const VALID_LOG_LEVELS = new Set(["info", "warn", "error"]);
|
|
47
|
+
const VALID_ALERT_CHANNELS = new Set([
|
|
48
|
+
"log",
|
|
49
|
+
"toast",
|
|
50
|
+
"webhook",
|
|
51
|
+
"email",
|
|
52
|
+
"sms",
|
|
53
|
+
"push",
|
|
54
|
+
]);
|
|
55
|
+
const VALID_DIAGNOSTIC_SEVERITIES = new Set(["info", "warning", "error"]);
|
|
56
|
+
const VALID_DRAWING_KINDS = new Set(DRAWING_KINDS);
|
|
57
|
+
const VALID_DRAWING_OPS = new Set(["create", "update", "remove"]);
|
|
58
|
+
const VALID_DIAGNOSTIC_CODES = new Set([
|
|
59
|
+
"unsupported-plot-kind",
|
|
60
|
+
"unsupported-drawing-kind",
|
|
61
|
+
"unsupported-alert-channel",
|
|
62
|
+
"unsupported-pane",
|
|
63
|
+
"unsupported-interval",
|
|
64
|
+
"multi-timeframe-not-supported",
|
|
65
|
+
"unknown-secondary-stream",
|
|
66
|
+
"lookback-exceeded",
|
|
67
|
+
"drawing-budget-exceeded",
|
|
68
|
+
"dropped-by-policy",
|
|
69
|
+
"input-coercion-failed",
|
|
70
|
+
"alert-conditions-not-supported",
|
|
71
|
+
"unknown-alert-condition",
|
|
72
|
+
"alert-rate-limited",
|
|
73
|
+
"runtime-cpu-budget-exceeded",
|
|
74
|
+
"runtime-memory-budget-exceeded",
|
|
75
|
+
"runtime-log-budget-exceeded",
|
|
76
|
+
"malformed-log-meta",
|
|
77
|
+
"runtime-error-thrown",
|
|
78
|
+
"session-info-missing",
|
|
79
|
+
"fixed-range-inverted",
|
|
80
|
+
"state-snapshot-restored",
|
|
81
|
+
"state-snapshot-future-dated",
|
|
82
|
+
"state-snapshot-malformed",
|
|
83
|
+
"state-snapshot-save-failed",
|
|
84
|
+
"malformed-emission",
|
|
85
|
+
]);
|
|
86
|
+
function bad(message, code = "malformed-emission") {
|
|
87
|
+
return { ok: false, code, message };
|
|
88
|
+
}
|
|
89
|
+
function isPlainObject(v) {
|
|
90
|
+
if (typeof v !== "object" || v === null)
|
|
91
|
+
return false;
|
|
92
|
+
const proto = Object.getPrototypeOf(v);
|
|
93
|
+
return proto === Object.prototype || proto === null;
|
|
94
|
+
}
|
|
95
|
+
function isFiniteNumber(v) {
|
|
96
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
97
|
+
}
|
|
98
|
+
function isNonNegativeInteger(v) {
|
|
99
|
+
return typeof v === "number" && Number.isInteger(v) && v >= 0;
|
|
100
|
+
}
|
|
101
|
+
function isNonEmptyString(v) {
|
|
102
|
+
return typeof v === "string" && v.length > 0;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Walk a `meta` payload and reject any non-JSON-friendly element. Per
|
|
106
|
+
* PLAN §7.3 universal payload rules: forbid `Map`, `Set`, `Date`,
|
|
107
|
+
* `RegExp`, `bigint`, `Function`, `Symbol`, `undefined`, class
|
|
108
|
+
* instances, and non-finite numbers anywhere in the tree.
|
|
109
|
+
*/
|
|
110
|
+
function walkMeta(v, path) {
|
|
111
|
+
if (v === null)
|
|
112
|
+
return { ok: true };
|
|
113
|
+
const t = typeof v;
|
|
114
|
+
if (t === "boolean" || t === "string")
|
|
115
|
+
return { ok: true };
|
|
116
|
+
if (t === "number") {
|
|
117
|
+
if (!Number.isFinite(v)) {
|
|
118
|
+
return bad(`${path}: non-finite number`);
|
|
119
|
+
}
|
|
120
|
+
return { ok: true };
|
|
121
|
+
}
|
|
122
|
+
if (t === "undefined")
|
|
123
|
+
return bad(`${path}: undefined values are forbidden`);
|
|
124
|
+
if (t === "bigint")
|
|
125
|
+
return bad(`${path}: bigint is forbidden`);
|
|
126
|
+
if (t === "function")
|
|
127
|
+
return bad(`${path}: function is forbidden`);
|
|
128
|
+
if (t === "symbol")
|
|
129
|
+
return bad(`${path}: symbol is forbidden`);
|
|
130
|
+
// typeof === "object" from here.
|
|
131
|
+
if (Array.isArray(v)) {
|
|
132
|
+
for (let i = 0; i < v.length; i++) {
|
|
133
|
+
const r = walkMeta(v[i], `${path}[${i}]`);
|
|
134
|
+
if (!r.ok)
|
|
135
|
+
return r;
|
|
136
|
+
}
|
|
137
|
+
return { ok: true };
|
|
138
|
+
}
|
|
139
|
+
if (!isPlainObject(v)) {
|
|
140
|
+
return bad(`${path}: only plain objects are allowed`);
|
|
141
|
+
}
|
|
142
|
+
for (const key of Object.keys(v)) {
|
|
143
|
+
// Use `Reflect.get` and a try/catch so a throwing-getter on the
|
|
144
|
+
// meta payload (constructed via `Object.defineProperty(..., {
|
|
145
|
+
// get() { throw … }})` either deliberately or by accident at a
|
|
146
|
+
// serialisation boundary) converts to a `malformed-emission`
|
|
147
|
+
// diagnostic instead of crashing the host.
|
|
148
|
+
let child;
|
|
149
|
+
try {
|
|
150
|
+
child = Reflect.get(v, key);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return bad(`${path}.${key}: getter threw during traversal`);
|
|
154
|
+
}
|
|
155
|
+
const r = walkMeta(child, `${path}.${key}`);
|
|
156
|
+
if (!r.ok)
|
|
157
|
+
return r;
|
|
158
|
+
}
|
|
159
|
+
return { ok: true };
|
|
160
|
+
}
|
|
161
|
+
function validateLineLikeStyle(style) {
|
|
162
|
+
const lineWidth = style.lineWidth;
|
|
163
|
+
if (!isFiniteNumber(lineWidth) || lineWidth <= 0) {
|
|
164
|
+
return bad("style.lineWidth: must be a finite positive number");
|
|
165
|
+
}
|
|
166
|
+
const lineStyle = style.lineStyle;
|
|
167
|
+
if (typeof lineStyle !== "string" || !VALID_LINE_STYLES.has(lineStyle)) {
|
|
168
|
+
return bad(`style.lineStyle: '${String(lineStyle)}' is not a valid line style`);
|
|
169
|
+
}
|
|
170
|
+
return { ok: true };
|
|
171
|
+
}
|
|
172
|
+
function validateAreaStyle(style) {
|
|
173
|
+
const lineCheck = validateLineLikeStyle(style);
|
|
174
|
+
if (!lineCheck.ok)
|
|
175
|
+
return lineCheck;
|
|
176
|
+
const fillAlpha = style.fillAlpha;
|
|
177
|
+
if (!isFiniteNumber(fillAlpha) || fillAlpha < 0 || fillAlpha > 1) {
|
|
178
|
+
return bad("style.fillAlpha: must be a finite number in [0, 1]");
|
|
179
|
+
}
|
|
180
|
+
return { ok: true };
|
|
181
|
+
}
|
|
182
|
+
function validateHistogramOrBarsStyle(style) {
|
|
183
|
+
const baseline = style.baseline;
|
|
184
|
+
if (!isFiniteNumber(baseline)) {
|
|
185
|
+
return bad("style.baseline: must be a finite number");
|
|
186
|
+
}
|
|
187
|
+
return { ok: true };
|
|
188
|
+
}
|
|
189
|
+
function validateFilledBandStyle(style) {
|
|
190
|
+
const upper = style.upper;
|
|
191
|
+
if (upper !== null && !isFiniteNumber(upper)) {
|
|
192
|
+
return bad("style.upper: must be a finite number or null");
|
|
193
|
+
}
|
|
194
|
+
const lower = style.lower;
|
|
195
|
+
if (lower !== null && !isFiniteNumber(lower)) {
|
|
196
|
+
return bad("style.lower: must be a finite number or null");
|
|
197
|
+
}
|
|
198
|
+
if (upper === null && lower === null) {
|
|
199
|
+
return bad("style.upper / style.lower: at least one bound must be non-null");
|
|
200
|
+
}
|
|
201
|
+
const alpha = style.alpha;
|
|
202
|
+
if (!isFiniteNumber(alpha) || alpha < 0 || alpha > 1) {
|
|
203
|
+
return bad("style.alpha: must be a finite number in [0, 1]");
|
|
204
|
+
}
|
|
205
|
+
return { ok: true };
|
|
206
|
+
}
|
|
207
|
+
function validateLabelStyle(style) {
|
|
208
|
+
const text = style.text;
|
|
209
|
+
if (typeof text !== "string" || text.length === 0) {
|
|
210
|
+
return bad("style.text: must be a non-empty string");
|
|
211
|
+
}
|
|
212
|
+
if (text.length > MAX_LABEL_LENGTH) {
|
|
213
|
+
return bad(`style.text: must be at most ${MAX_LABEL_LENGTH} characters`);
|
|
214
|
+
}
|
|
215
|
+
const position = style.position;
|
|
216
|
+
if (typeof position !== "string" || !VALID_LABEL_POSITIONS.has(position)) {
|
|
217
|
+
return bad(`style.position: '${String(position)}' is not a valid label position`);
|
|
218
|
+
}
|
|
219
|
+
return { ok: true };
|
|
220
|
+
}
|
|
221
|
+
function validateMarkerStyle(style) {
|
|
222
|
+
const shape = style.shape;
|
|
223
|
+
if (typeof shape !== "string" || !VALID_MARKER_SHAPES.has(shape)) {
|
|
224
|
+
return bad(`style.shape: '${String(shape)}' is not a valid marker shape`);
|
|
225
|
+
}
|
|
226
|
+
const size = style.size;
|
|
227
|
+
if (!isFiniteNumber(size) || size <= 0) {
|
|
228
|
+
return bad("style.size: must be a finite positive number");
|
|
229
|
+
}
|
|
230
|
+
return { ok: true };
|
|
231
|
+
}
|
|
232
|
+
function validateOptionalLocation(style) {
|
|
233
|
+
const location = style.location;
|
|
234
|
+
if (location !== undefined &&
|
|
235
|
+
(typeof location !== "string" || !VALID_PLOT_LOCATIONS.has(location))) {
|
|
236
|
+
return bad(`style.location: '${String(location)}' is not a valid plot location`);
|
|
237
|
+
}
|
|
238
|
+
return { ok: true };
|
|
239
|
+
}
|
|
240
|
+
function validatePlotShapeStyle(style) {
|
|
241
|
+
const shape = style.shape;
|
|
242
|
+
if (typeof shape !== "string" || !VALID_SHAPE_GLYPHS.has(shape)) {
|
|
243
|
+
return bad(`style.shape: '${String(shape)}' is not a valid shape glyph`);
|
|
244
|
+
}
|
|
245
|
+
const size = style.size;
|
|
246
|
+
if (!isFiniteNumber(size) || size <= 0) {
|
|
247
|
+
return bad("style.size: must be a finite positive number");
|
|
248
|
+
}
|
|
249
|
+
return validateOptionalLocation(style);
|
|
250
|
+
}
|
|
251
|
+
function validateCharacterStyle(style) {
|
|
252
|
+
const char = style.char;
|
|
253
|
+
if (typeof char !== "string" || char.length === 0) {
|
|
254
|
+
return bad("style.char: must be a non-empty string");
|
|
255
|
+
}
|
|
256
|
+
const size = style.size;
|
|
257
|
+
if (!isFiniteNumber(size) || size <= 0) {
|
|
258
|
+
return bad("style.size: must be a finite positive number");
|
|
259
|
+
}
|
|
260
|
+
return validateOptionalLocation(style);
|
|
261
|
+
}
|
|
262
|
+
function validateArrowStyle(style) {
|
|
263
|
+
const direction = style.direction;
|
|
264
|
+
if (typeof direction !== "string" || !VALID_ARROW_DIRECTIONS.has(direction)) {
|
|
265
|
+
return bad(`style.direction: '${String(direction)}' is not a valid arrow direction`);
|
|
266
|
+
}
|
|
267
|
+
const size = style.size;
|
|
268
|
+
if (!isFiniteNumber(size) || size <= 0) {
|
|
269
|
+
return bad("style.size: must be a finite positive number");
|
|
270
|
+
}
|
|
271
|
+
return { ok: true };
|
|
272
|
+
}
|
|
273
|
+
function validateColor(value, path) {
|
|
274
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
275
|
+
return bad(`${path}: must be a non-empty string`);
|
|
276
|
+
}
|
|
277
|
+
return { ok: true };
|
|
278
|
+
}
|
|
279
|
+
function validateCandleOverrideStyle(style) {
|
|
280
|
+
const bull = validateColor(style.bull, "style.bull");
|
|
281
|
+
if (!bull.ok)
|
|
282
|
+
return bull;
|
|
283
|
+
const bear = validateColor(style.bear, "style.bear");
|
|
284
|
+
if (!bear.ok)
|
|
285
|
+
return bear;
|
|
286
|
+
if (style.doji !== undefined) {
|
|
287
|
+
return validateColor(style.doji, "style.doji");
|
|
288
|
+
}
|
|
289
|
+
return { ok: true };
|
|
290
|
+
}
|
|
291
|
+
function validateSingleColorStyle(style, path) {
|
|
292
|
+
return validateColor(style.color, path);
|
|
293
|
+
}
|
|
294
|
+
function validateBgColorStyle(style) {
|
|
295
|
+
const color = validateSingleColorStyle(style, "style.color");
|
|
296
|
+
if (!color.ok)
|
|
297
|
+
return color;
|
|
298
|
+
const transp = style.transp;
|
|
299
|
+
if (transp !== undefined && (!isFiniteNumber(transp) || transp < 0 || transp > 100)) {
|
|
300
|
+
return bad("style.transp: must be a finite number in [0, 100]");
|
|
301
|
+
}
|
|
302
|
+
return { ok: true };
|
|
303
|
+
}
|
|
304
|
+
function validateHorizontalHistogramStyle(style) {
|
|
305
|
+
const buckets = style.buckets;
|
|
306
|
+
if (!Array.isArray(buckets)) {
|
|
307
|
+
return bad("style.buckets: must be an array");
|
|
308
|
+
}
|
|
309
|
+
for (let i = 0; i < buckets.length; i++) {
|
|
310
|
+
const bucket = buckets[i];
|
|
311
|
+
if (!isPlainObject(bucket)) {
|
|
312
|
+
return bad(`style.buckets[${i}]: must be an object`);
|
|
313
|
+
}
|
|
314
|
+
if (!isFiniteNumber(bucket.price)) {
|
|
315
|
+
return bad(`style.buckets[${i}].price: must be a finite number`);
|
|
316
|
+
}
|
|
317
|
+
if (!isFiniteNumber(bucket.volume) || bucket.volume < 0) {
|
|
318
|
+
return bad(`style.buckets[${i}].volume: must be a finite non-negative number`);
|
|
319
|
+
}
|
|
320
|
+
if (bucket.color !== undefined) {
|
|
321
|
+
const color = validateColor(bucket.color, `style.buckets[${i}].color`);
|
|
322
|
+
if (!color.ok)
|
|
323
|
+
return color;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return { ok: true };
|
|
327
|
+
}
|
|
328
|
+
function validatePlotStyle(style) {
|
|
329
|
+
if (!isPlainObject(style))
|
|
330
|
+
return bad("style: not an object");
|
|
331
|
+
const kind = style.kind;
|
|
332
|
+
if (typeof kind !== "string" || !VALID_PLOT_STYLE_KINDS.has(kind)) {
|
|
333
|
+
return bad(`style.kind: '${String(kind)}' is not a known plot kind`);
|
|
334
|
+
}
|
|
335
|
+
switch (kind) {
|
|
336
|
+
case "line":
|
|
337
|
+
case "step-line":
|
|
338
|
+
case "horizontal-line":
|
|
339
|
+
return validateLineLikeStyle(style);
|
|
340
|
+
case "histogram":
|
|
341
|
+
case "bars":
|
|
342
|
+
return validateHistogramOrBarsStyle(style);
|
|
343
|
+
case "area":
|
|
344
|
+
return validateAreaStyle(style);
|
|
345
|
+
case "filled-band":
|
|
346
|
+
return validateFilledBandStyle(style);
|
|
347
|
+
case "label":
|
|
348
|
+
return validateLabelStyle(style);
|
|
349
|
+
case "marker":
|
|
350
|
+
return validateMarkerStyle(style);
|
|
351
|
+
case "shape":
|
|
352
|
+
return validatePlotShapeStyle(style);
|
|
353
|
+
case "character":
|
|
354
|
+
return validateCharacterStyle(style);
|
|
355
|
+
case "arrow":
|
|
356
|
+
return validateArrowStyle(style);
|
|
357
|
+
case "candle-override":
|
|
358
|
+
return validateCandleOverrideStyle(style);
|
|
359
|
+
case "bar-override":
|
|
360
|
+
case "bar-color":
|
|
361
|
+
return validateSingleColorStyle(style, "style.color");
|
|
362
|
+
case "bg-color":
|
|
363
|
+
return validateBgColorStyle(style);
|
|
364
|
+
case "horizontal-histogram":
|
|
365
|
+
return validateHorizontalHistogramStyle(style);
|
|
366
|
+
/* v8 ignore next 2 -- kind is already gated by VALID_PLOT_STYLE_KINDS */
|
|
367
|
+
default:
|
|
368
|
+
return bad(`style.kind: '${kind}' has no validator`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function validatePlotEmission(e) {
|
|
372
|
+
if (!isNonEmptyString(e.slotId))
|
|
373
|
+
return bad("plot.slotId: must be a non-empty string");
|
|
374
|
+
if (typeof e.title !== "string")
|
|
375
|
+
return bad("plot.title: must be a string");
|
|
376
|
+
const styleResult = validatePlotStyle(e.style);
|
|
377
|
+
if (!styleResult.ok)
|
|
378
|
+
return styleResult;
|
|
379
|
+
if (!isNonNegativeInteger(e.bar)) {
|
|
380
|
+
return bad("plot.bar: must be a non-negative integer");
|
|
381
|
+
}
|
|
382
|
+
if (!isFiniteNumber(e.time))
|
|
383
|
+
return bad("plot.time: must be a finite number");
|
|
384
|
+
const value = e.value;
|
|
385
|
+
if (value !== null && !isFiniteNumber(value)) {
|
|
386
|
+
return bad("plot.value: must be a finite number or null");
|
|
387
|
+
}
|
|
388
|
+
const color = e.color;
|
|
389
|
+
if (color !== null && typeof color !== "string") {
|
|
390
|
+
return bad("plot.color: must be a string or null");
|
|
391
|
+
}
|
|
392
|
+
if (!isPlainObject(e.meta))
|
|
393
|
+
return bad("plot.meta: must be a plain object");
|
|
394
|
+
const metaResult = walkMeta(e.meta, "plot.meta");
|
|
395
|
+
if (!metaResult.ok)
|
|
396
|
+
return metaResult;
|
|
397
|
+
if (typeof e.pane !== "string")
|
|
398
|
+
return bad("plot.pane: must be a string");
|
|
399
|
+
return { ok: true };
|
|
400
|
+
}
|
|
401
|
+
function validateAlertEmission(e) {
|
|
402
|
+
if (!isNonEmptyString(e.slotId))
|
|
403
|
+
return bad("alert.slotId: must be a non-empty string");
|
|
404
|
+
const severity = e.severity;
|
|
405
|
+
if (typeof severity !== "string" || !VALID_ALERT_SEVERITIES.has(severity)) {
|
|
406
|
+
return bad(`alert.severity: '${String(severity)}' is not a valid severity`);
|
|
407
|
+
}
|
|
408
|
+
if (!isNonEmptyString(e.message)) {
|
|
409
|
+
return bad("alert.message: must be a non-empty string");
|
|
410
|
+
}
|
|
411
|
+
if (!isNonNegativeInteger(e.bar)) {
|
|
412
|
+
return bad("alert.bar: must be a non-negative integer");
|
|
413
|
+
}
|
|
414
|
+
if (!isFiniteNumber(e.time))
|
|
415
|
+
return bad("alert.time: must be a finite number");
|
|
416
|
+
if (!isPlainObject(e.meta))
|
|
417
|
+
return bad("alert.meta: must be a plain object");
|
|
418
|
+
const metaResult = walkMeta(e.meta, "alert.meta");
|
|
419
|
+
if (!metaResult.ok)
|
|
420
|
+
return metaResult;
|
|
421
|
+
const channels = e.channels;
|
|
422
|
+
if (!Array.isArray(channels))
|
|
423
|
+
return bad("alert.channels: must be an array");
|
|
424
|
+
for (let i = 0; i < channels.length; i++) {
|
|
425
|
+
const c = channels[i];
|
|
426
|
+
if (typeof c !== "string" || !VALID_ALERT_CHANNELS.has(c)) {
|
|
427
|
+
return bad(`alert.channels[${i}]: '${String(c)}' is not a valid alert channel`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (!isNonEmptyString(e.dedupeKey)) {
|
|
431
|
+
return bad("alert.dedupeKey: must be a non-empty string");
|
|
432
|
+
}
|
|
433
|
+
return { ok: true };
|
|
434
|
+
}
|
|
435
|
+
function validateAlertConditionEmission(e) {
|
|
436
|
+
if (!isNonEmptyString(e.conditionId)) {
|
|
437
|
+
return bad("alert-condition.conditionId: must be a non-empty string");
|
|
438
|
+
}
|
|
439
|
+
if (typeof e.title !== "string") {
|
|
440
|
+
return bad("alert-condition.title: must be a string");
|
|
441
|
+
}
|
|
442
|
+
if (typeof e.description !== "string") {
|
|
443
|
+
return bad("alert-condition.description: must be a string");
|
|
444
|
+
}
|
|
445
|
+
if (typeof e.defaultMessage !== "string") {
|
|
446
|
+
return bad("alert-condition.defaultMessage: must be a string");
|
|
447
|
+
}
|
|
448
|
+
if (typeof e.fired !== "boolean") {
|
|
449
|
+
return bad("alert-condition.fired: must be a boolean");
|
|
450
|
+
}
|
|
451
|
+
if (!isNonNegativeInteger(e.bar)) {
|
|
452
|
+
return bad("alert-condition.bar: must be a non-negative integer");
|
|
453
|
+
}
|
|
454
|
+
if (!isFiniteNumber(e.time)) {
|
|
455
|
+
return bad("alert-condition.time: must be a finite number");
|
|
456
|
+
}
|
|
457
|
+
return { ok: true };
|
|
458
|
+
}
|
|
459
|
+
function validateLogEmission(e) {
|
|
460
|
+
const level = e.level;
|
|
461
|
+
if (typeof level !== "string" || !VALID_LOG_LEVELS.has(level)) {
|
|
462
|
+
return bad(`log.level: '${String(level)}' is not a valid log level`);
|
|
463
|
+
}
|
|
464
|
+
if (!isNonEmptyString(e.message)) {
|
|
465
|
+
return bad("log.message: must be a non-empty string");
|
|
466
|
+
}
|
|
467
|
+
const meta = e.meta;
|
|
468
|
+
if (meta !== undefined) {
|
|
469
|
+
if (!isPlainObject(meta))
|
|
470
|
+
return bad("log.meta: must be a plain object");
|
|
471
|
+
const metaResult = walkMeta(meta, "log.meta");
|
|
472
|
+
if (!metaResult.ok)
|
|
473
|
+
return metaResult;
|
|
474
|
+
}
|
|
475
|
+
if (!isNonNegativeInteger(e.bar)) {
|
|
476
|
+
return bad("log.bar: must be a non-negative integer");
|
|
477
|
+
}
|
|
478
|
+
if (!isFiniteNumber(e.time)) {
|
|
479
|
+
return bad("log.time: must be a finite number");
|
|
480
|
+
}
|
|
481
|
+
return { ok: true };
|
|
482
|
+
}
|
|
483
|
+
function isWorldPoint(v) {
|
|
484
|
+
if (!isPlainObject(v))
|
|
485
|
+
return false;
|
|
486
|
+
return isFiniteNumber(v.time) && isFiniteNumber(v.price);
|
|
487
|
+
}
|
|
488
|
+
function validateAnchorFixed(v, path, count) {
|
|
489
|
+
if (!Array.isArray(v) || v.length !== count) {
|
|
490
|
+
return bad(`${path}: must be a ${count}-element WorldPoint tuple`);
|
|
491
|
+
}
|
|
492
|
+
for (let i = 0; i < count; i++) {
|
|
493
|
+
if (!isWorldPoint(v[i])) {
|
|
494
|
+
return bad(`${path}[${i}]: not a WorldPoint (need finite time + price)`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return { ok: true };
|
|
498
|
+
}
|
|
499
|
+
function validateAnchorPair(v, path) {
|
|
500
|
+
return validateAnchorFixed(v, path, 2);
|
|
501
|
+
}
|
|
502
|
+
function validateAnchorTriple(v, path) {
|
|
503
|
+
return validateAnchorFixed(v, path, 3);
|
|
504
|
+
}
|
|
505
|
+
function validateAnchorQuad(v, path) {
|
|
506
|
+
return validateAnchorFixed(v, path, 4);
|
|
507
|
+
}
|
|
508
|
+
function validateAnchorQuint(v, path) {
|
|
509
|
+
return validateAnchorFixed(v, path, 5);
|
|
510
|
+
}
|
|
511
|
+
function validateAnchorHept(v, path) {
|
|
512
|
+
return validateAnchorFixed(v, path, 7);
|
|
513
|
+
}
|
|
514
|
+
function validateOptionalLabels(v, path, expectedCount) {
|
|
515
|
+
if (v === undefined)
|
|
516
|
+
return { ok: true };
|
|
517
|
+
if (!Array.isArray(v) || v.length !== expectedCount) {
|
|
518
|
+
return bad(`${path}: must be an array of ${expectedCount} strings`);
|
|
519
|
+
}
|
|
520
|
+
for (let i = 0; i < expectedCount; i++) {
|
|
521
|
+
if (typeof v[i] !== "string") {
|
|
522
|
+
return bad(`${path}[${i}]: must be a string`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return { ok: true };
|
|
526
|
+
}
|
|
527
|
+
function validateAnchorVariable(v, path, min, max) {
|
|
528
|
+
if (!Array.isArray(v) || v.length < min || v.length > max) {
|
|
529
|
+
return bad(`${path}: must be an array of ${min}..${max} WorldPoints`);
|
|
530
|
+
}
|
|
531
|
+
for (let i = 0; i < v.length; i++) {
|
|
532
|
+
if (!isWorldPoint(v[i])) {
|
|
533
|
+
return bad(`${path}[${i}]: not a WorldPoint (need finite time + price)`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return { ok: true };
|
|
537
|
+
}
|
|
538
|
+
function validateLineDrawStyle(s, path) {
|
|
539
|
+
if (!isPlainObject(s))
|
|
540
|
+
return bad(`${path}: must be a plain object`);
|
|
541
|
+
if (s.color !== undefined && typeof s.color !== "string") {
|
|
542
|
+
return bad(`${path}.color: must be a string`);
|
|
543
|
+
}
|
|
544
|
+
if (s.lineWidth !== undefined && (!isFiniteNumber(s.lineWidth) || s.lineWidth <= 0)) {
|
|
545
|
+
return bad(`${path}.lineWidth: must be a finite positive number`);
|
|
546
|
+
}
|
|
547
|
+
if (s.lineStyle !== undefined && !VALID_LINE_STYLES.has(String(s.lineStyle))) {
|
|
548
|
+
return bad(`${path}.lineStyle: '${String(s.lineStyle)}' is not a valid line style`);
|
|
549
|
+
}
|
|
550
|
+
if (s.extendLeft !== undefined && typeof s.extendLeft !== "boolean") {
|
|
551
|
+
return bad(`${path}.extendLeft: must be a boolean`);
|
|
552
|
+
}
|
|
553
|
+
if (s.extendRight !== undefined && typeof s.extendRight !== "boolean") {
|
|
554
|
+
return bad(`${path}.extendRight: must be a boolean`);
|
|
555
|
+
}
|
|
556
|
+
return { ok: true };
|
|
557
|
+
}
|
|
558
|
+
function validateShapeStyle(s, path) {
|
|
559
|
+
if (!isPlainObject(s))
|
|
560
|
+
return bad(`${path}: must be a plain object`);
|
|
561
|
+
if (s.stroke !== undefined && typeof s.stroke !== "string") {
|
|
562
|
+
return bad(`${path}.stroke: must be a string`);
|
|
563
|
+
}
|
|
564
|
+
if (s.fill !== undefined && typeof s.fill !== "string") {
|
|
565
|
+
return bad(`${path}.fill: must be a string`);
|
|
566
|
+
}
|
|
567
|
+
if (s.lineWidth !== undefined && (!isFiniteNumber(s.lineWidth) || s.lineWidth <= 0)) {
|
|
568
|
+
return bad(`${path}.lineWidth: must be a finite positive number`);
|
|
569
|
+
}
|
|
570
|
+
if (s.lineStyle !== undefined && !VALID_LINE_STYLES.has(String(s.lineStyle))) {
|
|
571
|
+
return bad(`${path}.lineStyle: '${String(s.lineStyle)}' is not a valid line style`);
|
|
572
|
+
}
|
|
573
|
+
if (s.fillAlpha !== undefined &&
|
|
574
|
+
(!isFiniteNumber(s.fillAlpha) || s.fillAlpha < 0 || s.fillAlpha > 1)) {
|
|
575
|
+
return bad(`${path}.fillAlpha: must be a finite number in [0, 1]`);
|
|
576
|
+
}
|
|
577
|
+
return { ok: true };
|
|
578
|
+
}
|
|
579
|
+
function validateDrawingMeta(state) {
|
|
580
|
+
if (state.name !== undefined && typeof state.name !== "string") {
|
|
581
|
+
return bad("drawing.state.name: must be a string");
|
|
582
|
+
}
|
|
583
|
+
if (state.visible !== undefined && typeof state.visible !== "boolean") {
|
|
584
|
+
return bad("drawing.state.visible: must be a boolean");
|
|
585
|
+
}
|
|
586
|
+
return { ok: true };
|
|
587
|
+
}
|
|
588
|
+
function validateLineState(state) {
|
|
589
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
590
|
+
if (!anchorsCheck.ok)
|
|
591
|
+
return anchorsCheck;
|
|
592
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
593
|
+
}
|
|
594
|
+
function validateHorizontalLineState(state) {
|
|
595
|
+
if (!isFiniteNumber(state.price))
|
|
596
|
+
return bad("drawing.state.price: must be a finite number");
|
|
597
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
598
|
+
}
|
|
599
|
+
function validateHorizontalRayState(state) {
|
|
600
|
+
if (!isWorldPoint(state.anchor))
|
|
601
|
+
return bad("drawing.state.anchor: not a WorldPoint");
|
|
602
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
603
|
+
}
|
|
604
|
+
function validateVerticalLineState(state) {
|
|
605
|
+
if (!isFiniteNumber(state.time))
|
|
606
|
+
return bad("drawing.state.time: must be a finite number");
|
|
607
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
608
|
+
}
|
|
609
|
+
function validateCrossLineState(state) {
|
|
610
|
+
if (!isWorldPoint(state.anchor))
|
|
611
|
+
return bad("drawing.state.anchor: not a WorldPoint");
|
|
612
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
613
|
+
}
|
|
614
|
+
function validateTrendAngleState(state) {
|
|
615
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
616
|
+
if (!anchorsCheck.ok)
|
|
617
|
+
return anchorsCheck;
|
|
618
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
619
|
+
}
|
|
620
|
+
function validateRectangleState(state) {
|
|
621
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
622
|
+
if (!anchorsCheck.ok)
|
|
623
|
+
return anchorsCheck;
|
|
624
|
+
return validateShapeStyle(state.style, "drawing.state.style");
|
|
625
|
+
}
|
|
626
|
+
function validateRotatedRectangleState(state) {
|
|
627
|
+
const anchorsCheck = validateAnchorQuad(state.anchors, "drawing.state.anchors");
|
|
628
|
+
if (!anchorsCheck.ok)
|
|
629
|
+
return anchorsCheck;
|
|
630
|
+
return validateShapeStyle(state.style, "drawing.state.style");
|
|
631
|
+
}
|
|
632
|
+
function validateTriangleState(state) {
|
|
633
|
+
const anchorsCheck = validateAnchorTriple(state.anchors, "drawing.state.anchors");
|
|
634
|
+
if (!anchorsCheck.ok)
|
|
635
|
+
return anchorsCheck;
|
|
636
|
+
return validateShapeStyle(state.style, "drawing.state.style");
|
|
637
|
+
}
|
|
638
|
+
const POLYLINE_MIN_ANCHORS = 3;
|
|
639
|
+
const POLYLINE_MAX_ANCHORS = 20;
|
|
640
|
+
function validatePolylineState(state) {
|
|
641
|
+
const anchorsCheck = validateAnchorVariable(state.anchors, "drawing.state.anchors", POLYLINE_MIN_ANCHORS, POLYLINE_MAX_ANCHORS);
|
|
642
|
+
if (!anchorsCheck.ok)
|
|
643
|
+
return anchorsCheck;
|
|
644
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
645
|
+
}
|
|
646
|
+
function validateCircleState(state) {
|
|
647
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
648
|
+
if (!anchorsCheck.ok)
|
|
649
|
+
return anchorsCheck;
|
|
650
|
+
return validateShapeStyle(state.style, "drawing.state.style");
|
|
651
|
+
}
|
|
652
|
+
function validateEllipseState(state) {
|
|
653
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
654
|
+
if (!anchorsCheck.ok)
|
|
655
|
+
return anchorsCheck;
|
|
656
|
+
return validateShapeStyle(state.style, "drawing.state.style");
|
|
657
|
+
}
|
|
658
|
+
function validatePathOpts(s, path) {
|
|
659
|
+
const lineCheck = validateLineDrawStyle(s, path);
|
|
660
|
+
if (!lineCheck.ok)
|
|
661
|
+
return lineCheck;
|
|
662
|
+
// validateLineDrawStyle proved `s` is a plain object.
|
|
663
|
+
const obj = s;
|
|
664
|
+
if (obj.closed !== undefined && typeof obj.closed !== "boolean") {
|
|
665
|
+
return bad(`${path}.closed: must be a boolean`);
|
|
666
|
+
}
|
|
667
|
+
return { ok: true };
|
|
668
|
+
}
|
|
669
|
+
const PATH_MIN_ANCHORS = 2;
|
|
670
|
+
const PATH_MAX_ANCHORS = 20;
|
|
671
|
+
function validatePathState(state) {
|
|
672
|
+
const anchorsCheck = validateAnchorVariable(state.anchors, "drawing.state.anchors", PATH_MIN_ANCHORS, PATH_MAX_ANCHORS);
|
|
673
|
+
if (!anchorsCheck.ok)
|
|
674
|
+
return anchorsCheck;
|
|
675
|
+
return validatePathOpts(state.style, "drawing.state.style");
|
|
676
|
+
}
|
|
677
|
+
const VALID_TEXT_SIZES = new Set(["tiny", "small", "normal", "large", "huge"]);
|
|
678
|
+
const VALID_TEXT_HALIGN = new Set(["left", "center", "right"]);
|
|
679
|
+
const VALID_TEXT_VALIGN = new Set(["top", "middle", "bottom"]);
|
|
680
|
+
const VALID_TABLE_POSITIONS = new Set([
|
|
681
|
+
"top-left",
|
|
682
|
+
"top-center",
|
|
683
|
+
"top-right",
|
|
684
|
+
"middle-left",
|
|
685
|
+
"middle-center",
|
|
686
|
+
"middle-right",
|
|
687
|
+
"bottom-left",
|
|
688
|
+
"bottom-center",
|
|
689
|
+
"bottom-right",
|
|
690
|
+
]);
|
|
691
|
+
function validateTextOpts(s, path) {
|
|
692
|
+
if (!isPlainObject(s))
|
|
693
|
+
return bad(`${path}: must be a plain object`);
|
|
694
|
+
if (s.color !== undefined && typeof s.color !== "string") {
|
|
695
|
+
return bad(`${path}.color: must be a string`);
|
|
696
|
+
}
|
|
697
|
+
if (s.size !== undefined && !VALID_TEXT_SIZES.has(String(s.size))) {
|
|
698
|
+
return bad(`${path}.size: '${String(s.size)}' is not a valid text size`);
|
|
699
|
+
}
|
|
700
|
+
if (s.halign !== undefined && !VALID_TEXT_HALIGN.has(String(s.halign))) {
|
|
701
|
+
return bad(`${path}.halign: '${String(s.halign)}' is not a valid halign`);
|
|
702
|
+
}
|
|
703
|
+
if (s.valign !== undefined && !VALID_TEXT_VALIGN.has(String(s.valign))) {
|
|
704
|
+
return bad(`${path}.valign: '${String(s.valign)}' is not a valid valign`);
|
|
705
|
+
}
|
|
706
|
+
if (s.bgColor !== undefined && typeof s.bgColor !== "string") {
|
|
707
|
+
return bad(`${path}.bgColor: must be a string`);
|
|
708
|
+
}
|
|
709
|
+
return { ok: true };
|
|
710
|
+
}
|
|
711
|
+
function validateMarkerState(state) {
|
|
712
|
+
if (!isWorldPoint(state.anchor))
|
|
713
|
+
return bad("drawing.state.anchor: not a WorldPoint");
|
|
714
|
+
if (state.text !== undefined && typeof state.text !== "string") {
|
|
715
|
+
return bad("drawing.state.text: must be a string");
|
|
716
|
+
}
|
|
717
|
+
if (state.value !== undefined && !isFiniteNumber(state.value)) {
|
|
718
|
+
return bad("drawing.state.value: must be a finite number");
|
|
719
|
+
}
|
|
720
|
+
return validateTextOpts(state.style, "drawing.state.style");
|
|
721
|
+
}
|
|
722
|
+
const FREEHAND_MIN_ANCHORS = 2;
|
|
723
|
+
const FREEHAND_MAX_ANCHORS = 500;
|
|
724
|
+
function validateHighlighterStyle(s, path) {
|
|
725
|
+
if (!isPlainObject(s))
|
|
726
|
+
return bad(`${path}: must be a plain object`);
|
|
727
|
+
if (typeof s.color !== "string")
|
|
728
|
+
return bad(`${path}.color: must be a string`);
|
|
729
|
+
if (!isFiniteNumber(s.alpha) || s.alpha < 0 || s.alpha > 1) {
|
|
730
|
+
return bad(`${path}.alpha: must be a finite number in [0, 1]`);
|
|
731
|
+
}
|
|
732
|
+
return { ok: true };
|
|
733
|
+
}
|
|
734
|
+
function validateBrushStyle(s, path) {
|
|
735
|
+
if (!isPlainObject(s))
|
|
736
|
+
return bad(`${path}: must be a plain object`);
|
|
737
|
+
if (typeof s.stroke !== "string")
|
|
738
|
+
return bad(`${path}.stroke: must be a string`);
|
|
739
|
+
if (typeof s.fill !== "string")
|
|
740
|
+
return bad(`${path}.fill: must be a string`);
|
|
741
|
+
return { ok: true };
|
|
742
|
+
}
|
|
743
|
+
function validateArcState(state) {
|
|
744
|
+
const anchorsCheck = validateAnchorTriple(state.anchors, "drawing.state.anchors");
|
|
745
|
+
if (!anchorsCheck.ok)
|
|
746
|
+
return anchorsCheck;
|
|
747
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
748
|
+
}
|
|
749
|
+
function validateCurveState(state) {
|
|
750
|
+
const anchorsCheck = validateAnchorTriple(state.anchors, "drawing.state.anchors");
|
|
751
|
+
if (!anchorsCheck.ok)
|
|
752
|
+
return anchorsCheck;
|
|
753
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
754
|
+
}
|
|
755
|
+
function validateDoubleCurveState(state) {
|
|
756
|
+
const anchorsCheck = validateAnchorQuint(state.anchors, "drawing.state.anchors");
|
|
757
|
+
if (!anchorsCheck.ok)
|
|
758
|
+
return anchorsCheck;
|
|
759
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
760
|
+
}
|
|
761
|
+
function validatePenState(state) {
|
|
762
|
+
const anchorsCheck = validateAnchorVariable(state.anchors, "drawing.state.anchors", FREEHAND_MIN_ANCHORS, FREEHAND_MAX_ANCHORS);
|
|
763
|
+
if (!anchorsCheck.ok)
|
|
764
|
+
return anchorsCheck;
|
|
765
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
766
|
+
}
|
|
767
|
+
function validateHighlighterState(state) {
|
|
768
|
+
const anchorsCheck = validateAnchorVariable(state.anchors, "drawing.state.anchors", FREEHAND_MIN_ANCHORS, FREEHAND_MAX_ANCHORS);
|
|
769
|
+
if (!anchorsCheck.ok)
|
|
770
|
+
return anchorsCheck;
|
|
771
|
+
return validateHighlighterStyle(state.style, "drawing.state.style");
|
|
772
|
+
}
|
|
773
|
+
function validateBrushState(state) {
|
|
774
|
+
const anchorsCheck = validateAnchorVariable(state.anchors, "drawing.state.anchors", FREEHAND_MIN_ANCHORS, FREEHAND_MAX_ANCHORS);
|
|
775
|
+
if (!anchorsCheck.ok)
|
|
776
|
+
return anchorsCheck;
|
|
777
|
+
return validateBrushStyle(state.style, "drawing.state.style");
|
|
778
|
+
}
|
|
779
|
+
const TEXT_BODY_MAX_LENGTH = 256;
|
|
780
|
+
function validateArrowOpts(s, path) {
|
|
781
|
+
const lineCheck = validateLineDrawStyle(s, path);
|
|
782
|
+
if (!lineCheck.ok)
|
|
783
|
+
return lineCheck;
|
|
784
|
+
// validateLineDrawStyle proved `s` is a plain object.
|
|
785
|
+
const obj = s;
|
|
786
|
+
if (obj.label !== undefined && typeof obj.label !== "string") {
|
|
787
|
+
return bad(`${path}.label: must be a string`);
|
|
788
|
+
}
|
|
789
|
+
return { ok: true };
|
|
790
|
+
}
|
|
791
|
+
function validateArrowMarkerOpts(s, path) {
|
|
792
|
+
if (!isPlainObject(s))
|
|
793
|
+
return bad(`${path}: must be a plain object`);
|
|
794
|
+
if (s.color !== undefined && typeof s.color !== "string") {
|
|
795
|
+
return bad(`${path}.color: must be a string`);
|
|
796
|
+
}
|
|
797
|
+
if (s.text !== undefined && typeof s.text !== "string") {
|
|
798
|
+
return bad(`${path}.text: must be a string`);
|
|
799
|
+
}
|
|
800
|
+
return { ok: true };
|
|
801
|
+
}
|
|
802
|
+
function validateTextState(state) {
|
|
803
|
+
if (!isWorldPoint(state.anchor))
|
|
804
|
+
return bad("drawing.state.anchor: not a WorldPoint");
|
|
805
|
+
// walkMeta catches non-JsonValue payloads (bigint / function / symbol /
|
|
806
|
+
// undefined / non-finite number) anywhere on `body`; a bare string is
|
|
807
|
+
// a valid JsonValue and passes through. The kind-specific
|
|
808
|
+
// non-empty / length cap follows so the wire-shape error message stays
|
|
809
|
+
// specific.
|
|
810
|
+
const bodyMetaCheck = walkMeta(state.body, "drawing.state.body");
|
|
811
|
+
if (!bodyMetaCheck.ok)
|
|
812
|
+
return bodyMetaCheck;
|
|
813
|
+
if (typeof state.body !== "string") {
|
|
814
|
+
return bad("drawing.state.body: must be a string");
|
|
815
|
+
}
|
|
816
|
+
if (state.body.length === 0) {
|
|
817
|
+
return bad("drawing.state.body: must be a non-empty string");
|
|
818
|
+
}
|
|
819
|
+
if (state.body.length > TEXT_BODY_MAX_LENGTH) {
|
|
820
|
+
return bad(`drawing.state.body: must be at most ${TEXT_BODY_MAX_LENGTH} characters`);
|
|
821
|
+
}
|
|
822
|
+
return validateTextOpts(state.style, "drawing.state.style");
|
|
823
|
+
}
|
|
824
|
+
function validateArrowState(state) {
|
|
825
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
826
|
+
if (!anchorsCheck.ok)
|
|
827
|
+
return anchorsCheck;
|
|
828
|
+
return validateArrowOpts(state.style, "drawing.state.style");
|
|
829
|
+
}
|
|
830
|
+
function validateArrowMarkerState(state) {
|
|
831
|
+
if (!isWorldPoint(state.anchor))
|
|
832
|
+
return bad("drawing.state.anchor: not a WorldPoint");
|
|
833
|
+
return validateArrowMarkerOpts(state.style, "drawing.state.style");
|
|
834
|
+
}
|
|
835
|
+
function validateArrowMarkUpState(state) {
|
|
836
|
+
if (!isWorldPoint(state.anchor))
|
|
837
|
+
return bad("drawing.state.anchor: not a WorldPoint");
|
|
838
|
+
return validateArrowMarkerOpts(state.style, "drawing.state.style");
|
|
839
|
+
}
|
|
840
|
+
function validateArrowMarkDownState(state) {
|
|
841
|
+
if (!isWorldPoint(state.anchor))
|
|
842
|
+
return bad("drawing.state.anchor: not a WorldPoint");
|
|
843
|
+
return validateArrowMarkerOpts(state.style, "drawing.state.style");
|
|
844
|
+
}
|
|
845
|
+
const VALID_REGRESSION_SOURCES = new Set([
|
|
846
|
+
"close",
|
|
847
|
+
"open",
|
|
848
|
+
"high",
|
|
849
|
+
"low",
|
|
850
|
+
"hl2",
|
|
851
|
+
"hlc3",
|
|
852
|
+
"ohlc4",
|
|
853
|
+
"hlcc4",
|
|
854
|
+
]);
|
|
855
|
+
function validateRegressionTrendOpts(s, path) {
|
|
856
|
+
if (!isPlainObject(s))
|
|
857
|
+
return bad(`${path}: must be a plain object`);
|
|
858
|
+
if (s.source !== undefined && !VALID_REGRESSION_SOURCES.has(String(s.source))) {
|
|
859
|
+
return bad(`${path}.source: '${String(s.source)}' is not a valid source`);
|
|
860
|
+
}
|
|
861
|
+
if (s.stdevMultiplier !== undefined) {
|
|
862
|
+
if (!isFiniteNumber(s.stdevMultiplier) || s.stdevMultiplier < 0) {
|
|
863
|
+
return bad(`${path}.stdevMultiplier: must be a non-negative finite number`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
if (s.showUpperBand !== undefined && typeof s.showUpperBand !== "boolean") {
|
|
867
|
+
return bad(`${path}.showUpperBand: must be a boolean`);
|
|
868
|
+
}
|
|
869
|
+
if (s.showLowerBand !== undefined && typeof s.showLowerBand !== "boolean") {
|
|
870
|
+
return bad(`${path}.showLowerBand: must be a boolean`);
|
|
871
|
+
}
|
|
872
|
+
if (s.color !== undefined && typeof s.color !== "string") {
|
|
873
|
+
return bad(`${path}.color: must be a string`);
|
|
874
|
+
}
|
|
875
|
+
return { ok: true };
|
|
876
|
+
}
|
|
877
|
+
function validateTrendChannelState(state) {
|
|
878
|
+
const anchorsCheck = validateAnchorTriple(state.anchors, "drawing.state.anchors");
|
|
879
|
+
if (!anchorsCheck.ok)
|
|
880
|
+
return anchorsCheck;
|
|
881
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
882
|
+
}
|
|
883
|
+
function validateFlatTopBottomState(state) {
|
|
884
|
+
const anchorsCheck = validateAnchorTriple(state.anchors, "drawing.state.anchors");
|
|
885
|
+
if (!anchorsCheck.ok)
|
|
886
|
+
return anchorsCheck;
|
|
887
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
888
|
+
}
|
|
889
|
+
function validateDisjointChannelState(state) {
|
|
890
|
+
const anchorsCheck = validateAnchorQuad(state.anchors, "drawing.state.anchors");
|
|
891
|
+
if (!anchorsCheck.ok)
|
|
892
|
+
return anchorsCheck;
|
|
893
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
894
|
+
}
|
|
895
|
+
function validateRegressionTrendState(state) {
|
|
896
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
897
|
+
if (!anchorsCheck.ok)
|
|
898
|
+
return anchorsCheck;
|
|
899
|
+
const anchors = state.anchors;
|
|
900
|
+
if (!(anchors[0].time < anchors[1].time)) {
|
|
901
|
+
return bad("drawing.state.anchors: anchors[0].time must be < anchors[1].time");
|
|
902
|
+
}
|
|
903
|
+
return validateRegressionTrendOpts(state.style, "drawing.state.style");
|
|
904
|
+
}
|
|
905
|
+
function validateFibOpts(s, path) {
|
|
906
|
+
if (!isPlainObject(s))
|
|
907
|
+
return bad(`${path}: must be a plain object`);
|
|
908
|
+
if (s.levels !== undefined) {
|
|
909
|
+
if (!Array.isArray(s.levels)) {
|
|
910
|
+
return bad(`${path}.levels: must be an array of finite numbers`);
|
|
911
|
+
}
|
|
912
|
+
if (s.levels.length === 0) {
|
|
913
|
+
return bad(`${path}.levels: must contain at least one level`);
|
|
914
|
+
}
|
|
915
|
+
for (let i = 0; i < s.levels.length; i++) {
|
|
916
|
+
if (!isFiniteNumber(s.levels[i])) {
|
|
917
|
+
return bad(`${path}.levels[${i}]: must be a finite number`);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (s.showLabels !== undefined && typeof s.showLabels !== "boolean") {
|
|
922
|
+
return bad(`${path}.showLabels: must be a boolean`);
|
|
923
|
+
}
|
|
924
|
+
if (s.color !== undefined && typeof s.color !== "string") {
|
|
925
|
+
return bad(`${path}.color: must be a string`);
|
|
926
|
+
}
|
|
927
|
+
if (s.extendLeft !== undefined && typeof s.extendLeft !== "boolean") {
|
|
928
|
+
return bad(`${path}.extendLeft: must be a boolean`);
|
|
929
|
+
}
|
|
930
|
+
if (s.extendRight !== undefined && typeof s.extendRight !== "boolean") {
|
|
931
|
+
return bad(`${path}.extendRight: must be a boolean`);
|
|
932
|
+
}
|
|
933
|
+
return { ok: true };
|
|
934
|
+
}
|
|
935
|
+
function validateFibRetracementState(state) {
|
|
936
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
937
|
+
if (!anchorsCheck.ok)
|
|
938
|
+
return anchorsCheck;
|
|
939
|
+
return validateFibOpts(state.style, "drawing.state.style");
|
|
940
|
+
}
|
|
941
|
+
function validateFibTrendExtensionState(state) {
|
|
942
|
+
const anchorsCheck = validateAnchorTriple(state.anchors, "drawing.state.anchors");
|
|
943
|
+
if (!anchorsCheck.ok)
|
|
944
|
+
return anchorsCheck;
|
|
945
|
+
return validateFibOpts(state.style, "drawing.state.style");
|
|
946
|
+
}
|
|
947
|
+
function validateFibChannelState(state) {
|
|
948
|
+
const anchorsCheck = validateAnchorTriple(state.anchors, "drawing.state.anchors");
|
|
949
|
+
if (!anchorsCheck.ok)
|
|
950
|
+
return anchorsCheck;
|
|
951
|
+
return validateFibOpts(state.style, "drawing.state.style");
|
|
952
|
+
}
|
|
953
|
+
function validateFibTimeZoneState(state) {
|
|
954
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
955
|
+
if (!anchorsCheck.ok)
|
|
956
|
+
return anchorsCheck;
|
|
957
|
+
return validateFibOpts(state.style, "drawing.state.style");
|
|
958
|
+
}
|
|
959
|
+
function validateFibWedgeState(state) {
|
|
960
|
+
const anchorsCheck = validateAnchorTriple(state.anchors, "drawing.state.anchors");
|
|
961
|
+
if (!anchorsCheck.ok)
|
|
962
|
+
return anchorsCheck;
|
|
963
|
+
return validateFibOpts(state.style, "drawing.state.style");
|
|
964
|
+
}
|
|
965
|
+
function validateFibSpeedFanState(state) {
|
|
966
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
967
|
+
if (!anchorsCheck.ok)
|
|
968
|
+
return anchorsCheck;
|
|
969
|
+
return validateFibOpts(state.style, "drawing.state.style");
|
|
970
|
+
}
|
|
971
|
+
function validateFibSpeedArcsState(state) {
|
|
972
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
973
|
+
if (!anchorsCheck.ok)
|
|
974
|
+
return anchorsCheck;
|
|
975
|
+
return validateFibOpts(state.style, "drawing.state.style");
|
|
976
|
+
}
|
|
977
|
+
function validateFibSpiralState(state) {
|
|
978
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
979
|
+
if (!anchorsCheck.ok)
|
|
980
|
+
return anchorsCheck;
|
|
981
|
+
return validateFibOpts(state.style, "drawing.state.style");
|
|
982
|
+
}
|
|
983
|
+
function validateFibCirclesState(state) {
|
|
984
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
985
|
+
if (!anchorsCheck.ok)
|
|
986
|
+
return anchorsCheck;
|
|
987
|
+
return validateFibOpts(state.style, "drawing.state.style");
|
|
988
|
+
}
|
|
989
|
+
function validateFibTrendTimeState(state) {
|
|
990
|
+
const anchorsCheck = validateAnchorTriple(state.anchors, "drawing.state.anchors");
|
|
991
|
+
if (!anchorsCheck.ok)
|
|
992
|
+
return anchorsCheck;
|
|
993
|
+
return validateFibOpts(state.style, "drawing.state.style");
|
|
994
|
+
}
|
|
995
|
+
function validateGannBoxState(state) {
|
|
996
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
997
|
+
if (!anchorsCheck.ok)
|
|
998
|
+
return anchorsCheck;
|
|
999
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1000
|
+
}
|
|
1001
|
+
function validateGannSquareFixedState(state) {
|
|
1002
|
+
if (!isWorldPoint(state.anchor))
|
|
1003
|
+
return bad("drawing.state.anchor: not a WorldPoint");
|
|
1004
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1005
|
+
}
|
|
1006
|
+
function validateGannSquareState(state) {
|
|
1007
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
1008
|
+
if (!anchorsCheck.ok)
|
|
1009
|
+
return anchorsCheck;
|
|
1010
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1011
|
+
}
|
|
1012
|
+
function validateGannFanState(state) {
|
|
1013
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
1014
|
+
if (!anchorsCheck.ok)
|
|
1015
|
+
return anchorsCheck;
|
|
1016
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1017
|
+
}
|
|
1018
|
+
const PITCHFORK_VARIANTS = new Set([
|
|
1019
|
+
"standard",
|
|
1020
|
+
"schiff",
|
|
1021
|
+
"modifiedSchiff",
|
|
1022
|
+
"inside",
|
|
1023
|
+
]);
|
|
1024
|
+
function validatePitchforkState(state) {
|
|
1025
|
+
const anchorsCheck = validateAnchorTriple(state.anchors, "drawing.state.anchors");
|
|
1026
|
+
if (!anchorsCheck.ok)
|
|
1027
|
+
return anchorsCheck;
|
|
1028
|
+
if (typeof state.variant !== "string" || !PITCHFORK_VARIANTS.has(state.variant)) {
|
|
1029
|
+
return bad(`drawing.state.variant: '${String(state.variant)}' must be 'standard' | 'schiff' | 'modifiedSchiff' | 'inside'`);
|
|
1030
|
+
}
|
|
1031
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1032
|
+
}
|
|
1033
|
+
function validatePitchfanState(state) {
|
|
1034
|
+
const anchorsCheck = validateAnchorTriple(state.anchors, "drawing.state.anchors");
|
|
1035
|
+
if (!anchorsCheck.ok)
|
|
1036
|
+
return anchorsCheck;
|
|
1037
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1038
|
+
}
|
|
1039
|
+
function validateXabcdPatternState(state) {
|
|
1040
|
+
const anchorsCheck = validateAnchorQuint(state.anchors, "drawing.state.anchors");
|
|
1041
|
+
if (!anchorsCheck.ok)
|
|
1042
|
+
return anchorsCheck;
|
|
1043
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1044
|
+
}
|
|
1045
|
+
function validateCypherPatternState(state) {
|
|
1046
|
+
const anchorsCheck = validateAnchorQuint(state.anchors, "drawing.state.anchors");
|
|
1047
|
+
if (!anchorsCheck.ok)
|
|
1048
|
+
return anchorsCheck;
|
|
1049
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1050
|
+
}
|
|
1051
|
+
function validateHeadAndShouldersState(state) {
|
|
1052
|
+
const anchorsCheck = validateAnchorQuint(state.anchors, "drawing.state.anchors");
|
|
1053
|
+
if (!anchorsCheck.ok)
|
|
1054
|
+
return anchorsCheck;
|
|
1055
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1056
|
+
}
|
|
1057
|
+
function validateAbcdPatternState(state) {
|
|
1058
|
+
const anchorsCheck = validateAnchorQuad(state.anchors, "drawing.state.anchors");
|
|
1059
|
+
if (!anchorsCheck.ok)
|
|
1060
|
+
return anchorsCheck;
|
|
1061
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1062
|
+
}
|
|
1063
|
+
function validateTrianglePatternState(state) {
|
|
1064
|
+
const anchorsCheck = validateAnchorTriple(state.anchors, "drawing.state.anchors");
|
|
1065
|
+
if (!anchorsCheck.ok)
|
|
1066
|
+
return anchorsCheck;
|
|
1067
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1068
|
+
}
|
|
1069
|
+
function validateThreeDrivesPatternState(state) {
|
|
1070
|
+
const anchorsCheck = validateAnchorHept(state.anchors, "drawing.state.anchors");
|
|
1071
|
+
if (!anchorsCheck.ok)
|
|
1072
|
+
return anchorsCheck;
|
|
1073
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1074
|
+
}
|
|
1075
|
+
function validateElliottImpulseWaveState(state) {
|
|
1076
|
+
const anchorsCheck = validateAnchorQuint(state.anchors, "drawing.state.anchors");
|
|
1077
|
+
if (!anchorsCheck.ok)
|
|
1078
|
+
return anchorsCheck;
|
|
1079
|
+
const labelsCheck = validateOptionalLabels(state.labels, "drawing.state.labels", 5);
|
|
1080
|
+
if (!labelsCheck.ok)
|
|
1081
|
+
return labelsCheck;
|
|
1082
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1083
|
+
}
|
|
1084
|
+
function validateElliottCorrectionWaveState(state) {
|
|
1085
|
+
const anchorsCheck = validateAnchorTriple(state.anchors, "drawing.state.anchors");
|
|
1086
|
+
if (!anchorsCheck.ok)
|
|
1087
|
+
return anchorsCheck;
|
|
1088
|
+
const labelsCheck = validateOptionalLabels(state.labels, "drawing.state.labels", 3);
|
|
1089
|
+
if (!labelsCheck.ok)
|
|
1090
|
+
return labelsCheck;
|
|
1091
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1092
|
+
}
|
|
1093
|
+
function validateElliottTriangleWaveState(state) {
|
|
1094
|
+
const anchorsCheck = validateAnchorQuint(state.anchors, "drawing.state.anchors");
|
|
1095
|
+
if (!anchorsCheck.ok)
|
|
1096
|
+
return anchorsCheck;
|
|
1097
|
+
const labelsCheck = validateOptionalLabels(state.labels, "drawing.state.labels", 5);
|
|
1098
|
+
if (!labelsCheck.ok)
|
|
1099
|
+
return labelsCheck;
|
|
1100
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1101
|
+
}
|
|
1102
|
+
function validateElliottDoubleComboState(state) {
|
|
1103
|
+
const anchorsCheck = validateAnchorHept(state.anchors, "drawing.state.anchors");
|
|
1104
|
+
if (!anchorsCheck.ok)
|
|
1105
|
+
return anchorsCheck;
|
|
1106
|
+
const labelsCheck = validateOptionalLabels(state.labels, "drawing.state.labels", 7);
|
|
1107
|
+
if (!labelsCheck.ok)
|
|
1108
|
+
return labelsCheck;
|
|
1109
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1110
|
+
}
|
|
1111
|
+
function validateElliottTripleComboState(state) {
|
|
1112
|
+
const anchorsCheck = validateAnchorHept(state.anchors, "drawing.state.anchors");
|
|
1113
|
+
if (!anchorsCheck.ok)
|
|
1114
|
+
return anchorsCheck;
|
|
1115
|
+
const labelsCheck = validateOptionalLabels(state.labels, "drawing.state.labels", 7);
|
|
1116
|
+
if (!labelsCheck.ok)
|
|
1117
|
+
return labelsCheck;
|
|
1118
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1119
|
+
}
|
|
1120
|
+
function validateCyclicLinesState(state) {
|
|
1121
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
1122
|
+
if (!anchorsCheck.ok)
|
|
1123
|
+
return anchorsCheck;
|
|
1124
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1125
|
+
}
|
|
1126
|
+
function validateTimeCyclesState(state) {
|
|
1127
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
1128
|
+
if (!anchorsCheck.ok)
|
|
1129
|
+
return anchorsCheck;
|
|
1130
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1131
|
+
}
|
|
1132
|
+
function validateSineLineState(state) {
|
|
1133
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
1134
|
+
if (!anchorsCheck.ok)
|
|
1135
|
+
return anchorsCheck;
|
|
1136
|
+
return validateLineDrawStyle(state.style, "drawing.state.style");
|
|
1137
|
+
}
|
|
1138
|
+
const MAX_CHILD_HANDLE_IDS = 100;
|
|
1139
|
+
function validateChildHandleIds(v, path) {
|
|
1140
|
+
if (!Array.isArray(v)) {
|
|
1141
|
+
return bad(`${path}: must be an array of handle id strings`);
|
|
1142
|
+
}
|
|
1143
|
+
if (v.length > MAX_CHILD_HANDLE_IDS) {
|
|
1144
|
+
return bad(`${path}: must be at most ${MAX_CHILD_HANDLE_IDS} entries`);
|
|
1145
|
+
}
|
|
1146
|
+
for (let i = 0; i < v.length; i++) {
|
|
1147
|
+
if (typeof v[i] !== "string") {
|
|
1148
|
+
return bad(`${path}[${i}]: must be a string`);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return { ok: true };
|
|
1152
|
+
}
|
|
1153
|
+
function validateFrameOpts(s, path) {
|
|
1154
|
+
if (!isPlainObject(s))
|
|
1155
|
+
return bad(`${path}: must be a plain object`);
|
|
1156
|
+
if (s.label !== undefined && typeof s.label !== "string") {
|
|
1157
|
+
return bad(`${path}.label: must be a string`);
|
|
1158
|
+
}
|
|
1159
|
+
if (s.bgColor !== undefined && typeof s.bgColor !== "string") {
|
|
1160
|
+
return bad(`${path}.bgColor: must be a string`);
|
|
1161
|
+
}
|
|
1162
|
+
return { ok: true };
|
|
1163
|
+
}
|
|
1164
|
+
function validateGroupState(state) {
|
|
1165
|
+
return validateChildHandleIds(state.childHandleIds, "drawing.state.childHandleIds");
|
|
1166
|
+
}
|
|
1167
|
+
function validateFrameState(state) {
|
|
1168
|
+
const anchorsCheck = validateAnchorPair(state.anchors, "drawing.state.anchors");
|
|
1169
|
+
if (!anchorsCheck.ok)
|
|
1170
|
+
return anchorsCheck;
|
|
1171
|
+
const childCheck = validateChildHandleIds(state.childHandleIds, "drawing.state.childHandleIds");
|
|
1172
|
+
if (!childCheck.ok)
|
|
1173
|
+
return childCheck;
|
|
1174
|
+
return validateFrameOpts(state.style, "drawing.state.style");
|
|
1175
|
+
}
|
|
1176
|
+
function validateTableCell(cell, path) {
|
|
1177
|
+
if (!isPlainObject(cell))
|
|
1178
|
+
return bad(`${path}: must be a plain object`);
|
|
1179
|
+
if (typeof cell.text !== "string")
|
|
1180
|
+
return bad(`${path}.text: must be a string`);
|
|
1181
|
+
if (cell.bgColor !== undefined && typeof cell.bgColor !== "string") {
|
|
1182
|
+
return bad(`${path}.bgColor: must be a string`);
|
|
1183
|
+
}
|
|
1184
|
+
if (cell.textColor !== undefined && typeof cell.textColor !== "string") {
|
|
1185
|
+
return bad(`${path}.textColor: must be a string`);
|
|
1186
|
+
}
|
|
1187
|
+
if (cell.textHalign !== undefined && !VALID_TEXT_HALIGN.has(String(cell.textHalign))) {
|
|
1188
|
+
return bad(`${path}.textHalign: '${String(cell.textHalign)}' is not a valid halign`);
|
|
1189
|
+
}
|
|
1190
|
+
if (cell.textValign !== undefined && !VALID_TEXT_VALIGN.has(String(cell.textValign))) {
|
|
1191
|
+
return bad(`${path}.textValign: '${String(cell.textValign)}' is not a valid valign`);
|
|
1192
|
+
}
|
|
1193
|
+
if (cell.textSize !== undefined && !VALID_TEXT_SIZES.has(String(cell.textSize))) {
|
|
1194
|
+
return bad(`${path}.textSize: '${String(cell.textSize)}' is not a valid text size`);
|
|
1195
|
+
}
|
|
1196
|
+
return { ok: true };
|
|
1197
|
+
}
|
|
1198
|
+
function validateTableState(state) {
|
|
1199
|
+
const position = state.position;
|
|
1200
|
+
if (typeof position !== "string" || !VALID_TABLE_POSITIONS.has(position)) {
|
|
1201
|
+
return bad(`drawing.state.position: '${String(position)}' is not a valid table position`);
|
|
1202
|
+
}
|
|
1203
|
+
const cells = state.cells;
|
|
1204
|
+
if (!Array.isArray(cells) || cells.length === 0) {
|
|
1205
|
+
return bad("drawing.state.cells: must be a non-empty 2D array");
|
|
1206
|
+
}
|
|
1207
|
+
for (let rowIndex = 0; rowIndex < cells.length; rowIndex++) {
|
|
1208
|
+
const row = cells[rowIndex];
|
|
1209
|
+
if (!Array.isArray(row) || row.length === 0) {
|
|
1210
|
+
return bad(`drawing.state.cells[${rowIndex}]: must be a non-empty array`);
|
|
1211
|
+
}
|
|
1212
|
+
for (let columnIndex = 0; columnIndex < row.length; columnIndex++) {
|
|
1213
|
+
const cellCheck = validateTableCell(row[columnIndex], `drawing.state.cells[${rowIndex}][${columnIndex}]`);
|
|
1214
|
+
if (!cellCheck.ok)
|
|
1215
|
+
return cellCheck;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
const hasBorderColor = state.borderColor !== undefined;
|
|
1219
|
+
const hasBorderWidth = state.borderWidth !== undefined;
|
|
1220
|
+
if (hasBorderColor !== hasBorderWidth) {
|
|
1221
|
+
return bad("drawing.state.borderColor/borderWidth: must be provided together");
|
|
1222
|
+
}
|
|
1223
|
+
if (hasBorderColor) {
|
|
1224
|
+
const colorCheck = validateColor(state.borderColor, "drawing.state.borderColor");
|
|
1225
|
+
if (!colorCheck.ok)
|
|
1226
|
+
return colorCheck;
|
|
1227
|
+
if (!isFiniteNumber(state.borderWidth) || state.borderWidth <= 0) {
|
|
1228
|
+
return bad("drawing.state.borderWidth: must be a finite positive number");
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
if (state.frame !== undefined) {
|
|
1232
|
+
if (!isPlainObject(state.frame))
|
|
1233
|
+
return bad("drawing.state.frame: must be a plain object");
|
|
1234
|
+
const colorCheck = validateColor(state.frame.color, "drawing.state.frame.color");
|
|
1235
|
+
if (!colorCheck.ok)
|
|
1236
|
+
return colorCheck;
|
|
1237
|
+
if (!isFiniteNumber(state.frame.width) || state.frame.width <= 0) {
|
|
1238
|
+
return bad("drawing.state.frame.width: must be a finite positive number");
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return { ok: true };
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Per-kind state dispatch. Exhaustive over the `DrawingKind` union: the
|
|
1245
|
+
* switch has no `default` arm, so adding a kind to `DrawingKind`
|
|
1246
|
+
* produces a compile error here and forces a matching validator arm.
|
|
1247
|
+
* Wire-shape checks (`handleId` / `op` / `bar` / `time` /
|
|
1248
|
+
* `state.kind === drawingKind`) run for every kind via
|
|
1249
|
+
* {@link validateDrawingEmission} before this dispatch is reached.
|
|
1250
|
+
*/
|
|
1251
|
+
function validateStateByKind(kind, state) {
|
|
1252
|
+
switch (kind) {
|
|
1253
|
+
case "line":
|
|
1254
|
+
return validateLineState(state);
|
|
1255
|
+
case "horizontal-line":
|
|
1256
|
+
return validateHorizontalLineState(state);
|
|
1257
|
+
case "horizontal-ray":
|
|
1258
|
+
return validateHorizontalRayState(state);
|
|
1259
|
+
case "vertical-line":
|
|
1260
|
+
return validateVerticalLineState(state);
|
|
1261
|
+
case "cross-line":
|
|
1262
|
+
return validateCrossLineState(state);
|
|
1263
|
+
case "trend-angle":
|
|
1264
|
+
return validateTrendAngleState(state);
|
|
1265
|
+
case "rectangle":
|
|
1266
|
+
return validateRectangleState(state);
|
|
1267
|
+
case "rotated-rectangle":
|
|
1268
|
+
return validateRotatedRectangleState(state);
|
|
1269
|
+
case "triangle":
|
|
1270
|
+
return validateTriangleState(state);
|
|
1271
|
+
case "polyline":
|
|
1272
|
+
return validatePolylineState(state);
|
|
1273
|
+
case "circle":
|
|
1274
|
+
return validateCircleState(state);
|
|
1275
|
+
case "ellipse":
|
|
1276
|
+
return validateEllipseState(state);
|
|
1277
|
+
case "path":
|
|
1278
|
+
return validatePathState(state);
|
|
1279
|
+
case "marker":
|
|
1280
|
+
return validateMarkerState(state);
|
|
1281
|
+
case "arc":
|
|
1282
|
+
return validateArcState(state);
|
|
1283
|
+
case "curve":
|
|
1284
|
+
return validateCurveState(state);
|
|
1285
|
+
case "double-curve":
|
|
1286
|
+
return validateDoubleCurveState(state);
|
|
1287
|
+
case "pen":
|
|
1288
|
+
return validatePenState(state);
|
|
1289
|
+
case "highlighter":
|
|
1290
|
+
return validateHighlighterState(state);
|
|
1291
|
+
case "brush":
|
|
1292
|
+
return validateBrushState(state);
|
|
1293
|
+
case "text":
|
|
1294
|
+
return validateTextState(state);
|
|
1295
|
+
case "arrow":
|
|
1296
|
+
return validateArrowState(state);
|
|
1297
|
+
case "arrow-marker":
|
|
1298
|
+
return validateArrowMarkerState(state);
|
|
1299
|
+
case "arrow-mark-up":
|
|
1300
|
+
return validateArrowMarkUpState(state);
|
|
1301
|
+
case "arrow-mark-down":
|
|
1302
|
+
return validateArrowMarkDownState(state);
|
|
1303
|
+
case "trend-channel":
|
|
1304
|
+
return validateTrendChannelState(state);
|
|
1305
|
+
case "flat-top-bottom":
|
|
1306
|
+
return validateFlatTopBottomState(state);
|
|
1307
|
+
case "disjoint-channel":
|
|
1308
|
+
return validateDisjointChannelState(state);
|
|
1309
|
+
case "regression-trend":
|
|
1310
|
+
return validateRegressionTrendState(state);
|
|
1311
|
+
case "fib-retracement":
|
|
1312
|
+
return validateFibRetracementState(state);
|
|
1313
|
+
case "fib-trend-extension":
|
|
1314
|
+
return validateFibTrendExtensionState(state);
|
|
1315
|
+
case "fib-channel":
|
|
1316
|
+
return validateFibChannelState(state);
|
|
1317
|
+
case "fib-time-zone":
|
|
1318
|
+
return validateFibTimeZoneState(state);
|
|
1319
|
+
case "fib-wedge":
|
|
1320
|
+
return validateFibWedgeState(state);
|
|
1321
|
+
case "fib-speed-fan":
|
|
1322
|
+
return validateFibSpeedFanState(state);
|
|
1323
|
+
case "fib-speed-arcs":
|
|
1324
|
+
return validateFibSpeedArcsState(state);
|
|
1325
|
+
case "fib-spiral":
|
|
1326
|
+
return validateFibSpiralState(state);
|
|
1327
|
+
case "fib-circles":
|
|
1328
|
+
return validateFibCirclesState(state);
|
|
1329
|
+
case "fib-trend-time":
|
|
1330
|
+
return validateFibTrendTimeState(state);
|
|
1331
|
+
case "gann-box":
|
|
1332
|
+
return validateGannBoxState(state);
|
|
1333
|
+
case "gann-square-fixed":
|
|
1334
|
+
return validateGannSquareFixedState(state);
|
|
1335
|
+
case "gann-square":
|
|
1336
|
+
return validateGannSquareState(state);
|
|
1337
|
+
case "gann-fan":
|
|
1338
|
+
return validateGannFanState(state);
|
|
1339
|
+
case "pitchfork":
|
|
1340
|
+
return validatePitchforkState(state);
|
|
1341
|
+
case "pitchfan":
|
|
1342
|
+
return validatePitchfanState(state);
|
|
1343
|
+
case "xabcd-pattern":
|
|
1344
|
+
return validateXabcdPatternState(state);
|
|
1345
|
+
case "cypher-pattern":
|
|
1346
|
+
return validateCypherPatternState(state);
|
|
1347
|
+
case "head-and-shoulders":
|
|
1348
|
+
return validateHeadAndShouldersState(state);
|
|
1349
|
+
case "abcd-pattern":
|
|
1350
|
+
return validateAbcdPatternState(state);
|
|
1351
|
+
case "triangle-pattern":
|
|
1352
|
+
return validateTrianglePatternState(state);
|
|
1353
|
+
case "three-drives-pattern":
|
|
1354
|
+
return validateThreeDrivesPatternState(state);
|
|
1355
|
+
case "elliott-impulse-wave":
|
|
1356
|
+
return validateElliottImpulseWaveState(state);
|
|
1357
|
+
case "elliott-correction-wave":
|
|
1358
|
+
return validateElliottCorrectionWaveState(state);
|
|
1359
|
+
case "elliott-triangle-wave":
|
|
1360
|
+
return validateElliottTriangleWaveState(state);
|
|
1361
|
+
case "elliott-double-combo":
|
|
1362
|
+
return validateElliottDoubleComboState(state);
|
|
1363
|
+
case "elliott-triple-combo":
|
|
1364
|
+
return validateElliottTripleComboState(state);
|
|
1365
|
+
case "cyclic-lines":
|
|
1366
|
+
return validateCyclicLinesState(state);
|
|
1367
|
+
case "time-cycles":
|
|
1368
|
+
return validateTimeCyclesState(state);
|
|
1369
|
+
case "sine-line":
|
|
1370
|
+
return validateSineLineState(state);
|
|
1371
|
+
case "group":
|
|
1372
|
+
return validateGroupState(state);
|
|
1373
|
+
case "frame":
|
|
1374
|
+
return validateFrameState(state);
|
|
1375
|
+
case "table":
|
|
1376
|
+
return validateTableState(state);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
function validateDrawingEmission(e) {
|
|
1380
|
+
if (!isNonEmptyString(e.handleId)) {
|
|
1381
|
+
return bad("drawing.handleId: must be a non-empty string");
|
|
1382
|
+
}
|
|
1383
|
+
const drawingKind = e.drawingKind;
|
|
1384
|
+
if (typeof drawingKind !== "string" || !VALID_DRAWING_KINDS.has(drawingKind)) {
|
|
1385
|
+
return {
|
|
1386
|
+
ok: false,
|
|
1387
|
+
code: "unsupported-drawing-kind",
|
|
1388
|
+
message: `drawing.drawingKind: '${String(drawingKind)}' is not a known DrawingKind`,
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
if (typeof e.op !== "string" || !VALID_DRAWING_OPS.has(e.op)) {
|
|
1392
|
+
return bad(`drawing.op: '${String(e.op)}' must be 'create' | 'update' | 'remove'`);
|
|
1393
|
+
}
|
|
1394
|
+
if (!isNonNegativeInteger(e.bar)) {
|
|
1395
|
+
return bad("drawing.bar: must be a non-negative integer");
|
|
1396
|
+
}
|
|
1397
|
+
if (!isFiniteNumber(e.time)) {
|
|
1398
|
+
return bad("drawing.time: must be a finite number");
|
|
1399
|
+
}
|
|
1400
|
+
const state = e.state;
|
|
1401
|
+
if (!isPlainObject(state)) {
|
|
1402
|
+
return bad("drawing.state: must be a plain object");
|
|
1403
|
+
}
|
|
1404
|
+
if (state.kind !== drawingKind) {
|
|
1405
|
+
return bad(`drawing.state.kind: '${String(state.kind)}' must equal drawing.drawingKind '${drawingKind}'`);
|
|
1406
|
+
}
|
|
1407
|
+
const metaCheck = validateDrawingMeta(state);
|
|
1408
|
+
if (!metaCheck.ok)
|
|
1409
|
+
return metaCheck;
|
|
1410
|
+
return validateStateByKind(drawingKind, state);
|
|
1411
|
+
}
|
|
1412
|
+
function validateDiagnostic(e) {
|
|
1413
|
+
const severity = e.severity;
|
|
1414
|
+
if (typeof severity !== "string" || !VALID_DIAGNOSTIC_SEVERITIES.has(severity)) {
|
|
1415
|
+
return bad(`diagnostic.severity: '${String(severity)}' is not a valid severity`);
|
|
1416
|
+
}
|
|
1417
|
+
const code = e.code;
|
|
1418
|
+
if (typeof code !== "string" || !VALID_DIAGNOSTIC_CODES.has(code)) {
|
|
1419
|
+
return bad(`diagnostic.code: '${String(code)}' is not a known DiagnosticCode`);
|
|
1420
|
+
}
|
|
1421
|
+
if (typeof e.message !== "string")
|
|
1422
|
+
return bad("diagnostic.message: must be a string");
|
|
1423
|
+
const slotId = e.slotId;
|
|
1424
|
+
if (slotId !== null && typeof slotId !== "string") {
|
|
1425
|
+
return bad("diagnostic.slotId: must be a string or null");
|
|
1426
|
+
}
|
|
1427
|
+
const bar = e.bar;
|
|
1428
|
+
if (bar !== null && !isNonNegativeInteger(bar)) {
|
|
1429
|
+
return bad("diagnostic.bar: must be a non-negative integer or null");
|
|
1430
|
+
}
|
|
1431
|
+
return { ok: true };
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Hand-rolled validator covering every Phase-1 / Phase-2 / Phase-3
|
|
1435
|
+
* emission shape. Returns `{ ok: true }` for well-formed payloads and
|
|
1436
|
+
* `{ ok: false, code, message }` otherwise. Hosts and adapters call
|
|
1437
|
+
* this at every structured-clone boundary (Worker `postMessage`,
|
|
1438
|
+
* QuickJS membrane) per PLAN §7.3.
|
|
1439
|
+
*
|
|
1440
|
+
* Phase 3 widens the drawing dispatch from an unconditional Phase-1
|
|
1441
|
+
* stub to a per-kind validator: unknown `drawingKind` returns
|
|
1442
|
+
* `unsupported-drawing-kind`; malformed payloads of a known kind
|
|
1443
|
+
* return `malformed-emission`. Tasks 6–18 each ADD their kind
|
|
1444
|
+
* validators to the dispatch as ports land.
|
|
1445
|
+
*
|
|
1446
|
+
* @since 0.1
|
|
1447
|
+
* @stable
|
|
1448
|
+
* @example
|
|
1449
|
+
* import { validateEmission } from "@invinite-org/chartlang-adapter-kit";
|
|
1450
|
+
*
|
|
1451
|
+
* const r = validateEmission({ kind: "plot" });
|
|
1452
|
+
* if (!r.ok) {
|
|
1453
|
+
* console.error(r.code, r.message);
|
|
1454
|
+
* }
|
|
1455
|
+
*/
|
|
1456
|
+
export function validateEmission(e) {
|
|
1457
|
+
if (!isPlainObject(e)) {
|
|
1458
|
+
return bad("emission: not a plain object");
|
|
1459
|
+
}
|
|
1460
|
+
if (!("kind" in e)) {
|
|
1461
|
+
return bad("emission: missing 'kind' discriminant");
|
|
1462
|
+
}
|
|
1463
|
+
const kind = e.kind;
|
|
1464
|
+
switch (kind) {
|
|
1465
|
+
case "plot":
|
|
1466
|
+
return validatePlotEmission(e);
|
|
1467
|
+
case "alert":
|
|
1468
|
+
return validateAlertEmission(e);
|
|
1469
|
+
case "alert-condition":
|
|
1470
|
+
return validateAlertConditionEmission(e);
|
|
1471
|
+
case "log":
|
|
1472
|
+
return validateLogEmission(e);
|
|
1473
|
+
case "drawing":
|
|
1474
|
+
return validateDrawingEmission(e);
|
|
1475
|
+
case "diagnostic":
|
|
1476
|
+
return validateDiagnostic(e);
|
|
1477
|
+
default:
|
|
1478
|
+
return bad(`emission.kind: '${String(kind)}' is not a known emission kind`);
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
//# sourceMappingURL=validateEmission.js.map
|