/** * Engine-agnostic contracts for the user-scripting feature ("اسکریپت شخصی"). * * IScriptEngine is the ONLY seam the rest of the app knows about; the concrete * engine (PineTS today) lives behind it and may be swapped without touching * the sandbox protocol, the host study, or the editor. * * Licensing note: the PineTS adapter (pinets-engine.ts) is the single module * allowed to import `pinets`, and it is only ever bundled into the sandbox * Web-Worker chunk — keeping the AGPL-covered code in a separately-served, * message-passing artifact. See README-user-scripts.md. */ export type Bar = { time: number; // ms since epoch (bar open time) open: number; high: number; low: number; close: number; volume: number; }; export type ScriptInputType = "int" | "float" | "bool" | "string" | "source" | "timeframe"; export type ScriptInputMeta = { /** stable id used as the inputs-record key at run time (Pine varId) */ id: string; type: ScriptInputType; title: string; defval: unknown; min?: number; max?: number; step?: number; options?: unknown[]; }; export type ScriptPlotKind = "line" | "columns" | "shapes" | "bg"; export type ScriptPlotMeta = { /** stable id — key of RunResult.plots (equals the Pine plot title) */ id: string; title: string; kind: ScriptPlotKind; /** static color if the script uses one (hex), undefined for dynamic colors */ color?: string; }; export type ScriptAlertMeta = { id: string; title: string }; export type CompileMeta = { title: string; overlay: boolean; inputs: ScriptInputMeta[]; plots: ScriptPlotMeta[]; alerts: ScriptAlertMeta[]; }; export type CompiledScript = { meta: CompileMeta; /** engine-private handle; opaque to every other module */ handle: unknown; }; export type RunResult = { /** plot id → per-bar values aligned 1:1 with the `bars` argument (NaN = not computed) */ plots: Record; /** alert id → per-bar 0/1 series (only ids from meta.alerts) */ alerts: Record; warnings: string[]; }; export type EngineCapabilities = { /** true if the engine can consume only new/updated bars while keeping state */ incremental: boolean; }; export interface IScriptEngine { readonly name: string; readonly version: string; readonly capabilities: EngineCapabilities; /** Parse + validate the source. Throws ScriptCompileError on failure. */ compile(source: string): Promise; /** Full-history run over `bars` with user input values (keyed by ScriptInputMeta.id). */ run(compiled: CompiledScript, bars: Bar[], inputs: Record): Promise; /** Optional — only when capabilities.incremental. `bars` = new/updated tail bars. */ runIncremental?(compiled: CompiledScript, bars: Bar[], inputs: Record): Promise; } /** Compile failure with optional 1-based source line, message is user-facing (Persian). */ export class ScriptCompileError extends Error { constructor( message: string, public readonly line?: number, ) { super(message); this.name = "ScriptCompileError"; } }