* @brief Compiles per-dataset JavaScript transforms into a shared QJSEngine; code is IIFE-wrapped * for isolation. */
| 2134 | * for isolation. |
| 2135 | */ |
| 2136 | void DataModel::FrameBuilder::compileTransformsJS(TransformEngine& engine, |
| 2137 | int sourceId, |
| 2138 | const std::vector<TransformEntry>& entries) |
| 2139 | { |
| 2140 | auto* js = new QJSEngine(); |
| 2141 | |
| 2142 | DataModel::ScriptApiCall::installAll(js, sourceId); |
| 2143 | |
| 2144 | for (const auto& entry : entries) { |
| 2145 | const QString wrapped = |
| 2146 | QStringLiteral("(function() {%1\n" |
| 2147 | ";return (typeof transform === 'function') ? transform : null;\n" |
| 2148 | "})();") |
| 2149 | .arg(entry.code); |
| 2150 | |
| 2151 | auto evalResult = js->evaluate(wrapped); |
| 2152 | if (evalResult.isError()) { |
| 2153 | qWarning() << "[FrameBuilder] Transform compile error for" |
| 2154 | << "dataset" << entry.uniqueId << "at line" |
| 2155 | << evalResult.property("lineNumber").toInt() << ":" |
| 2156 | << evalResult.property("message").toString(); |
| 2157 | continue; |
| 2158 | } |
| 2159 | |
| 2160 | if (!evalResult.isCallable()) { |
| 2161 | qWarning() << "[FrameBuilder] Dataset" << entry.uniqueId |
| 2162 | << "transform code does not define transform()"; |
| 2163 | continue; |
| 2164 | } |
| 2165 | |
| 2166 | const bool acceptsInfo = (evalResult.property(QStringLiteral("length")).toInt() >= 2); |
| 2167 | engine.jsRefs[entry.uniqueId] = JsTransformRef{evalResult, acceptsInfo}; |
| 2168 | } |
| 2169 | |
| 2170 | engine.jsEngine = js; |
| 2171 | engine.jsWatchdog = |
| 2172 | std::make_unique<JsWatchdog>(js, kTransformWatchdogMs, QStringLiteral("transform")); |
| 2173 | } |
| 2174 | |
| 2175 | /** |
| 2176 | * @brief Runs one GC pass over every per-source transform engine. |