""" Execute defquery / defaction definitions. Unlike fragment handlers (which produce SX markup via ``async_eval_to_sx``), query/action defs produce **data** (dicts, lists, scalars) that get JSON-serialized by the calling blueprint. Uses ``async_eval()`` with the I/O primitive pipeline so ``(service ...)`` calls are awaited inline. """ from __future__ import annotations from typing import Any from .types import QueryDef, ActionDef, NIL async def execute_query(query_def: QueryDef, params: dict[str, str]) -> Any: """Execute a defquery and return a JSON-serializable result. Parameters are bound from request query string args. """ from .jinja_bridge import get_component_env, _get_request_context import os env = dict(get_component_env()) env.update(query_def.closure) # Bind params from request args (try kebab-case and snake_case) for param in query_def.params: snake = param.replace("-", "_") val = params.get(param, params.get(snake, NIL)) # Coerce type=int for common patterns if isinstance(val, str) and val.lstrip("-").isdigit(): val = int(val) env[param] = val if os.environ.get("SX_USE_OCAML") == "1": from .ocaml_bridge import get_bridge from .parser import serialize, parse_all from .pages import _wrap_with_env bridge = await get_bridge() sx_text = _wrap_with_env(query_def.body, env) ctx = {"_helper_service": ""} raw = await bridge.eval(sx_text, ctx=ctx) if raw: parsed = parse_all(raw) result = parsed[0] if parsed else None else: result = None return _normalize(result) if os.environ.get("SX_USE_REF") == "1": from .ref.async_eval_ref import async_eval else: from .async_eval import async_eval ctx = _get_request_context() result = await async_eval(query_def.body, env, ctx) return _normalize(result) async def execute_action(action_def: ActionDef, payload: dict[str, Any]) -> Any: """Execute a defaction and return a JSON-serializable result. Parameters are bound from the JSON request body. """ from .jinja_bridge import get_component_env, _get_request_context import os env = dict(get_component_env()) env.update(action_def.closure) # Bind params from JSON payload (try kebab-case and snake_case) for param in action_def.params: snake = param.replace("-", "_") val = payload.get(param, payload.get(snake, NIL)) env[param] = val if os.environ.get("SX_USE_OCAML") == "1": from .ocaml_bridge import get_bridge from .parser import serialize, parse_all from .pages import _wrap_with_env bridge = await get_bridge() sx_text = _wrap_with_env(action_def.body, env) ctx = {"_helper_service": ""} raw = await bridge.eval(sx_text, ctx=ctx) if raw: parsed = parse_all(raw) result = parsed[0] if parsed else None else: result = None return _normalize(result) if os.environ.get("SX_USE_REF") == "1": from .ref.async_eval_ref import async_eval else: from .async_eval import async_eval ctx = _get_request_context() result = await async_eval(action_def.body, env, ctx) return _normalize(result) def _normalize(value: Any) -> Any: """Ensure result is JSON-serializable (strip NIL, convert sets, etc).""" if value is NIL or value is None: return None if isinstance(value, set): return list(value) return value