All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 23m17s
- wasm_of_ocaml compiles OCaml SX engine to WASM (722/722 spec tests) - js_of_ocaml fallback also working (722/722 spec tests) - Thin JS platform layer (sx-platform.js) with ~80 DOM/browser natives - Lambda callback bridge: SX lambdas callable from JS via handle table - Side-channel pattern bypasses js_of_ocaml return-value property stripping - Web adapters (signals, deps, router, adapter-html) load as SX source - Render mode dispatch: HTML tags + fragments route to OCaml renderer - Island/component accessors handle both Component and Island types - Dict-based signal support (signals.sx creates dicts, not native Signal) - Scope stack implementation (collect!/collected/emit!/emitted/context) - Bundle script embeds web adapters + WASM loader + platform layer - SX_USE_WASM env var toggles WASM engine in dev/production - Bootstrap extended: --web flag transpiles web adapters, :effects stripping Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
677 lines
24 KiB
JavaScript
677 lines
24 KiB
JavaScript
/**
|
|
* sx-platform.js — Thin JS platform layer for the OCaml SX WASM engine.
|
|
*
|
|
* This file provides browser-native primitives (DOM, fetch, timers, etc.)
|
|
* to the WASM-compiled OCaml CEK machine. It:
|
|
* 1. Loads the WASM module (SxKernel)
|
|
* 2. Registers ~80 native browser functions via registerNative
|
|
* 3. Loads web adapters (.sx files) into the engine
|
|
* 4. Exports the public Sx API
|
|
*
|
|
* Both wasm_of_ocaml and js_of_ocaml targets bind to this same layer.
|
|
*/
|
|
|
|
(function(global) {
|
|
"use strict";
|
|
|
|
function initPlatform() {
|
|
var K = global.SxKernel;
|
|
if (!K) {
|
|
// WASM loader is async — wait and retry
|
|
setTimeout(initPlatform, 20);
|
|
return;
|
|
}
|
|
|
|
var _hasDom = typeof document !== "undefined";
|
|
var NIL = null;
|
|
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
|
|
// =========================================================================
|
|
// Helper: wrap SX lambda for use as JS callback
|
|
// =========================================================================
|
|
|
|
function wrapLambda(fn) {
|
|
// For now, SX lambdas from registerNative are opaque — we can't call them
|
|
// directly from JS. They need to go through the engine.
|
|
// TODO: add callLambda API to SxKernel
|
|
return fn;
|
|
}
|
|
|
|
// =========================================================================
|
|
// 1. DOM Creation & Manipulation
|
|
// =========================================================================
|
|
|
|
K.registerNative("dom-create-element", function(args) {
|
|
if (!_hasDom) return NIL;
|
|
var tag = args[0], ns = args[1];
|
|
if (ns && ns !== NIL) return document.createElementNS(ns, tag);
|
|
return document.createElement(tag);
|
|
});
|
|
|
|
K.registerNative("create-text-node", function(args) {
|
|
return _hasDom ? document.createTextNode(args[0] || "") : NIL;
|
|
});
|
|
|
|
K.registerNative("create-comment", function(args) {
|
|
return _hasDom ? document.createComment(args[0] || "") : NIL;
|
|
});
|
|
|
|
K.registerNative("create-fragment", function(_args) {
|
|
return _hasDom ? document.createDocumentFragment() : NIL;
|
|
});
|
|
|
|
K.registerNative("dom-clone", function(args) {
|
|
var node = args[0];
|
|
return node && node.cloneNode ? node.cloneNode(true) : node;
|
|
});
|
|
|
|
K.registerNative("dom-parse-html", function(args) {
|
|
if (!_hasDom) return NIL;
|
|
var tpl = document.createElement("template");
|
|
tpl.innerHTML = args[0] || "";
|
|
return tpl.content;
|
|
});
|
|
|
|
K.registerNative("dom-parse-html-document", function(args) {
|
|
if (!_hasDom) return NIL;
|
|
var parser = new DOMParser();
|
|
return parser.parseFromString(args[0] || "", "text/html");
|
|
});
|
|
|
|
// =========================================================================
|
|
// 2. DOM Queries
|
|
// =========================================================================
|
|
|
|
K.registerNative("dom-query", function(args) {
|
|
return _hasDom ? document.querySelector(args[0]) || NIL : NIL;
|
|
});
|
|
|
|
K.registerNative("dom-query-all", function(args) {
|
|
var root = args[0] || (_hasDom ? document : null);
|
|
if (!root || !root.querySelectorAll) return [];
|
|
return Array.prototype.slice.call(root.querySelectorAll(args[1] || args[0]));
|
|
});
|
|
|
|
K.registerNative("dom-query-by-id", function(args) {
|
|
return _hasDom ? document.getElementById(args[0]) || NIL : NIL;
|
|
});
|
|
|
|
K.registerNative("dom-body", function(_args) {
|
|
return _hasDom ? document.body : NIL;
|
|
});
|
|
|
|
K.registerNative("dom-ensure-element", function(args) {
|
|
if (!_hasDom) return NIL;
|
|
var sel = args[0];
|
|
var el = document.querySelector(sel);
|
|
if (el) return el;
|
|
if (sel.charAt(0) === "#") {
|
|
el = document.createElement("div");
|
|
el.id = sel.slice(1);
|
|
document.body.appendChild(el);
|
|
return el;
|
|
}
|
|
return NIL;
|
|
});
|
|
|
|
// =========================================================================
|
|
// 3. DOM Attributes
|
|
// =========================================================================
|
|
|
|
K.registerNative("dom-get-attr", function(args) {
|
|
var el = args[0], name = args[1];
|
|
if (!el || !el.getAttribute) return NIL;
|
|
var v = el.getAttribute(name);
|
|
return v === null ? NIL : v;
|
|
});
|
|
|
|
K.registerNative("dom-set-attr", function(args) {
|
|
var el = args[0], name = args[1], val = args[2];
|
|
if (el && el.setAttribute) el.setAttribute(name, val);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-remove-attr", function(args) {
|
|
if (args[0] && args[0].removeAttribute) args[0].removeAttribute(args[1]);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-has-attr?", function(args) {
|
|
return !!(args[0] && args[0].hasAttribute && args[0].hasAttribute(args[1]));
|
|
});
|
|
|
|
K.registerNative("dom-attr-list", function(args) {
|
|
var el = args[0];
|
|
if (!el || !el.attributes) return [];
|
|
var r = [];
|
|
for (var i = 0; i < el.attributes.length; i++) {
|
|
r.push([el.attributes[i].name, el.attributes[i].value]);
|
|
}
|
|
return r;
|
|
});
|
|
|
|
// =========================================================================
|
|
// 4. DOM Content
|
|
// =========================================================================
|
|
|
|
K.registerNative("dom-text-content", function(args) {
|
|
var el = args[0];
|
|
return el ? el.textContent || el.nodeValue || "" : "";
|
|
});
|
|
|
|
K.registerNative("dom-set-text-content", function(args) {
|
|
var el = args[0], s = args[1];
|
|
if (el) {
|
|
if (el.nodeType === 3 || el.nodeType === 8) el.nodeValue = s;
|
|
else el.textContent = s;
|
|
}
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-inner-html", function(args) {
|
|
return args[0] && args[0].innerHTML != null ? args[0].innerHTML : "";
|
|
});
|
|
|
|
K.registerNative("dom-set-inner-html", function(args) {
|
|
if (args[0]) args[0].innerHTML = args[1] || "";
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-insert-adjacent-html", function(args) {
|
|
var el = args[0], pos = args[1], html = args[2];
|
|
if (el && el.insertAdjacentHTML) el.insertAdjacentHTML(pos, html);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-body-inner-html", function(args) {
|
|
var doc = args[0];
|
|
return doc && doc.body ? doc.body.innerHTML : "";
|
|
});
|
|
|
|
// =========================================================================
|
|
// 5. DOM Structure & Navigation
|
|
// =========================================================================
|
|
|
|
K.registerNative("dom-parent", function(args) { return args[0] ? args[0].parentNode || NIL : NIL; });
|
|
K.registerNative("dom-first-child", function(args) { return args[0] ? args[0].firstChild || NIL : NIL; });
|
|
K.registerNative("dom-next-sibling", function(args) { return args[0] ? args[0].nextSibling || NIL : NIL; });
|
|
K.registerNative("dom-id", function(args) { return args[0] && args[0].id ? args[0].id : NIL; });
|
|
K.registerNative("dom-node-type", function(args) { return args[0] ? args[0].nodeType : 0; });
|
|
K.registerNative("dom-node-name", function(args) { return args[0] ? args[0].nodeName : ""; });
|
|
K.registerNative("dom-tag-name", function(args) { return args[0] && args[0].tagName ? args[0].tagName : ""; });
|
|
|
|
K.registerNative("dom-child-list", function(args) {
|
|
var el = args[0];
|
|
if (!el || !el.childNodes) return [];
|
|
return Array.prototype.slice.call(el.childNodes);
|
|
});
|
|
|
|
K.registerNative("dom-child-nodes", function(args) {
|
|
var el = args[0];
|
|
if (!el || !el.childNodes) return [];
|
|
return Array.prototype.slice.call(el.childNodes);
|
|
});
|
|
|
|
// =========================================================================
|
|
// 6. DOM Insertion & Removal
|
|
// =========================================================================
|
|
|
|
K.registerNative("dom-append", function(args) {
|
|
var parent = args[0], child = args[1];
|
|
if (parent && child) parent.appendChild(child);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-prepend", function(args) {
|
|
var parent = args[0], child = args[1];
|
|
if (parent && child) parent.insertBefore(child, parent.firstChild);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-insert-before", function(args) {
|
|
var parent = args[0], node = args[1], ref = args[2];
|
|
if (parent && node) parent.insertBefore(node, ref || null);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-insert-after", function(args) {
|
|
var ref = args[0], node = args[1];
|
|
if (ref && ref.parentNode && node) {
|
|
ref.parentNode.insertBefore(node, ref.nextSibling);
|
|
}
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-remove", function(args) {
|
|
var node = args[0];
|
|
if (node && node.parentNode) node.parentNode.removeChild(node);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-remove-child", function(args) {
|
|
var parent = args[0], child = args[1];
|
|
if (parent && child && child.parentNode === parent) parent.removeChild(child);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-replace-child", function(args) {
|
|
var parent = args[0], newC = args[1], oldC = args[2];
|
|
if (parent && newC && oldC) parent.replaceChild(newC, oldC);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-remove-children-after", function(args) {
|
|
var marker = args[0];
|
|
if (!marker || !marker.parentNode) return NIL;
|
|
var parent = marker.parentNode;
|
|
while (marker.nextSibling) parent.removeChild(marker.nextSibling);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-append-to-head", function(args) {
|
|
if (_hasDom && args[0]) document.head.appendChild(args[0]);
|
|
return NIL;
|
|
});
|
|
|
|
// =========================================================================
|
|
// 7. DOM Type Checks
|
|
// =========================================================================
|
|
|
|
K.registerNative("dom-is-fragment?", function(args) { return args[0] ? args[0].nodeType === 11 : false; });
|
|
K.registerNative("dom-is-child-of?", function(args) { return !!(args[1] && args[0] && args[0].parentNode === args[1]); });
|
|
K.registerNative("dom-is-active-element?", function(args) { return _hasDom && args[0] === document.activeElement; });
|
|
K.registerNative("dom-is-input-element?", function(args) {
|
|
if (!args[0] || !args[0].tagName) return false;
|
|
var t = args[0].tagName;
|
|
return t === "INPUT" || t === "TEXTAREA" || t === "SELECT";
|
|
});
|
|
|
|
// =========================================================================
|
|
// 8. DOM Styles & Classes
|
|
// =========================================================================
|
|
|
|
K.registerNative("dom-get-style", function(args) {
|
|
return args[0] && args[0].style ? args[0].style[args[1]] || "" : "";
|
|
});
|
|
|
|
K.registerNative("dom-set-style", function(args) {
|
|
if (args[0] && args[0].style) args[0].style[args[1]] = args[2];
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-add-class", function(args) {
|
|
if (args[0] && args[0].classList) args[0].classList.add(args[1]);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-remove-class", function(args) {
|
|
if (args[0] && args[0].classList) args[0].classList.remove(args[1]);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-has-class?", function(args) {
|
|
return !!(args[0] && args[0].classList && args[0].classList.contains(args[1]));
|
|
});
|
|
|
|
// =========================================================================
|
|
// 9. DOM Properties & Data
|
|
// =========================================================================
|
|
|
|
K.registerNative("dom-get-prop", function(args) { return args[0] ? args[0][args[1]] : NIL; });
|
|
K.registerNative("dom-set-prop", function(args) { if (args[0]) args[0][args[1]] = args[2]; return NIL; });
|
|
|
|
K.registerNative("dom-set-data", function(args) {
|
|
var el = args[0], key = args[1], val = args[2];
|
|
if (el) { if (!el._sxData) el._sxData = {}; el._sxData[key] = val; }
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("dom-get-data", function(args) {
|
|
var el = args[0], key = args[1];
|
|
return (el && el._sxData) ? (el._sxData[key] != null ? el._sxData[key] : NIL) : NIL;
|
|
});
|
|
|
|
K.registerNative("dom-call-method", function(args) {
|
|
var obj = args[0], method = args[1];
|
|
var callArgs = args.slice(2);
|
|
if (obj && typeof obj[method] === "function") {
|
|
try { return obj[method].apply(obj, callArgs); }
|
|
catch(e) { return NIL; }
|
|
}
|
|
return NIL;
|
|
});
|
|
|
|
// =========================================================================
|
|
// 10. DOM Events
|
|
// =========================================================================
|
|
|
|
K.registerNative("dom-listen", function(args) {
|
|
var el = args[0], name = args[1], handler = args[2];
|
|
if (!_hasDom || !el) return function() {};
|
|
|
|
// handler is a wrapped SX lambda (JS function with __sx_handle).
|
|
// Wrap it to:
|
|
// - Pass the event object as arg (or no args for 0-arity handlers)
|
|
// - Catch errors from the CEK machine
|
|
var arity = K.fnArity(handler);
|
|
var wrapped;
|
|
if (arity === 0) {
|
|
wrapped = function(_e) {
|
|
try { K.callFn(handler, []); }
|
|
catch(err) { console.error("[sx] event handler error:", name, err); }
|
|
};
|
|
} else {
|
|
wrapped = function(e) {
|
|
try { K.callFn(handler, [e]); }
|
|
catch(err) { console.error("[sx] event handler error:", name, err); }
|
|
};
|
|
}
|
|
el.addEventListener(name, wrapped);
|
|
return function() { el.removeEventListener(name, wrapped); };
|
|
});
|
|
|
|
K.registerNative("dom-dispatch", function(args) {
|
|
if (!_hasDom || !args[0]) return false;
|
|
var evt = new CustomEvent(args[1], { bubbles: true, cancelable: true, detail: args[2] || {} });
|
|
return args[0].dispatchEvent(evt);
|
|
});
|
|
|
|
K.registerNative("event-detail", function(args) {
|
|
return (args[0] && args[0].detail != null) ? args[0].detail : NIL;
|
|
});
|
|
|
|
// =========================================================================
|
|
// 11. Browser Navigation & History
|
|
// =========================================================================
|
|
|
|
K.registerNative("browser-location-href", function(_args) {
|
|
return typeof location !== "undefined" ? location.href : "";
|
|
});
|
|
|
|
K.registerNative("browser-same-origin?", function(args) {
|
|
try { return new URL(args[0], location.href).origin === location.origin; }
|
|
catch (e) { return true; }
|
|
});
|
|
|
|
K.registerNative("browser-push-state", function(args) {
|
|
if (typeof history !== "undefined") {
|
|
try { history.pushState({ sxUrl: args[0], scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", args[0]); }
|
|
catch (e) {}
|
|
}
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("browser-replace-state", function(args) {
|
|
if (typeof history !== "undefined") {
|
|
try { history.replaceState({ sxUrl: args[0], scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", args[0]); }
|
|
catch (e) {}
|
|
}
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("browser-navigate", function(args) {
|
|
if (typeof location !== "undefined") location.assign(args[0]);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("browser-reload", function(_args) {
|
|
if (typeof location !== "undefined") location.reload();
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("browser-scroll-to", function(args) {
|
|
if (typeof window !== "undefined") window.scrollTo(args[0] || 0, args[1] || 0);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("browser-media-matches?", function(args) {
|
|
if (typeof window === "undefined") return false;
|
|
return window.matchMedia(args[0]).matches;
|
|
});
|
|
|
|
K.registerNative("browser-confirm", function(args) {
|
|
if (typeof window === "undefined") return false;
|
|
return window.confirm(args[0]);
|
|
});
|
|
|
|
K.registerNative("browser-prompt", function(args) {
|
|
if (typeof window === "undefined") return NIL;
|
|
var r = window.prompt(args[0]);
|
|
return r === null ? NIL : r;
|
|
});
|
|
|
|
// =========================================================================
|
|
// 12. Timers
|
|
// =========================================================================
|
|
|
|
K.registerNative("set-timeout", function(args) {
|
|
var fn = args[0], ms = args[1] || 0;
|
|
var cb = (typeof fn === "function" && fn.__sx_handle != null)
|
|
? function() { try { K.callFn(fn, []); } catch(e) { console.error("[sx] timeout error:", e); } }
|
|
: fn;
|
|
return setTimeout(cb, ms);
|
|
});
|
|
|
|
K.registerNative("set-interval", function(args) {
|
|
var fn = args[0], ms = args[1] || 1000;
|
|
var cb = (typeof fn === "function" && fn.__sx_handle != null)
|
|
? function() { try { K.callFn(fn, []); } catch(e) { console.error("[sx] interval error:", e); } }
|
|
: fn;
|
|
return setInterval(cb, ms);
|
|
});
|
|
|
|
K.registerNative("clear-timeout", function(args) { clearTimeout(args[0]); return NIL; });
|
|
K.registerNative("clear-interval", function(args) { clearInterval(args[0]); return NIL; });
|
|
K.registerNative("now-ms", function(_args) {
|
|
return (typeof performance !== "undefined") ? performance.now() : Date.now();
|
|
});
|
|
|
|
K.registerNative("request-animation-frame", function(args) {
|
|
var fn = args[0];
|
|
var cb = (typeof fn === "function" && fn.__sx_handle != null)
|
|
? function() { try { K.callFn(fn, []); } catch(e) { console.error("[sx] raf error:", e); } }
|
|
: fn;
|
|
if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(cb);
|
|
else setTimeout(cb, 16);
|
|
return NIL;
|
|
});
|
|
|
|
// =========================================================================
|
|
// 13. Promises
|
|
// =========================================================================
|
|
|
|
K.registerNative("promise-resolve", function(args) { return Promise.resolve(args[0]); });
|
|
|
|
K.registerNative("promise-then", function(args) {
|
|
var p = args[0];
|
|
if (!p || !p.then) return p;
|
|
var onResolve = function(v) { return K.callFn(args[1], [v]); };
|
|
var onReject = args[2] ? function(e) { return K.callFn(args[2], [e]); } : undefined;
|
|
return onReject ? p.then(onResolve, onReject) : p.then(onResolve);
|
|
});
|
|
|
|
K.registerNative("promise-catch", function(args) {
|
|
if (!args[0] || !args[0].catch) return args[0];
|
|
return args[0].catch(function(e) { return K.callFn(args[1], [e]); });
|
|
});
|
|
|
|
K.registerNative("promise-delayed", function(args) {
|
|
return new Promise(function(resolve) {
|
|
setTimeout(function() { resolve(args[1]); }, args[0]);
|
|
});
|
|
});
|
|
|
|
// =========================================================================
|
|
// 14. Abort Controllers
|
|
// =========================================================================
|
|
|
|
var _controllers = typeof WeakMap !== "undefined" ? new WeakMap() : null;
|
|
var _targetControllers = typeof WeakMap !== "undefined" ? new WeakMap() : null;
|
|
|
|
K.registerNative("new-abort-controller", function(_args) {
|
|
return typeof AbortController !== "undefined" ? new AbortController() : { signal: null, abort: function() {} };
|
|
});
|
|
|
|
K.registerNative("abort-previous", function(args) {
|
|
if (_controllers) { var prev = _controllers.get(args[0]); if (prev) prev.abort(); }
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("track-controller", function(args) {
|
|
if (_controllers) _controllers.set(args[0], args[1]);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("abort-previous-target", function(args) {
|
|
if (_targetControllers) { var prev = _targetControllers.get(args[0]); if (prev) prev.abort(); }
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("track-controller-target", function(args) {
|
|
if (_targetControllers) _targetControllers.set(args[0], args[1]);
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("controller-signal", function(args) { return args[0] ? args[0].signal : NIL; });
|
|
K.registerNative("is-abort-error", function(args) { return args[0] && args[0].name === "AbortError"; });
|
|
|
|
// =========================================================================
|
|
// 15. Fetch
|
|
// =========================================================================
|
|
|
|
K.registerNative("fetch-request", function(args) {
|
|
var config = args[0], successFn = args[1], errorFn = args[2];
|
|
var opts = { method: config.method, headers: config.headers };
|
|
if (config.signal) opts.signal = config.signal;
|
|
if (config.body && config.method !== "GET") opts.body = config.body;
|
|
if (config["cross-origin"]) opts.credentials = "include";
|
|
|
|
return fetch(config.url, opts).then(function(resp) {
|
|
return resp.text().then(function(text) {
|
|
var getHeader = function(name) {
|
|
var v = resp.headers.get(name);
|
|
return v === null ? NIL : v;
|
|
};
|
|
return K.callFn(successFn, [resp.ok, resp.status, getHeader, text]);
|
|
});
|
|
}).catch(function(err) {
|
|
return K.callFn(errorFn, [err]);
|
|
});
|
|
});
|
|
|
|
K.registerNative("csrf-token", function(_args) {
|
|
if (!_hasDom) return NIL;
|
|
var m = document.querySelector('meta[name="csrf-token"]');
|
|
return m ? m.getAttribute("content") : NIL;
|
|
});
|
|
|
|
K.registerNative("is-cross-origin", function(args) {
|
|
try {
|
|
var h = new URL(args[0], location.href).hostname;
|
|
return h !== location.hostname &&
|
|
(h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0);
|
|
} catch (e) { return false; }
|
|
});
|
|
|
|
// =========================================================================
|
|
// 16. localStorage
|
|
// =========================================================================
|
|
|
|
K.registerNative("local-storage-get", function(args) {
|
|
try { var v = localStorage.getItem(args[0]); return v === null ? NIL : v; }
|
|
catch(e) { return NIL; }
|
|
});
|
|
|
|
K.registerNative("local-storage-set", function(args) {
|
|
try { localStorage.setItem(args[0], args[1]); } catch(e) {}
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("local-storage-remove", function(args) {
|
|
try { localStorage.removeItem(args[0]); } catch(e) {}
|
|
return NIL;
|
|
});
|
|
|
|
// =========================================================================
|
|
// 17. Document Head & Title
|
|
// =========================================================================
|
|
|
|
K.registerNative("set-document-title", function(args) {
|
|
if (_hasDom) document.title = args[0] || "";
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("remove-head-element", function(args) {
|
|
if (_hasDom) {
|
|
var el = document.head.querySelector(args[0]);
|
|
if (el) el.remove();
|
|
}
|
|
return NIL;
|
|
});
|
|
|
|
// =========================================================================
|
|
// 18. Logging
|
|
// =========================================================================
|
|
|
|
K.registerNative("log-info", function(args) { console.log("[sx]", args[0]); return NIL; });
|
|
K.registerNative("log-warn", function(args) { console.warn("[sx]", args[0]); return NIL; });
|
|
K.registerNative("log-error", function(args) { console.error("[sx]", args[0]); return NIL; });
|
|
|
|
// =========================================================================
|
|
// 19. JSON
|
|
// =========================================================================
|
|
|
|
K.registerNative("json-parse", function(args) {
|
|
try { return JSON.parse(args[0]); } catch(e) { return {}; }
|
|
});
|
|
|
|
K.registerNative("try-parse-json", function(args) {
|
|
try { return JSON.parse(args[0]); } catch(e) { return NIL; }
|
|
});
|
|
|
|
// =========================================================================
|
|
// 20. Processing markers
|
|
// =========================================================================
|
|
|
|
K.registerNative("mark-processed!", function(args) {
|
|
var el = args[0], key = args[1] || "sx";
|
|
if (el) { if (!el._sxProcessed) el._sxProcessed = {}; el._sxProcessed[key] = true; }
|
|
return NIL;
|
|
});
|
|
|
|
K.registerNative("is-processed?", function(args) {
|
|
var el = args[0], key = args[1] || "sx";
|
|
return !!(el && el._sxProcessed && el._sxProcessed[key]);
|
|
});
|
|
|
|
// =========================================================================
|
|
// Public Sx API (wraps SxKernel for compatibility with existing code)
|
|
// =========================================================================
|
|
|
|
var Sx = {
|
|
// Core (delegated to WASM engine)
|
|
parse: K.parse,
|
|
eval: K.eval,
|
|
evalExpr: K.evalExpr,
|
|
load: K.load,
|
|
loadSource: K.loadSource,
|
|
renderToHtml: K.renderToHtml,
|
|
typeOf: K.typeOf,
|
|
inspect: K.inspect,
|
|
engine: K.engine,
|
|
|
|
// Will be populated after web adapters load:
|
|
// mount, hydrate, processElements, etc.
|
|
};
|
|
|
|
global.Sx = Sx;
|
|
global.SxKernel = K; // Keep kernel available for direct access
|
|
|
|
console.log("[sx-platform] registered, engine:", K.engine());
|
|
|
|
} // end initPlatform
|
|
|
|
initPlatform();
|
|
|
|
})(typeof globalThis !== "undefined" ? globalThis : this);
|