@supersolutions/super-chart-superscript 0.0.0-stage → 0.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/LICENSE +674 -0
- package/README.md +12 -2
- package/dist/chunk-PAWYIS77.js +1214 -0
- package/dist/index.d.ts +354 -0
- package/dist/index.js +429 -0
- package/dist/scripts.d.ts +119 -0
- package/dist/scripts.js +443 -0
- package/package.json +51 -4
package/dist/scripts.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BUILTINS,
|
|
3
|
+
PINE_ALIASES,
|
|
4
|
+
PINE_ARG_NAMES,
|
|
5
|
+
SuperScriptError,
|
|
6
|
+
check,
|
|
7
|
+
evaluate,
|
|
8
|
+
parse
|
|
9
|
+
} from "./chunk-PAWYIS77.js";
|
|
10
|
+
|
|
11
|
+
// packages/superscript/src/scripts.ts
|
|
12
|
+
var ENGINE_SCRIPT_LIMITS = Object.freeze({
|
|
13
|
+
sourceLength: 32768,
|
|
14
|
+
nodes: 2048,
|
|
15
|
+
depth: 64,
|
|
16
|
+
plots: 32,
|
|
17
|
+
inputs: 64,
|
|
18
|
+
bars: 1e5,
|
|
19
|
+
window: 1e4,
|
|
20
|
+
seriesCells: 8e6,
|
|
21
|
+
workUnits: 5e7
|
|
22
|
+
});
|
|
23
|
+
var EngineScriptError = class extends Error {
|
|
24
|
+
constructor(code, message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.code = code;
|
|
27
|
+
this.name = "EngineScriptError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
var PALETTE = ["#8bafff", "#ffb454", "#cf9bff", "#58c9ac", "#f28b82", "#d8c66a"];
|
|
31
|
+
function limitsOf(overrides = {}) {
|
|
32
|
+
const limits = { ...ENGINE_SCRIPT_LIMITS };
|
|
33
|
+
for (const key of Object.keys(overrides)) {
|
|
34
|
+
const value = overrides[key];
|
|
35
|
+
if (!Object.prototype.hasOwnProperty.call(ENGINE_SCRIPT_LIMITS, key) || !Number.isSafeInteger(value) || value < 1 || value > ENGINE_SCRIPT_LIMITS[key]) {
|
|
36
|
+
throw new EngineScriptError("LIMIT", `Invalid script ceiling: ${key}`);
|
|
37
|
+
}
|
|
38
|
+
limits[key] = value;
|
|
39
|
+
}
|
|
40
|
+
return Object.freeze(limits);
|
|
41
|
+
}
|
|
42
|
+
function sourceCode(source, limits) {
|
|
43
|
+
if (typeof source !== "string" || source.length > limits.sourceLength)
|
|
44
|
+
throw new EngineScriptError("LIMIT", "Script source is too large");
|
|
45
|
+
let code = "", quote = "", depth = 0;
|
|
46
|
+
for (let i = 0; i < source.length; i++) {
|
|
47
|
+
const c = source[i];
|
|
48
|
+
if (quote !== "") {
|
|
49
|
+
if (c === "\\") {
|
|
50
|
+
code += c + (source[++i] ?? "");
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (c === quote)
|
|
54
|
+
quote = "";
|
|
55
|
+
code += c;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (c === '"' || c === "'") {
|
|
59
|
+
quote = c;
|
|
60
|
+
code += c;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (c === "/" && source[i + 1] === "/") {
|
|
64
|
+
while (i < source.length && source[i] !== "\n") {
|
|
65
|
+
code += " ";
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
code += "\n";
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (c === "(" || c === "[") {
|
|
72
|
+
if (++depth > limits.depth)
|
|
73
|
+
throw new EngineScriptError("LIMIT", "Script nesting is too deep");
|
|
74
|
+
} else if (c === ")" || c === "]")
|
|
75
|
+
depth--;
|
|
76
|
+
code += c;
|
|
77
|
+
}
|
|
78
|
+
const tokens = code.match(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[A-Za-z_][\w.]*|[()[\],=]|\S/g) ?? [];
|
|
79
|
+
if (tokens.length > Math.min(4096, limits.nodes * 8))
|
|
80
|
+
throw new EngineScriptError("LIMIT", "Script token budget exceeded");
|
|
81
|
+
const calls = [];
|
|
82
|
+
let nesting = 0, unary = 0;
|
|
83
|
+
const unsupported = /* @__PURE__ */ new Set(["editable", "display", "trackprice", "show_last", "join", "transp", "force_overlay", "format", "precision"]);
|
|
84
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
85
|
+
const token = tokens[i];
|
|
86
|
+
unary = ["+", "-", "!", "not"].includes(token) ? unary + 1 : 0;
|
|
87
|
+
if (unary > limits.depth)
|
|
88
|
+
throw new EngineScriptError("LIMIT", "Script unary nesting is too deep");
|
|
89
|
+
const call = calls[calls.length - 1];
|
|
90
|
+
const styleArgument = call?.name === "plot" && call.depth === nesting && (tokens[i - 1] === "=" && ["style", "type"].includes(tokens[i - 2]) || call.argument === 4 && tokens[i - 1] === ",");
|
|
91
|
+
if (styleArgument && /^plot\.style_(stepline|area|cross)$/.test(token[0] === '"' || token[0] === "'" ? token.slice(1, -1) : token)) {
|
|
92
|
+
throw new EngineScriptError("UNSUPPORTED", "Step, area and cross script plots are not supported");
|
|
93
|
+
}
|
|
94
|
+
if (token === "(") {
|
|
95
|
+
nesting++;
|
|
96
|
+
calls.push({ name: tokens[i - 1] ?? "", depth: nesting, argument: 0 });
|
|
97
|
+
} else if (token === ")") {
|
|
98
|
+
if (calls[calls.length - 1]?.depth === nesting)
|
|
99
|
+
calls.pop();
|
|
100
|
+
nesting--;
|
|
101
|
+
} else if (token === "[")
|
|
102
|
+
nesting++;
|
|
103
|
+
else if (token === "]")
|
|
104
|
+
nesting--;
|
|
105
|
+
else if (token === "," && call?.depth === nesting)
|
|
106
|
+
call.argument++;
|
|
107
|
+
else if (tokens[i + 1] === "=" && calls[calls.length - 1]?.depth === nesting) {
|
|
108
|
+
const name = calls[calls.length - 1].name;
|
|
109
|
+
if (/^(plot|hline)$/.test(name) && unsupported.has(token) || /^(indicator|study)$/.test(name) && token !== "title" && token !== "overlay" || /^input(\.int|\.float)?$/.test(name) && !["title", "minval", "maxval", "step", "defval"].includes(token)) {
|
|
110
|
+
throw new EngineScriptError("UNSUPPORTED", `Script argument is not supported: ${name}.${token}`);
|
|
111
|
+
}
|
|
112
|
+
if (/^(indicator|study)$/.test(name) && token === "overlay" && (!["true", "false", "0", "1"].includes(tokens[i + 2]) || ![",", ")"].includes(tokens[i + 3]))) {
|
|
113
|
+
throw new EngineScriptError("UNSUPPORTED", "Indicator overlay must be a literal boolean");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return code;
|
|
118
|
+
}
|
|
119
|
+
function analyze(program, limits) {
|
|
120
|
+
let nodes = 0, statementIndex = 0;
|
|
121
|
+
const guards = [];
|
|
122
|
+
const visit = (expression, depth) => {
|
|
123
|
+
if (++nodes > limits.nodes || depth > limits.depth)
|
|
124
|
+
throw new EngineScriptError("LIMIT", "Script expression budget exceeded");
|
|
125
|
+
const child = (e) => visit(e, depth + 1);
|
|
126
|
+
switch (expression.k) {
|
|
127
|
+
case "bin":
|
|
128
|
+
child(expression.l);
|
|
129
|
+
child(expression.r);
|
|
130
|
+
break;
|
|
131
|
+
case "tern":
|
|
132
|
+
child(expression.c);
|
|
133
|
+
child(expression.a);
|
|
134
|
+
child(expression.b);
|
|
135
|
+
break;
|
|
136
|
+
case "unary":
|
|
137
|
+
child(expression.arg);
|
|
138
|
+
break;
|
|
139
|
+
case "index":
|
|
140
|
+
guards.push({ expression: expression.offset, label: "history offset", scan: false, statement: statementIndex });
|
|
141
|
+
child(expression.target);
|
|
142
|
+
child(expression.offset);
|
|
143
|
+
break;
|
|
144
|
+
case "call": {
|
|
145
|
+
expression.args.forEach(child);
|
|
146
|
+
expression.kw.forEach((kw) => child(kw.value));
|
|
147
|
+
let name = PINE_ALIASES.get(expression.name) ?? expression.name;
|
|
148
|
+
let args = expression.args;
|
|
149
|
+
if (expression.name === "ta.cci" || expression.name === "ta.mfi") {
|
|
150
|
+
name = expression.name.slice(3);
|
|
151
|
+
args = expression.args.slice(1);
|
|
152
|
+
} else if (expression.name === "ta.stoch") {
|
|
153
|
+
name = "stoch";
|
|
154
|
+
args = expression.args.slice(3);
|
|
155
|
+
}
|
|
156
|
+
const builtin = BUILTINS.get(name);
|
|
157
|
+
builtin?.params.forEach((param, index) => {
|
|
158
|
+
if (param.kind !== "length" && param.kind !== "count")
|
|
159
|
+
return;
|
|
160
|
+
const expressionValue = args[index] ?? expression.kw.find((kw) => (PINE_ARG_NAMES.get(kw.name) ?? kw.name) === param.name)?.value;
|
|
161
|
+
if (expressionValue !== void 0) {
|
|
162
|
+
guards.push({
|
|
163
|
+
expression: expressionValue,
|
|
164
|
+
label: `${name}.${param.name}`,
|
|
165
|
+
statement: statementIndex,
|
|
166
|
+
// Conservative: even amortised rolling algorithms can fall back to
|
|
167
|
+
// window scans after arithmetic overflows in a nested expression.
|
|
168
|
+
scan: param.kind === "length" || name === "pivothigh" || name === "pivotlow"
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
for (const statement of program.statements) {
|
|
177
|
+
visit(statement.value, 1);
|
|
178
|
+
if ((statement.k === "plot" || statement.k === "hline") && statement.lineWidth !== void 0)
|
|
179
|
+
visit(statement.lineWidth, 1);
|
|
180
|
+
if (statement.k === "plot" && statement.histbase !== void 0)
|
|
181
|
+
visit(statement.histbase, 1);
|
|
182
|
+
statementIndex++;
|
|
183
|
+
}
|
|
184
|
+
return { nodes, guards };
|
|
185
|
+
}
|
|
186
|
+
function guardProgram(program, guards) {
|
|
187
|
+
const statements = [], plots = [], windows = [];
|
|
188
|
+
let plotIndex = 0;
|
|
189
|
+
program.statements.forEach((statement, index) => {
|
|
190
|
+
guards.forEach((guard, guardIndex) => {
|
|
191
|
+
if (guard.statement !== index)
|
|
192
|
+
return;
|
|
193
|
+
statements.push({
|
|
194
|
+
k: "plot",
|
|
195
|
+
value: { k: "num", v: 0, text: "0", at: guard.expression.at },
|
|
196
|
+
histbase: guard.expression,
|
|
197
|
+
style: {},
|
|
198
|
+
at: guard.expression.at
|
|
199
|
+
});
|
|
200
|
+
windows[guardIndex] = plotIndex++;
|
|
201
|
+
});
|
|
202
|
+
statements.push(statement);
|
|
203
|
+
if (statement.k === "plot" || statement.k === "hline")
|
|
204
|
+
plots.push(plotIndex++);
|
|
205
|
+
});
|
|
206
|
+
return { program: { ...program, statements }, plots, windows };
|
|
207
|
+
}
|
|
208
|
+
function figuresOf(plots) {
|
|
209
|
+
return Object.freeze(plots.map((plot, index) => {
|
|
210
|
+
const style = plot.style ?? {}, width = style.lineWidth ?? 1.5, base = style.histbase ?? 0;
|
|
211
|
+
if (!Number.isFinite(width) || width <= 0 || width > 32 || !Number.isFinite(base))
|
|
212
|
+
throw new EngineScriptError("RESULT", "Plot width/base must be finite; width must be within (0, 32]");
|
|
213
|
+
const dash = style.lineStyle === "dotted" ? [1, 3] : style.lineStyle === "dashed" || style.lineStyle === void 0 && plot.kind === "hline" ? [4, 4] : [];
|
|
214
|
+
return Object.freeze({
|
|
215
|
+
key: plot.id,
|
|
216
|
+
title: plot.title,
|
|
217
|
+
color: style.color ?? PALETTE[index % PALETTE.length],
|
|
218
|
+
type: style.type === "histogram" ? "bar" : style.type === "circle" ? "circle" : "line",
|
|
219
|
+
lineWidth: width,
|
|
220
|
+
dash: Object.freeze(dash),
|
|
221
|
+
...style.type === "histogram" ? { base } : {}
|
|
222
|
+
});
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
225
|
+
function sameFigures(a, b) {
|
|
226
|
+
return a.length === b.length && a.every((figure, i) => {
|
|
227
|
+
const next = b[i];
|
|
228
|
+
return figure.key === next.key && figure.title === next.title && figure.type === next.type && figure.color === next.color && figure.lineWidth === next.lineWidth && figure.base === next.base && figure.dash?.join(",") === next.dash?.join(",");
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
function normalizeParams(inputs, given, current) {
|
|
232
|
+
if (given !== void 0) {
|
|
233
|
+
if (typeof given !== "object" || given === null)
|
|
234
|
+
throw new EngineScriptError("PARAMS", "Script inputs must be an array or object");
|
|
235
|
+
if (Array.isArray(given) ? given.length > inputs.length : Object.keys(given).some((key) => !inputs.some((input) => input.id === key))) {
|
|
236
|
+
throw new EngineScriptError("PARAMS", "Unknown script input");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return Object.freeze(inputs.map((input, i) => {
|
|
240
|
+
const supplied = Array.isArray(given) ? given[i] : given !== void 0 && Object.prototype.hasOwnProperty.call(given, input.id) ? given[input.id] : void 0;
|
|
241
|
+
let value = supplied == null ? current[i] : supplied;
|
|
242
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
243
|
+
throw new EngineScriptError("PARAMS", `Input must be finite: ${input.id}`);
|
|
244
|
+
if (input.type === "int")
|
|
245
|
+
value = Math.round(value);
|
|
246
|
+
if (input.min !== void 0)
|
|
247
|
+
value = Math.max(input.min, value);
|
|
248
|
+
if (input.max !== void 0)
|
|
249
|
+
value = Math.min(input.max, value);
|
|
250
|
+
return value;
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
function validateBars(bars, limits) {
|
|
254
|
+
if (!Array.isArray(bars) || bars.length > limits.bars)
|
|
255
|
+
throw new EngineScriptError("LIMIT", "Script history exceeds the bar ceiling; history is not silently truncated");
|
|
256
|
+
let previous = -Infinity;
|
|
257
|
+
for (const bar of bars) {
|
|
258
|
+
if (bar == null || ![bar.timestamp, bar.open, bar.high, bar.low, bar.close].every((value) => typeof value === "number" && Number.isFinite(value)) || Math.abs(bar.timestamp) > 864e13 || bar.timestamp <= previous || bar.high < Math.max(bar.open, bar.low, bar.close) || bar.low > Math.min(bar.open, bar.high, bar.close) || bar.volume !== void 0 && (typeof bar.volume !== "number" || !Number.isFinite(bar.volume) || bar.volume < 0)) {
|
|
259
|
+
throw new EngineScriptError("DATA", "Scripts require ascending unique epoch-ms timestamps, finite coherent OHLC and nonnegative reported volume");
|
|
260
|
+
}
|
|
261
|
+
previous = bar.timestamp;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function createEngineScriptIndicator(options) {
|
|
265
|
+
if (typeof options.name !== "string" || options.name.trim() === "")
|
|
266
|
+
throw new TypeError("Script indicator name must not be empty");
|
|
267
|
+
const limits = limitsOf(options.limits);
|
|
268
|
+
sourceCode(options.source, limits);
|
|
269
|
+
let program;
|
|
270
|
+
try {
|
|
271
|
+
program = parse(options.source);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (error instanceof RangeError)
|
|
274
|
+
throw new EngineScriptError("LIMIT", "Script parser recursion budget exceeded");
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
const analysis = analyze(program, limits), checked = check(program);
|
|
278
|
+
if (checked.diagnostics.length > 0)
|
|
279
|
+
throw new SuperScriptError(checked.diagnostics[0], checked.diagnostics);
|
|
280
|
+
if (checked.plots.length === 0 || checked.plots.length > limits.plots || checked.inputs.length > limits.inputs)
|
|
281
|
+
throw new EngineScriptError("LIMIT", "Script plot/input ceiling exceeded");
|
|
282
|
+
const inputs = Object.freeze(checked.inputs.map((input) => {
|
|
283
|
+
if (!Number.isFinite(input.defval) || input.min !== void 0 && !Number.isFinite(input.min) || input.max !== void 0 && !Number.isFinite(input.max) || input.min !== void 0 && input.max !== void 0 && input.min > input.max || input.min !== void 0 && input.defval < input.min || input.max !== void 0 && input.defval > input.max || input.step !== void 0 && (!Number.isFinite(input.step) || input.step <= 0) || input.type === "int" && [input.min, input.max].some((value) => value !== void 0 && !Number.isSafeInteger(value))) {
|
|
284
|
+
throw new EngineScriptError("PARAMS", `Invalid input bounds: ${input.id}`);
|
|
285
|
+
}
|
|
286
|
+
return Object.freeze({ ...input });
|
|
287
|
+
}));
|
|
288
|
+
const initialParams = normalizeParams(inputs, options.params, inputs.map((input) => input.defval));
|
|
289
|
+
const guarded = guardProgram(program, analysis.guards);
|
|
290
|
+
const preflight = (params) => {
|
|
291
|
+
const result = evaluate(guarded.program, [], params);
|
|
292
|
+
const plots = guarded.plots.map((index, i) => ({ ...result.plots[index], id: checked.plots[i].id, title: checked.plots[i].title }));
|
|
293
|
+
if (result.inputs.length !== inputs.length || result.inputs.some((input, i) => {
|
|
294
|
+
const initial = inputs[i];
|
|
295
|
+
return input.id !== initial.id || input.type !== initial.type || input.defval !== initial.defval || input.min !== initial.min || input.max !== initial.max || input.step !== initial.step;
|
|
296
|
+
}))
|
|
297
|
+
throw new EngineScriptError("UNSUPPORTED", "Input declarations depend on other inputs; use fixed defaults and bounds");
|
|
298
|
+
let scan = 0;
|
|
299
|
+
for (let i = 0; i < analysis.guards.length; i++) {
|
|
300
|
+
const guard = analysis.guards[i], value = result.plots[guarded.windows[i]]?.style?.histbase;
|
|
301
|
+
if (value === void 0 || !Number.isSafeInteger(value) || value < 0 || value > limits.window)
|
|
302
|
+
throw new EngineScriptError("LIMIT", `Script ${guard.label} exceeds the window ceiling (${limits.window})`);
|
|
303
|
+
if (guard.scan)
|
|
304
|
+
scan += 2 * value;
|
|
305
|
+
}
|
|
306
|
+
return { figures: figuresOf(plots), scan };
|
|
307
|
+
};
|
|
308
|
+
let cachedParams = initialParams, cached = preflight(initialParams);
|
|
309
|
+
const figures = cached.figures;
|
|
310
|
+
const validated = (params) => {
|
|
311
|
+
if (params.length !== inputs.length)
|
|
312
|
+
throw new EngineScriptError("PARAMS", `Script requires ${inputs.length} inputs`);
|
|
313
|
+
const normalized = normalizeParams(inputs, params, initialParams);
|
|
314
|
+
if (normalized.some((value, i) => value !== cachedParams[i])) {
|
|
315
|
+
const next = preflight(normalized);
|
|
316
|
+
if (!sameFigures(figures, next.figures))
|
|
317
|
+
throw new EngineScriptError("UNSUPPORTED", "These inputs change plot width/base; recompile the script before replacing its definition");
|
|
318
|
+
cached = next;
|
|
319
|
+
cachedParams = normalized;
|
|
320
|
+
}
|
|
321
|
+
return normalized;
|
|
322
|
+
};
|
|
323
|
+
const definition = Object.freeze({
|
|
324
|
+
name: options.name,
|
|
325
|
+
params: initialParams,
|
|
326
|
+
pane: program.overlay ? "price" : "separate",
|
|
327
|
+
figures,
|
|
328
|
+
validateParams: (params) => {
|
|
329
|
+
const normalized = validated(params);
|
|
330
|
+
if (normalized.some((value, i) => value !== params[i]))
|
|
331
|
+
throw new EngineScriptError("PARAMS", "Script inputs must respect their numeric bounds and integer type; attachment.setParams normalizes them");
|
|
332
|
+
},
|
|
333
|
+
calculate: (bars, params) => {
|
|
334
|
+
const actual = validated(params);
|
|
335
|
+
validateBars(bars, limits);
|
|
336
|
+
if (bars.length * (analysis.nodes + figures.length + 10) > limits.seriesCells || bars.length * (analysis.nodes * 8 + cached.scan) > limits.workUnits) {
|
|
337
|
+
throw new EngineScriptError("LIMIT", "Script estimated calculation budget exceeded");
|
|
338
|
+
}
|
|
339
|
+
const result = evaluate(program, bars, actual);
|
|
340
|
+
if (!sameFigures(figures, figuresOf(result.plots)) || result.plots.some((plot) => plot.values.length !== bars.length))
|
|
341
|
+
throw new EngineScriptError("RESULT", "Script results do not match their declared plots/history");
|
|
342
|
+
return bars.map((_bar, index) => {
|
|
343
|
+
const row = /* @__PURE__ */ Object.create(null);
|
|
344
|
+
for (const plot of result.plots) {
|
|
345
|
+
const value = plot.values[index];
|
|
346
|
+
row[plot.id] = typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
347
|
+
}
|
|
348
|
+
return row;
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
let disposed = false;
|
|
353
|
+
const owned = /* @__PURE__ */ new Map();
|
|
354
|
+
return Object.freeze({
|
|
355
|
+
title: program.title,
|
|
356
|
+
definition,
|
|
357
|
+
inputs,
|
|
358
|
+
limits,
|
|
359
|
+
diagnostics: Object.freeze(checked.diagnostics.map((diagnostic) => Object.freeze({ ...diagnostic }))),
|
|
360
|
+
attach: (host, settings = {}) => {
|
|
361
|
+
if (disposed)
|
|
362
|
+
throw new EngineScriptError("DISPOSED", "Script bridge has been disposed");
|
|
363
|
+
if (owned.has(host))
|
|
364
|
+
throw new Error("This script is already attached to this host");
|
|
365
|
+
const params = validated(normalizeParams(inputs, settings.params, initialParams));
|
|
366
|
+
host.registerIndicator(definition);
|
|
367
|
+
let id;
|
|
368
|
+
try {
|
|
369
|
+
id = host.addIndicator({ ...settings, name: definition.name, params });
|
|
370
|
+
} catch (error) {
|
|
371
|
+
try {
|
|
372
|
+
host.unregisterIndicator(definition.name, definition.calculate);
|
|
373
|
+
} catch {
|
|
374
|
+
}
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
let detached = false;
|
|
378
|
+
const attachment = Object.freeze({
|
|
379
|
+
id,
|
|
380
|
+
setParams: (given) => {
|
|
381
|
+
if (detached || disposed)
|
|
382
|
+
throw new EngineScriptError("DISPOSED", "Script attachment has been disposed");
|
|
383
|
+
const current = host.listIndicators().find((instance) => instance.id === id);
|
|
384
|
+
if (current?.name !== definition.name)
|
|
385
|
+
throw new Error("Script instance no longer exists");
|
|
386
|
+
const next = validated(normalizeParams(inputs, given, current.params));
|
|
387
|
+
if (!host.updateIndicator(id, { params: next }))
|
|
388
|
+
throw new Error("Script instance no longer exists");
|
|
389
|
+
return next;
|
|
390
|
+
},
|
|
391
|
+
dispose: () => {
|
|
392
|
+
if (detached)
|
|
393
|
+
return;
|
|
394
|
+
detached = true;
|
|
395
|
+
owned.delete(host);
|
|
396
|
+
let failure, failed = false;
|
|
397
|
+
try {
|
|
398
|
+
if (host.listIndicators().some((instance) => instance.id === id && instance.name === definition.name))
|
|
399
|
+
host.removeIndicator(id);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
failure = error;
|
|
402
|
+
failed = true;
|
|
403
|
+
}
|
|
404
|
+
try {
|
|
405
|
+
host.unregisterIndicator(definition.name, definition.calculate);
|
|
406
|
+
} catch (error) {
|
|
407
|
+
if (!failed) {
|
|
408
|
+
failure = error;
|
|
409
|
+
failed = true;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (failed)
|
|
413
|
+
throw failure;
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
owned.set(host, attachment);
|
|
417
|
+
return attachment;
|
|
418
|
+
},
|
|
419
|
+
dispose: () => {
|
|
420
|
+
if (disposed)
|
|
421
|
+
return;
|
|
422
|
+
disposed = true;
|
|
423
|
+
let failure, failed = false;
|
|
424
|
+
for (const attachment of [...owned.values()]) {
|
|
425
|
+
try {
|
|
426
|
+
attachment.dispose();
|
|
427
|
+
} catch (error) {
|
|
428
|
+
if (!failed) {
|
|
429
|
+
failure = error;
|
|
430
|
+
failed = true;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (failed)
|
|
435
|
+
throw failure;
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
export {
|
|
440
|
+
ENGINE_SCRIPT_LIMITS,
|
|
441
|
+
EngineScriptError,
|
|
442
|
+
createEngineScriptIndicator
|
|
443
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supersolutions/super-chart-superscript",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SuperScript language runtime, AI helpers and the script-to-indicator bridge for Super Chart. Runs in plain Node.",
|
|
5
|
+
"license": "GPL-3.0-or-later",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/Super-Solution/super-chart.git",
|
|
13
|
+
"directory": "packages/superscript"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/Super-Solution/super-chart#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/Super-Solution/super-chart/issues"
|
|
18
|
+
},
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./scripts": {
|
|
31
|
+
"types": "./dist/scripts.d.ts",
|
|
32
|
+
"import": "./dist/scripts.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"typesVersions": {
|
|
36
|
+
"*": {
|
|
37
|
+
"scripts": [
|
|
38
|
+
"./dist/scripts.d.ts"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"main": "./dist/index.js",
|
|
43
|
+
"types": "./dist/index.d.ts",
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"registry": "https://registry.npmjs.org/",
|
|
47
|
+
"tag": "latest"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@supersolutions/super-chart-core": "0.1.0"
|
|
51
|
+
},
|
|
52
|
+
"author": "SUPER SOLUTION LABS INC"
|
|
53
|
+
}
|