@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object . freeze ( { _nil : true , toString : function ( ) { return "nil" ; } } ) ;
var SX _VERSION = "2026-03-07T02:10:28 Z" ;
var SX _VERSION = "2026-03-07T09:51:42 Z" ;
function isNil ( x ) { return x === NIL || x === null || x === undefined ; }
function isSxTruthy ( x ) { return x !== false && ! isNil ( x ) ; }
@@ -565,92 +565,6 @@
return makeThunk ( componentBody ( comp ) , local ) ;
} ;
// =========================================================================
// Platform: deps module — component dependency analysis
// =========================================================================
function componentDeps ( c ) {
return c . deps ? c . deps . slice ( ) : [ ] ;
}
function componentSetDeps ( c , deps ) {
c . deps = deps ;
}
function componentCssClasses ( c ) {
return c . cssClasses ? c . cssClasses . slice ( ) : [ ] ;
}
function envComponents ( env ) {
var names = [ ] ;
for ( var k in env ) {
var v = env [ k ] ;
if ( v && ( v . _component || v . _macro ) ) names . push ( k ) ;
}
return names ;
}
function regexFindAll ( pattern , source ) {
var re = new RegExp ( pattern , "g" ) ;
var results = [ ] ;
var m ;
while ( ( m = re . exec ( source ) ) !== null ) {
if ( m [ 1 ] !== undefined ) results . push ( m [ 1 ] ) ;
else results . push ( m [ 0 ] ) ;
}
return results ;
}
function scanCssClasses ( source ) {
var classes = { } ;
var result = [ ] ;
var m ;
var re1 = /:class\s+"([^"]*)"/g ;
while ( ( m = re1 . exec ( source ) ) !== null ) {
var parts = m [ 1 ] . split ( /\s+/ ) ;
for ( var i = 0 ; i < parts . length ; i ++ ) {
if ( parts [ i ] && ! classes [ parts [ i ] ] ) {
classes [ parts [ i ] ] = true ;
result . push ( parts [ i ] ) ;
}
}
}
var re2 = /:class\s+\(str\s+((?:"[^"]*"\s*)+)\)/g ;
while ( ( m = re2 . exec ( source ) ) !== null ) {
var re3 = /"([^"]*)"/g ;
var m2 ;
while ( ( m2 = re3 . exec ( m [ 1 ] ) ) !== null ) {
var parts2 = m2 [ 1 ] . split ( /\s+/ ) ;
for ( var j = 0 ; j < parts2 . length ; j ++ ) {
if ( parts2 [ j ] && ! classes [ parts2 [ j ] ] ) {
classes [ parts2 [ j ] ] = true ;
result . push ( parts2 [ j ] ) ;
}
}
}
}
var re4 = /;;\s*@css\s+(.+)/g ;
while ( ( m = re4 . exec ( source ) ) !== null ) {
var parts3 = m [ 1 ] . split ( /\s+/ ) ;
for ( var k = 0 ; k < parts3 . length ; k ++ ) {
if ( parts3 [ k ] && ! classes [ parts3 [ k ] ] ) {
classes [ parts3 [ k ] ] = true ;
result . push ( parts3 [ k ] ) ;
}
}
}
return result ;
}
function componentIoRefs ( c ) {
return c . ioRefs ? c . ioRefs . slice ( ) : [ ] ;
}
function componentSetIoRefs ( c , refs ) {
c . ioRefs = refs ;
}
// =========================================================================
// Platform interface — Parser
// =========================================================================
@@ -2116,24 +2030,35 @@ return domAppendToHead(link); }, domQueryAll(container, "link[rel=\"stylesheet\"
var pageName = get ( match , "name" ) ;
return ( isSxTruthy ( sxOr ( isNil ( contentSrc ) , isEmpty ( contentSrc ) ) ) ? ( logWarn ( ( String ( "sx:route no content for " ) + String ( pathname ) ) ) , false ) : ( function ( ) {
var target = resolveRouteTarget ( targetSel ) ;
return ( isSxTruthy ( isNil ( target ) ) ? ( logWarn ( ( String ( "sx:route target not found: " ) + String ( targetSel ) ) ) , false ) : ( isSxTruthy ( ! isSxTruthy ( depsSatisfied _p ( match ) ) ) ? ( logInfo ( ( String ( "sx:route deps miss for " ) + String ( pageName ) ) ) , false ) : ( isSxTruthy ( get ( match , "has-data" ) ) ? ( function ( ) {
return ( isSxTruthy ( isNil ( target ) ) ? ( logWarn ( ( String ( "sx:route target not found: " ) + String ( targetSel ) ) ) , false ) : ( isSxTruthy ( ! isSxTruthy ( depsSatisfied _p ( match ) ) ) ? ( logInfo ( ( String ( "sx:route deps miss for " ) + String ( pageName ) ) ) , false ) : ( function ( ) {
var ioDeps = get ( match , "io-deps" ) ;
var hasIo = ( isSxTruthy ( ioDeps ) && ! isSxTruthy ( isEmpty ( ioDeps ) ) ) ;
if ( isSxTruthy ( hasIo ) ) {
registerIoDeps ( ioDeps ) ;
}
return ( isSxTruthy ( get ( match , "has-data" ) ) ? ( function ( ) {
var cacheKey = pageDataCacheKey ( pageName , params ) ;
var cached = pageDataCacheGet ( cacheKey ) ;
return ( isSxTruthy ( cached ) ? ( function ( ) {
var env = merge ( closure , params , cached ) ;
return ( isSxTruthy ( hasIo ) ? ( logInfo ( ( String ( "sx:route client+cache+async " ) + String ( pathname ) ) ) , tryAsyncEvalContent ( contentSrc , env , function ( rendered ) { return ( isSxTruthy ( isNil ( rendered ) ) ? logWarn ( ( String ( "sx:route async eval failed for " ) + String ( pathname ) ) ) : swapRenderedContent ( target , rendered , pathname ) ) ; } ) , true ) : ( function ( ) {
var rendered = tryEvalContent ( contentSrc , env ) ;
return ( isSxTruthy ( isNil ( rendered ) ) ? ( logWarn ( ( String ( "sx:route cached eval failed for " ) + String ( pathname ) ) ) , false ) : ( logInfo ( ( String ( "sx:route client+cache " ) + String ( pathname ) ) ) , swapRenderedContent ( target , rendered , pathname ) , true ) ) ;
} ) ( ) ) ;
} ) ( ) : ( logInfo ( ( String ( "sx:route client+data " ) + String ( pathname ) ) ) , resolvePageData ( pageName , params , function ( data ) { pageDataCacheSet ( cacheKey , data ) ;
return ( function ( ) {
var env = merge ( closure , params , data ) ;
return ( isSxTruthy ( hasIo ) ? tryAsyncEvalContent ( contentSrc , env , function ( rendered ) { return ( isSxTruthy ( isNil ( rendered ) ) ? logWarn ( ( String ( "sx:route data+async eval failed for " ) + String ( pathname ) ) ) : swapRenderedContent ( target , rendered , pathname ) ) ; } ) : ( function ( ) {
var rendered = tryEvalContent ( contentSrc , env ) ;
return ( isSxTruthy ( isNil ( rendered ) ) ? logWarn ( ( String ( "sx:route data eval failed for " ) + String ( pathname ) ) ) : swapRenderedContent ( target , rendered , pathname ) ) ;
} ) ( ) ) ;
} ) ( ) ; } ) , true ) ) ;
} ) ( ) : ( function ( ) {
} ) ( ) : ( isSxTruthy ( hasIo ) ? ( logInfo ( ( String ( "sx:route client+async " ) + String ( pathname ) ) ) , tryAsyncEvalContent ( contentSrc , merge ( closure , params ) , function ( rendered ) { return ( isSxTruthy ( isNil ( rendered ) ) ? logWarn ( ( String ( "sx:route async eval failed for " ) + String ( pathname ) ) ) : swapRenderedContent ( target , rendered , pathname ) ) ; } ) , true ) : ( function ( ) {
var env = merge ( closure , params ) ;
var rendered = tryEvalContent ( contentSrc , env ) ;
return ( isSxTruthy ( isNil ( rendered ) ) ? ( logInfo ( ( String ( "sx:route server (eval failed) " ) + String ( pathname ) ) ) , false ) : ( swapRenderedContent ( target , rendered , pathname ) , true ) ) ;
} ) ( ) ) ) ) ;
} ) ( ) ) ) ;
} ) ( ) ) ) ;
} ) ( ) ) ;
} ) ( ) ) ;
} ) ( ) ; } ;
@@ -2558,119 +2483,6 @@ callExpr.push(dictGet(kwargs, k)); } }
var bootInit = function ( ) { return ( logInfo ( ( String ( "sx-browser " ) + String ( SX _VERSION ) ) ) , initCssTracking ( ) , initStyleDict ( ) , processPageScripts ( ) , processSxScripts ( NIL ) , sxHydrateElements ( NIL ) , processElements ( NIL ) ) ; } ;
// === Transpiled from deps (component dependency analysis) ===
// scan-refs
var scanRefs = function ( node ) { return ( function ( ) {
var refs = [ ] ;
scanRefsWalk ( node , refs ) ;
return refs ;
} ) ( ) ; } ;
// scan-refs-walk
var scanRefsWalk = function ( node , refs ) { return ( isSxTruthy ( ( typeOf ( node ) == "symbol" ) ) ? ( function ( ) {
var name = symbolName ( node ) ;
return ( isSxTruthy ( startsWith ( name , "~" ) ) ? ( isSxTruthy ( ! isSxTruthy ( contains ( refs , name ) ) ) ? append _b ( refs , name ) : NIL ) : NIL ) ;
} ) ( ) : ( isSxTruthy ( ( typeOf ( node ) == "list" ) ) ? forEach ( function ( item ) { return scanRefsWalk ( item , refs ) ; } , node ) : ( isSxTruthy ( ( typeOf ( node ) == "dict" ) ) ? forEach ( function ( key ) { return scanRefsWalk ( dictGet ( node , key ) , refs ) ; } , keys ( node ) ) : NIL ) ) ) ; } ;
// transitive-deps-walk
var transitiveDepsWalk = function ( n , seen , env ) { return ( isSxTruthy ( ! isSxTruthy ( contains ( seen , n ) ) ) ? ( append _b ( seen , n ) , ( function ( ) {
var val = envGet ( env , n ) ;
return ( isSxTruthy ( ( typeOf ( val ) == "component" ) ) ? forEach ( function ( ref ) { return transitiveDepsWalk ( ref , seen , env ) ; } , scanRefs ( componentBody ( val ) ) ) : ( isSxTruthy ( ( typeOf ( val ) == "macro" ) ) ? forEach ( function ( ref ) { return transitiveDepsWalk ( ref , seen , env ) ; } , scanRefs ( macroBody ( val ) ) ) : NIL ) ) ;
} ) ( ) ) : NIL ) ; } ;
// transitive-deps
var transitiveDeps = function ( name , env ) { return ( function ( ) {
var seen = [ ] ;
var key = ( isSxTruthy ( startsWith ( name , "~" ) ) ? name : ( String ( "~" ) + String ( name ) ) ) ;
transitiveDepsWalk ( key , seen , env ) ;
return filter ( function ( x ) { return ! isSxTruthy ( ( x == key ) ) ; } , seen ) ;
} ) ( ) ; } ;
// compute-all-deps
var computeAllDeps = function ( env ) { return forEach ( function ( name ) { return ( function ( ) {
var val = envGet ( env , name ) ;
return ( isSxTruthy ( ( typeOf ( val ) == "component" ) ) ? componentSetDeps ( val , transitiveDeps ( name , env ) ) : NIL ) ;
} ) ( ) ; } , envComponents ( env ) ) ; } ;
// scan-components-from-source
var scanComponentsFromSource = function ( source ) { return ( function ( ) {
var matches = regexFindAll ( "\\(~([a-zA-Z_][a-zA-Z0-9_\\-]*)" , source ) ;
return map ( function ( m ) { return ( String ( "~" ) + String ( m ) ) ; } , matches ) ;
} ) ( ) ; } ;
// components-needed
var componentsNeeded = function ( pageSource , env ) { return ( function ( ) {
var direct = scanComponentsFromSource ( pageSource ) ;
var allNeeded = [ ] ;
{ var _c = direct ; for ( var _i = 0 ; _i < _c . length ; _i ++ ) { var name = _c [ _i ] ; if ( isSxTruthy ( ! isSxTruthy ( contains ( allNeeded , name ) ) ) ) {
allNeeded . push ( name ) ;
}
( function ( ) {
var val = envGet ( env , name ) ;
return ( function ( ) {
var deps = ( isSxTruthy ( ( isSxTruthy ( ( typeOf ( val ) == "component" ) ) && ! isSxTruthy ( isEmpty ( componentDeps ( val ) ) ) ) ) ? componentDeps ( val ) : transitiveDeps ( name , env ) ) ;
return forEach ( function ( dep ) { return ( isSxTruthy ( ! isSxTruthy ( contains ( allNeeded , dep ) ) ) ? append _b ( allNeeded , dep ) : NIL ) ; } , deps ) ;
} ) ( ) ;
} ) ( ) ; } }
return allNeeded ;
} ) ( ) ; } ;
// page-component-bundle
var pageComponentBundle = function ( pageSource , env ) { return componentsNeeded ( pageSource , env ) ; } ;
// page-css-classes
var pageCssClasses = function ( pageSource , env ) { return ( function ( ) {
var needed = componentsNeeded ( pageSource , env ) ;
var classes = [ ] ;
{ var _c = needed ; for ( var _i = 0 ; _i < _c . length ; _i ++ ) { var name = _c [ _i ] ; ( function ( ) {
var val = envGet ( env , name ) ;
return ( isSxTruthy ( ( typeOf ( val ) == "component" ) ) ? forEach ( function ( cls ) { return ( isSxTruthy ( ! isSxTruthy ( contains ( classes , cls ) ) ) ? append _b ( classes , cls ) : NIL ) ; } , componentCssClasses ( val ) ) : NIL ) ;
} ) ( ) ; } }
{ var _c = scanCssClasses ( pageSource ) ; for ( var _i = 0 ; _i < _c . length ; _i ++ ) { var cls = _c [ _i ] ; if ( isSxTruthy ( ! isSxTruthy ( contains ( classes , cls ) ) ) ) {
classes . push ( cls ) ;
} } }
return classes ;
} ) ( ) ; } ;
// scan-io-refs-walk
var scanIoRefsWalk = function ( node , ioNames , refs ) { return ( isSxTruthy ( ( typeOf ( node ) == "symbol" ) ) ? ( function ( ) {
var name = symbolName ( node ) ;
return ( isSxTruthy ( contains ( ioNames , name ) ) ? ( isSxTruthy ( ! isSxTruthy ( contains ( refs , name ) ) ) ? append _b ( refs , name ) : NIL ) : NIL ) ;
} ) ( ) : ( isSxTruthy ( ( typeOf ( node ) == "list" ) ) ? forEach ( function ( item ) { return scanIoRefsWalk ( item , ioNames , refs ) ; } , node ) : ( isSxTruthy ( ( typeOf ( node ) == "dict" ) ) ? forEach ( function ( key ) { return scanIoRefsWalk ( dictGet ( node , key ) , ioNames , refs ) ; } , keys ( node ) ) : NIL ) ) ) ; } ;
// scan-io-refs
var scanIoRefs = function ( node , ioNames ) { return ( function ( ) {
var refs = [ ] ;
scanIoRefsWalk ( node , ioNames , refs ) ;
return refs ;
} ) ( ) ; } ;
// transitive-io-refs-walk
var transitiveIoRefsWalk = function ( n , seen , allRefs , env , ioNames ) { return ( isSxTruthy ( ! isSxTruthy ( contains ( seen , n ) ) ) ? ( append _b ( seen , n ) , ( function ( ) {
var val = envGet ( env , n ) ;
return ( isSxTruthy ( ( typeOf ( val ) == "component" ) ) ? ( forEach ( function ( ref ) { return ( isSxTruthy ( ! isSxTruthy ( contains ( allRefs , ref ) ) ) ? append _b ( allRefs , ref ) : NIL ) ; } , scanIoRefs ( componentBody ( val ) , ioNames ) ) , forEach ( function ( dep ) { return transitiveIoRefsWalk ( dep , seen , allRefs , env , ioNames ) ; } , scanRefs ( componentBody ( val ) ) ) ) : ( isSxTruthy ( ( typeOf ( val ) == "macro" ) ) ? ( forEach ( function ( ref ) { return ( isSxTruthy ( ! isSxTruthy ( contains ( allRefs , ref ) ) ) ? append _b ( allRefs , ref ) : NIL ) ; } , scanIoRefs ( macroBody ( val ) , ioNames ) ) , forEach ( function ( dep ) { return transitiveIoRefsWalk ( dep , seen , allRefs , env , ioNames ) ; } , scanRefs ( macroBody ( val ) ) ) ) : NIL ) ) ;
} ) ( ) ) : NIL ) ; } ;
// transitive-io-refs
var transitiveIoRefs = function ( name , env , ioNames ) { return ( function ( ) {
var allRefs = [ ] ;
var seen = [ ] ;
var key = ( isSxTruthy ( startsWith ( name , "~" ) ) ? name : ( String ( "~" ) + String ( name ) ) ) ;
transitiveIoRefsWalk ( key , seen , allRefs , env , ioNames ) ;
return allRefs ;
} ) ( ) ; } ;
// compute-all-io-refs
var computeAllIoRefs = function ( env , ioNames ) { return forEach ( function ( name ) { return ( function ( ) {
var val = envGet ( env , name ) ;
return ( isSxTruthy ( ( typeOf ( val ) == "component" ) ) ? componentSetIoRefs ( val , transitiveIoRefs ( name , env , ioNames ) ) : NIL ) ;
} ) ( ) ; } , envComponents ( env ) ) ; } ;
// component-pure?
var componentPure _p = function ( name , env , ioNames ) { return isEmpty ( transitiveIoRefs ( name , env , ioNames ) ) ; } ;
// === Transpiled from router (client-side route matching) ===
// split-path-segments
@@ -3510,6 +3322,32 @@ callExpr.push(dictGet(kwargs, k)); } }
}
}
// Async eval with callback — used for pages with IO deps.
// Calls callback(rendered) when done, callback(null) on failure.
function tryAsyncEvalContent ( source , env , callback ) {
var merged = merge ( componentEnv ) ;
if ( env && ! isNil ( env ) ) {
var ks = Object . keys ( env ) ;
for ( var i = 0 ; i < ks . length ; i ++ ) merged [ ks [ i ] ] = env [ ks [ i ] ] ;
}
try {
var result = asyncSxRenderWithEnv ( source , merged ) ;
if ( isPromise ( result ) ) {
result . then ( function ( rendered ) {
callback ( rendered ) ;
} ) . catch ( function ( e ) {
logWarn ( "sx:async eval miss: " + ( e && e . message ? e . message : e ) ) ;
callback ( null ) ;
} ) ;
} else {
callback ( result ) ;
}
} catch ( e ) {
logInfo ( "sx:async eval miss: " + ( e && e . message ? e . message : e ) ) ;
callback ( null ) ;
}
}
function resolvePageData ( pageName , params , callback ) {
// Platform implementation: fetch page data via HTTP from /sx/data/ endpoint.
// The spec only knows about resolve-page-data(name, params, callback) —
@@ -3939,6 +3777,654 @@ callExpr.push(dictGet(kwargs, k)); } }
if ( typeof aser === "function" ) PRIMITIVES [ "aser" ] = aser ;
if ( typeof renderToDom === "function" ) PRIMITIVES [ "render-to-dom" ] = renderToDom ;
// =========================================================================
// Async IO: Promise-aware rendering for client-side IO primitives
// =========================================================================
//
// IO primitives (query, current-user, etc.) return Promises on the client.
// asyncRenderToDom walks the component tree; when it encounters an IO
// primitive, it awaits the Promise and continues rendering.
//
// The sync evaluator/renderer is untouched. This is a separate async path
// used only when a page's component tree contains IO references.
var IO _PRIMITIVES = { } ;
function registerIoPrimitive ( name , fn ) {
IO _PRIMITIVES [ name ] = fn ;
}
function isPromise ( x ) {
return x != null && typeof x === "object" && typeof x . then === "function" ;
}
// Async trampoline: resolves thunks, awaits Promises
function asyncTrampoline ( val ) {
if ( isPromise ( val ) ) return val . then ( asyncTrampoline ) ;
if ( isThunk ( val ) ) return asyncTrampoline ( evalExpr ( thunkExpr ( val ) , thunkEnv ( val ) ) ) ;
return val ;
}
// Async eval: like trampoline(evalExpr(...)) but handles IO primitives
function asyncEval ( expr , env ) {
// Intercept IO primitive calls at the AST level
if ( Array . isArray ( expr ) && expr . length > 0 ) {
var head = expr [ 0 ] ;
if ( head && head . _sym ) {
var name = head . name ;
if ( IO _PRIMITIVES [ name ] ) {
// Evaluate args, then call the IO primitive
return asyncEvalIoCall ( name , expr . slice ( 1 ) , env ) ;
}
}
}
// Non-IO: use sync eval, but result might be a thunk
var result = evalExpr ( expr , env ) ;
return asyncTrampoline ( result ) ;
}
function asyncEvalIoCall ( name , rawArgs , env ) {
// Parse keyword args and positional args, evaluating each (may be async)
var kwargs = { } ;
var args = [ ] ;
var promises = [ ] ;
var i = 0 ;
while ( i < rawArgs . length ) {
var arg = rawArgs [ i ] ;
if ( arg && arg . _kw && ( i + 1 ) < rawArgs . length ) {
var kName = arg . name ;
var kVal = asyncEval ( rawArgs [ i + 1 ] , env ) ;
if ( isPromise ( kVal ) ) {
( function ( k ) { promises . push ( kVal . then ( function ( v ) { kwargs [ k ] = v ; } ) ) ; } ) ( kName ) ;
} else {
kwargs [ kName ] = kVal ;
}
i += 2 ;
} else {
var aVal = asyncEval ( arg , env ) ;
if ( isPromise ( aVal ) ) {
( function ( idx ) { promises . push ( aVal . then ( function ( v ) { args [ idx ] = v ; } ) ) ; } ) ( args . length ) ;
args . push ( null ) ; // placeholder
} else {
args . push ( aVal ) ;
}
i ++ ;
}
}
var ioFn = IO _PRIMITIVES [ name ] ;
if ( promises . length > 0 ) {
return Promise . all ( promises ) . then ( function ( ) { return ioFn ( args , kwargs ) ; } ) ;
}
return ioFn ( args , kwargs ) ;
}
// Async render-to-dom: returns Promise<Node> or Node
function asyncRenderToDom ( expr , env , ns ) {
// Literals
if ( expr === NIL || expr === null || expr === undefined ) return null ;
if ( expr === true || expr === false ) return null ;
if ( typeof expr === "string" ) return document . createTextNode ( expr ) ;
if ( typeof expr === "number" ) return document . createTextNode ( String ( expr ) ) ;
// Symbol -> async eval then render
if ( expr && expr . _sym ) {
var val = asyncEval ( expr , env ) ;
if ( isPromise ( val ) ) return val . then ( function ( v ) { return asyncRenderToDom ( v , env , ns ) ; } ) ;
return asyncRenderToDom ( val , env , ns ) ;
}
// Keyword
if ( expr && expr . _kw ) return document . createTextNode ( expr . name ) ;
// DocumentFragment / DOM nodes pass through
if ( expr instanceof DocumentFragment || ( expr && expr . nodeType ) ) return expr ;
// Dict -> skip
if ( expr && typeof expr === "object" && ! Array . isArray ( expr ) ) return null ;
// List
if ( ! Array . isArray ( expr ) || expr . length === 0 ) return null ;
var head = expr [ 0 ] ;
if ( ! head ) return null ;
// Symbol head
if ( head . _sym ) {
var hname = head . name ;
// IO primitive
if ( IO _PRIMITIVES [ hname ] ) {
var ioResult = asyncEval ( expr , env ) ;
if ( isPromise ( ioResult ) ) return ioResult . then ( function ( v ) { return asyncRenderToDom ( v , env , ns ) ; } ) ;
return asyncRenderToDom ( ioResult , env , ns ) ;
}
// Fragment
if ( hname === "<>" ) return asyncRenderChildren ( expr . slice ( 1 ) , env , ns ) ;
// raw!
if ( hname === "raw!" ) {
return asyncEvalRaw ( expr . slice ( 1 ) , env ) ;
}
// Special forms that need async handling
if ( hname === "if" ) return asyncRenderIf ( expr , env , ns ) ;
if ( hname === "when" ) return asyncRenderWhen ( expr , env , ns ) ;
if ( hname === "cond" ) return asyncRenderCond ( expr , env , ns ) ;
if ( hname === "case" ) return asyncRenderCase ( expr , env , ns ) ;
if ( hname === "let" || hname === "let*" ) return asyncRenderLet ( expr , env , ns ) ;
if ( hname === "begin" || hname === "do" ) return asyncRenderChildren ( expr . slice ( 1 ) , env , ns ) ;
if ( hname === "map" ) return asyncRenderMap ( expr , env , ns ) ;
if ( hname === "map-indexed" ) return asyncRenderMapIndexed ( expr , env , ns ) ;
if ( hname === "for-each" ) return asyncRenderMap ( expr , env , ns ) ;
// define/defcomp/defmacro — eval for side effects
if ( hname === "define" || hname === "defcomp" || hname === "defmacro" ||
hname === "defstyle" || hname === "defkeyframes" || hname === "defhandler" ) {
trampoline ( evalExpr ( expr , env ) ) ;
return null ;
}
// quote
if ( hname === "quote" ) return null ;
// lambda/fn
if ( hname === "lambda" || hname === "fn" ) {
trampoline ( evalExpr ( expr , env ) ) ;
return null ;
}
// and/or — eval and render result
if ( hname === "and" || hname === "or" || hname === "->" ) {
var aoResult = asyncEval ( expr , env ) ;
if ( isPromise ( aoResult ) ) return aoResult . then ( function ( v ) { return asyncRenderToDom ( v , env , ns ) ; } ) ;
return asyncRenderToDom ( aoResult , env , ns ) ;
}
// set!
if ( hname === "set!" ) {
asyncEval ( expr , env ) ;
return null ;
}
// Component
if ( hname . charAt ( 0 ) === "~" ) {
var comp = env [ hname ] ;
if ( comp && comp . _component ) return asyncRenderComponent ( comp , expr . slice ( 1 ) , env , ns ) ;
if ( comp && comp . _macro ) {
var expanded = trampoline ( expandMacro ( comp , expr . slice ( 1 ) , env ) ) ;
return asyncRenderToDom ( expanded , env , ns ) ;
}
}
// Macro
if ( env [ hname ] && env [ hname ] . _macro ) {
var mac = env [ hname ] ;
var expanded = trampoline ( expandMacro ( mac , expr . slice ( 1 ) , env ) ) ;
return asyncRenderToDom ( expanded , env , ns ) ;
}
// HTML tag
if ( typeof renderDomElement === "function" && contains ( HTML _TAGS , hname ) ) {
return asyncRenderElement ( hname , expr . slice ( 1 ) , env , ns ) ;
}
// html: prefix
if ( hname . indexOf ( "html:" ) === 0 ) {
return asyncRenderElement ( hname . slice ( 5 ) , expr . slice ( 1 ) , env , ns ) ;
}
// Custom element
if ( hname . indexOf ( "-" ) >= 0 && expr . length > 1 && expr [ 1 ] && expr [ 1 ] . _kw ) {
return asyncRenderElement ( hname , expr . slice ( 1 ) , env , ns ) ;
}
// SVG context
if ( ns ) return asyncRenderElement ( hname , expr . slice ( 1 ) , env , ns ) ;
// Fallback: eval and render
var fResult = asyncEval ( expr , env ) ;
if ( isPromise ( fResult ) ) return fResult . then ( function ( v ) { return asyncRenderToDom ( v , env , ns ) ; } ) ;
return asyncRenderToDom ( fResult , env , ns ) ;
}
// Non-symbol head: eval call
var cResult = asyncEval ( expr , env ) ;
if ( isPromise ( cResult ) ) return cResult . then ( function ( v ) { return asyncRenderToDom ( v , env , ns ) ; } ) ;
return asyncRenderToDom ( cResult , env , ns ) ;
}
function asyncRenderChildren ( exprs , env , ns ) {
var frag = document . createDocumentFragment ( ) ;
var pending = [ ] ;
for ( var i = 0 ; i < exprs . length ; i ++ ) {
var result = asyncRenderToDom ( exprs [ i ] , env , ns ) ;
if ( isPromise ( result ) ) {
// Insert placeholder, replace when resolved
var placeholder = document . createComment ( "async" ) ;
frag . appendChild ( placeholder ) ;
( function ( ph ) {
pending . push ( result . then ( function ( node ) {
if ( node ) ph . parentNode . replaceChild ( node , ph ) ;
else ph . parentNode . removeChild ( ph ) ;
} ) ) ;
} ) ( placeholder ) ;
} else if ( result ) {
frag . appendChild ( result ) ;
}
}
if ( pending . length > 0 ) {
return Promise . all ( pending ) . then ( function ( ) { return frag ; } ) ;
}
return frag ;
}
function asyncRenderElement ( tag , args , env , ns ) {
var newNs = tag === "svg" ? SVG _NS : tag === "math" ? MATH _NS : ns ;
var el = domCreateElement ( tag , newNs ) ;
var pending = [ ] ;
var isVoid = contains ( VOID _ELEMENTS , tag ) ;
for ( var i = 0 ; i < args . length ; i ++ ) {
var arg = args [ i ] ;
if ( arg && arg . _kw && ( i + 1 ) < args . length ) {
var attrName = arg . name ;
var attrVal = asyncEval ( args [ i + 1 ] , env ) ;
i ++ ;
if ( isPromise ( attrVal ) ) {
( function ( an , av ) {
pending . push ( av . then ( function ( v ) {
if ( ! isNil ( v ) && v !== false ) {
if ( contains ( BOOLEAN _ATTRS , an ) ) { if ( isSxTruthy ( v ) ) el . setAttribute ( an , "" ) ; }
else if ( v === true ) el . setAttribute ( an , "" ) ;
else el . setAttribute ( an , String ( v ) ) ;
}
} ) ) ;
} ) ( attrName , attrVal ) ;
} else {
if ( ! isNil ( attrVal ) && attrVal !== false ) {
if ( attrName === "class" && attrVal && attrVal . _styleValue ) {
el . setAttribute ( "class" , ( el . getAttribute ( "class" ) || "" ) + " " + attrVal . className ) ;
} else if ( attrName === "style" && attrVal && attrVal . _styleValue ) {
el . setAttribute ( "class" , ( el . getAttribute ( "class" ) || "" ) + " " + attrVal . className ) ;
} else if ( contains ( BOOLEAN _ATTRS , attrName ) ) {
if ( isSxTruthy ( attrVal ) ) el . setAttribute ( attrName , "" ) ;
} else if ( attrVal === true ) {
el . setAttribute ( attrName , "" ) ;
} else {
el . setAttribute ( attrName , String ( attrVal ) ) ;
}
}
}
} else if ( ! isVoid ) {
var child = asyncRenderToDom ( arg , env , newNs ) ;
if ( isPromise ( child ) ) {
var placeholder = document . createComment ( "async" ) ;
el . appendChild ( placeholder ) ;
( function ( ph ) {
pending . push ( child . then ( function ( node ) {
if ( node ) ph . parentNode . replaceChild ( node , ph ) ;
else ph . parentNode . removeChild ( ph ) ;
} ) ) ;
} ) ( placeholder ) ;
} else if ( child ) {
el . appendChild ( child ) ;
}
}
}
if ( pending . length > 0 ) return Promise . all ( pending ) . then ( function ( ) { return el ; } ) ;
return el ;
}
function asyncRenderComponent ( comp , args , env , ns ) {
var kwargs = { } ;
var children = [ ] ;
var pending = [ ] ;
for ( var i = 0 ; i < args . length ; i ++ ) {
var arg = args [ i ] ;
if ( arg && arg . _kw && ( i + 1 ) < args . length ) {
var kName = arg . name ;
var kVal = asyncEval ( args [ i + 1 ] , env ) ;
if ( isPromise ( kVal ) ) {
( function ( k ) { pending . push ( kVal . then ( function ( v ) { kwargs [ k ] = v ; } ) ) ; } ) ( kName ) ;
} else {
kwargs [ kName ] = kVal ;
}
i ++ ;
} else {
children . push ( arg ) ;
}
}
function doRender ( ) {
var local = Object . create ( componentClosure ( comp ) ) ;
for ( var k in env ) if ( env . hasOwnProperty ( k ) ) local [ k ] = env [ k ] ;
var params = componentParams ( comp ) ;
for ( var j = 0 ; j < params . length ; j ++ ) {
local [ params [ j ] ] = params [ j ] in kwargs ? kwargs [ params [ j ] ] : NIL ;
}
if ( componentHasChildren ( comp ) ) {
var childResult = asyncRenderChildren ( children , env , ns ) ;
if ( isPromise ( childResult ) ) {
return childResult . then ( function ( childFrag ) {
local [ "children" ] = childFrag ;
return asyncRenderToDom ( componentBody ( comp ) , local , ns ) ;
} ) ;
}
local [ "children" ] = childResult ;
}
return asyncRenderToDom ( componentBody ( comp ) , local , ns ) ;
}
if ( pending . length > 0 ) return Promise . all ( pending ) . then ( doRender ) ;
return doRender ( ) ;
}
function asyncRenderIf ( expr , env , ns ) {
var cond = asyncEval ( expr [ 1 ] , env ) ;
if ( isPromise ( cond ) ) {
return cond . then ( function ( v ) {
return isSxTruthy ( v )
? asyncRenderToDom ( expr [ 2 ] , env , ns )
: ( expr . length > 3 ? asyncRenderToDom ( expr [ 3 ] , env , ns ) : null ) ;
} ) ;
}
return isSxTruthy ( cond )
? asyncRenderToDom ( expr [ 2 ] , env , ns )
: ( expr . length > 3 ? asyncRenderToDom ( expr [ 3 ] , env , ns ) : null ) ;
}
function asyncRenderWhen ( expr , env , ns ) {
var cond = asyncEval ( expr [ 1 ] , env ) ;
if ( isPromise ( cond ) ) {
return cond . then ( function ( v ) {
return isSxTruthy ( v ) ? asyncRenderChildren ( expr . slice ( 2 ) , env , ns ) : null ;
} ) ;
}
return isSxTruthy ( cond ) ? asyncRenderChildren ( expr . slice ( 2 ) , env , ns ) : null ;
}
function asyncRenderCond ( expr , env , ns ) {
var clauses = expr . slice ( 1 ) ;
function step ( idx ) {
if ( idx >= clauses . length ) return null ;
var clause = clauses [ idx ] ;
if ( ! Array . isArray ( clause ) || clause . length < 2 ) return step ( idx + 1 ) ;
var test = clause [ 0 ] ;
if ( ( test && test . _sym && ( test . name === "else" || test . name === ":else" ) ) ||
( test && test . _kw && test . name === "else" ) ) {
return asyncRenderToDom ( clause [ 1 ] , env , ns ) ;
}
var v = asyncEval ( test , env ) ;
if ( isPromise ( v ) ) return v . then ( function ( r ) { return isSxTruthy ( r ) ? asyncRenderToDom ( clause [ 1 ] , env , ns ) : step ( idx + 1 ) ; } ) ;
return isSxTruthy ( v ) ? asyncRenderToDom ( clause [ 1 ] , env , ns ) : step ( idx + 1 ) ;
}
return step ( 0 ) ;
}
function asyncRenderCase ( expr , env , ns ) {
var matchVal = asyncEval ( expr [ 1 ] , env ) ;
function doCase ( mv ) {
var clauses = expr . slice ( 2 ) ;
for ( var i = 0 ; i < clauses . length - 1 ; i += 2 ) {
var test = clauses [ i ] ;
if ( ( test && test . _kw && test . name === "else" ) ||
( test && test . _sym && ( test . name === "else" || test . name === ":else" ) ) ) {
return asyncRenderToDom ( clauses [ i + 1 ] , env , ns ) ;
}
var tv = trampoline ( evalExpr ( test , env ) ) ;
if ( mv === tv || ( typeof mv === "string" && typeof tv === "string" && mv === tv ) ) {
return asyncRenderToDom ( clauses [ i + 1 ] , env , ns ) ;
}
}
return null ;
}
if ( isPromise ( matchVal ) ) return matchVal . then ( doCase ) ;
return doCase ( matchVal ) ;
}
function asyncRenderLet ( expr , env , ns ) {
var bindings = expr [ 1 ] ;
var local = Object . create ( env ) ;
for ( var k in env ) if ( env . hasOwnProperty ( k ) ) local [ k ] = env [ k ] ;
function bindStep ( idx ) {
if ( ! Array . isArray ( bindings ) ) return asyncRenderChildren ( expr . slice ( 2 ) , local , ns ) ;
// Nested pairs: ((a 1) (b 2))
if ( bindings . length > 0 && Array . isArray ( bindings [ 0 ] ) ) {
if ( idx >= bindings . length ) return asyncRenderChildren ( expr . slice ( 2 ) , local , ns ) ;
var b = bindings [ idx ] ;
var vname = b [ 0 ] . _sym ? b [ 0 ] . name : String ( b [ 0 ] ) ;
var val = asyncEval ( b [ 1 ] , local ) ;
if ( isPromise ( val ) ) return val . then ( function ( v ) { local [ vname ] = v ; return bindStep ( idx + 1 ) ; } ) ;
local [ vname ] = val ;
return bindStep ( idx + 1 ) ;
}
// Flat pairs: (a 1 b 2)
if ( idx >= bindings . length ) return asyncRenderChildren ( expr . slice ( 2 ) , local , ns ) ;
var vn = bindings [ idx ] . _sym ? bindings [ idx ] . name : String ( bindings [ idx ] ) ;
var vv = asyncEval ( bindings [ idx + 1 ] , local ) ;
if ( isPromise ( vv ) ) return vv . then ( function ( v ) { local [ vn ] = v ; return bindStep ( idx + 2 ) ; } ) ;
local [ vn ] = vv ;
return bindStep ( idx + 2 ) ;
}
return bindStep ( 0 ) ;
}
function asyncRenderMap ( expr , env , ns ) {
var fn = asyncEval ( expr [ 1 ] , env ) ;
var coll = asyncEval ( expr [ 2 ] , env ) ;
function doMap ( f , c ) {
if ( ! Array . isArray ( c ) ) return null ;
var frag = document . createDocumentFragment ( ) ;
var pending = [ ] ;
for ( var i = 0 ; i < c . length ; i ++ ) {
var item = c [ i ] ;
var result ;
if ( f && f . _lambda ) {
var lenv = Object . create ( f . closure || env ) ;
for ( var k in env ) if ( env . hasOwnProperty ( k ) ) lenv [ k ] = env [ k ] ;
lenv [ f . params [ 0 ] ] = item ;
result = asyncRenderToDom ( f . body , lenv , null ) ;
} else if ( typeof f === "function" ) {
var r = f ( item ) ;
result = isPromise ( r ) ? r . then ( function ( v ) { return asyncRenderToDom ( v , env , null ) ; } ) : asyncRenderToDom ( r , env , null ) ;
} else {
result = asyncRenderToDom ( item , env , null ) ;
}
if ( isPromise ( result ) ) {
var ph = document . createComment ( "async" ) ;
frag . appendChild ( ph ) ;
( function ( p ) { pending . push ( result . then ( function ( n ) { if ( n ) p . parentNode . replaceChild ( n , p ) ; else p . parentNode . removeChild ( p ) ; } ) ) ; } ) ( ph ) ;
} else if ( result ) {
frag . appendChild ( result ) ;
}
}
if ( pending . length ) return Promise . all ( pending ) . then ( function ( ) { return frag ; } ) ;
return frag ;
}
if ( isPromise ( fn ) || isPromise ( coll ) ) {
return Promise . all ( [ isPromise ( fn ) ? fn : Promise . resolve ( fn ) , isPromise ( coll ) ? coll : Promise . resolve ( coll ) ] )
. then ( function ( r ) { return doMap ( r [ 0 ] , r [ 1 ] ) ; } ) ;
}
return doMap ( fn , coll ) ;
}
function asyncRenderMapIndexed ( expr , env , ns ) {
var fn = asyncEval ( expr [ 1 ] , env ) ;
var coll = asyncEval ( expr [ 2 ] , env ) ;
function doMap ( f , c ) {
if ( ! Array . isArray ( c ) ) return null ;
var frag = document . createDocumentFragment ( ) ;
var pending = [ ] ;
for ( var i = 0 ; i < c . length ; i ++ ) {
var item = c [ i ] ;
var result ;
if ( f && f . _lambda ) {
var lenv = Object . create ( f . closure || env ) ;
for ( var k in env ) if ( env . hasOwnProperty ( k ) ) lenv [ k ] = env [ k ] ;
lenv [ f . params [ 0 ] ] = i ;
lenv [ f . params [ 1 ] ] = item ;
result = asyncRenderToDom ( f . body , lenv , null ) ;
} else if ( typeof f === "function" ) {
var r = f ( i , item ) ;
result = isPromise ( r ) ? r . then ( function ( v ) { return asyncRenderToDom ( v , env , null ) ; } ) : asyncRenderToDom ( r , env , null ) ;
} else {
result = asyncRenderToDom ( item , env , null ) ;
}
if ( isPromise ( result ) ) {
var ph = document . createComment ( "async" ) ;
frag . appendChild ( ph ) ;
( function ( p ) { pending . push ( result . then ( function ( n ) { if ( n ) p . parentNode . replaceChild ( n , p ) ; else p . parentNode . removeChild ( p ) ; } ) ) ; } ) ( ph ) ;
} else if ( result ) {
frag . appendChild ( result ) ;
}
}
if ( pending . length ) return Promise . all ( pending ) . then ( function ( ) { return frag ; } ) ;
return frag ;
}
if ( isPromise ( fn ) || isPromise ( coll ) ) {
return Promise . all ( [ isPromise ( fn ) ? fn : Promise . resolve ( fn ) , isPromise ( coll ) ? coll : Promise . resolve ( coll ) ] )
. then ( function ( r ) { return doMap ( r [ 0 ] , r [ 1 ] ) ; } ) ;
}
return doMap ( fn , coll ) ;
}
function asyncEvalRaw ( args , env ) {
var parts = [ ] ;
var pending = [ ] ;
for ( var i = 0 ; i < args . length ; i ++ ) {
var val = asyncEval ( args [ i ] , env ) ;
if ( isPromise ( val ) ) {
( function ( idx ) {
pending . push ( val . then ( function ( v ) { parts [ idx ] = v ; } ) ) ;
} ) ( parts . length ) ;
parts . push ( null ) ;
} else {
parts . push ( val ) ;
}
}
function assemble ( ) {
var html = "" ;
for ( var j = 0 ; j < parts . length ; j ++ ) {
var p = parts [ j ] ;
if ( p && p . _rawHtml ) html += p . html ;
else if ( typeof p === "string" ) html += p ;
else if ( p != null && ! isNil ( p ) ) html += String ( p ) ;
}
var el = document . createElement ( "span" ) ;
el . innerHTML = html ;
var frag = document . createDocumentFragment ( ) ;
while ( el . firstChild ) frag . appendChild ( el . firstChild ) ;
return frag ;
}
if ( pending . length ) return Promise . all ( pending ) . then ( assemble ) ;
return assemble ( ) ;
}
// Async version of sxRenderWithEnv — returns Promise<DocumentFragment>
function asyncSxRenderWithEnv ( source , extraEnv ) {
var env = extraEnv ? merge ( componentEnv , extraEnv ) : componentEnv ;
var exprs = parse ( source ) ;
if ( ! _hasDom ) return Promise . resolve ( null ) ;
return asyncRenderChildren ( exprs , env , null ) ;
}
// IO proxy cache: key → { value, expires }
var _ioCache = { } ;
var IO _CACHE _TTL = 300000 ; // 5 minutes
// Register a server-proxied IO primitive: fetches from /sx/io/<name>
// Uses GET for short args, POST for long payloads (URL length safety).
// Results are cached client-side by (name + args) with a TTL.
function registerProxiedIo ( name ) {
registerIoPrimitive ( name , function ( args , kwargs ) {
// Cache key: name + serialized args
var cacheKey = name ;
for ( var ci = 0 ; ci < args . length ; ci ++ ) cacheKey += "