// Production WASM W1 core entrypoint. // // This file deliberately includes the production UCE runtime amalgamation with // __UCE_WASM_CORE__ enabled. Native-only pieces are carved out in the runtime // sources, while the workspace-owned DValue ABI and output plumbing are built // into core.wasm. #define __UCE_WASM_CORE__ 1 #include "../lib/uce_lib.cpp" #include "../lib/mysql-connector.h" #include "../lib/sqlite-connector.h" // ---- W3 connector membranes ----------------------------------------------- // sqlite/mysql run host-side (the host links the native connectors and owns the // connections in per-workspace handle tables). UCEB2-marshalled hostcalls carry // operation requests/responses; `connection` holds the host handle (>0). static const char* WASM_DB_UNAVAILABLE = "database connector is not available in the wasm workspace"; extern "C" size_t uce_host_mysql(const char* in, size_t in_len, char* out, size_t cap); extern "C" size_t uce_host_zip(const char* in, size_t in_len, char* out, size_t cap); extern "C" size_t uce_host_units(const char* in, size_t in_len, char* out, size_t cap); static DValue wasm_sized_hostcall(DValue request, size_t (*hostcall)(const char*, size_t, char*, size_t)) { String encoded = ucb_encode(request); size_t need = hostcall(encoded.data(), encoded.size(), 0, 0); if(need == 0) return(DValue()); String buffer(need, 0); size_t got = hostcall(encoded.data(), encoded.size(), &buffer[0], need); if(got == 0 || got > need) return(DValue()); DValue response; String error; ucb_decode(String(buffer.data(), got), response, &error); return(response); } static DValue wasm_zip_call(DValue request) { DValue response = wasm_sized_hostcall(request, uce_host_zip); return(response); } DValue zip_list(String zip_file_name) { DValue request; request["op"] = "list"; request["path"] = zip_file_name; DValue response = wasm_zip_call(request); DValue* result = response.key("result"); return(result ? *result : DValue()); } String zip_read(String zip_file_name, String entry_name) { DValue request; request["op"] = "read"; request["path"] = zip_file_name; request["entry"] = entry_name; DValue response = wasm_zip_call(request); return(response["result"].to_string()); } bool zip_create(String zip_file_name, DValue entries) { DValue request; request["op"] = "create"; request["path"] = zip_file_name; request["entries"] = entries; DValue response = wasm_zip_call(request); return(response["ok"].to_bool()); } bool zip_extract(String zip_file_name, String destination_directory) { DValue request; request["op"] = "extract"; request["path"] = zip_file_name; request["destination"] = destination_directory; DValue response = wasm_zip_call(request); return(response["ok"].to_bool()); } String gz_compress(String src) { DValue request; request["op"] = "gz_compress"; request["src"] = src; DValue response = wasm_zip_call(request); return(response["result"].to_string()); } String gz_uncompress(String compressed) { DValue request; request["op"] = "gz_uncompress"; request["src"] = compressed; DValue response = wasm_zip_call(request); return(response["result"].to_string()); } static DValue wasm_units_call(DValue request) { return(wasm_sized_hostcall(request, uce_host_units)); } DValue unit_info(String path) { DValue request; request["op"] = "info"; request["path"] = path; DValue response = wasm_units_call(request); DValue* result = response.key("result"); return(result ? *result : DValue()); } StringList units_list() { DValue request; request["op"] = "list"; DValue response = wasm_units_call(request); StringList result; DValue* items = response.key("result"); if(items) items->each([&](const DValue& value, String) { result.push_back(value.to_string()); }); return(result); } bool unit_compile(String path) { DValue request; request["op"] = "compile"; request["path"] = path; DValue response = wasm_units_call(request); return(response["ok"].to_bool()); } static DValue wasm_unit_call_result; static DValue wasm_mysql_call(DValue request) { return(wasm_sized_hostcall(request, uce_host_mysql)); } bool MySQL::connect(String host, String username, String password) { DValue request; request["op"] = "connect"; request["host"] = host; request["username"] = username; request["password"] = password; DValue response = wasm_mysql_call(request); u64 handle = response["handle"].to_u64(); connection = (void*)(uintptr_t)handle; _preload_next_error_code = (u32)response["error_code"].to_u64(); statement_info = response["statement_info"].to_string(); return(handle != 0 && _preload_next_error_code == 0); } void MySQL::disconnect() { if(connection) { DValue request; request["op"] = "disconnect"; request["handle"] = (f64)(uintptr_t)connection; wasm_mysql_call(request); connection = 0; } } String MySQL::error() { String result = statement_info; statement_info = ""; _preload_next_error_code = 0; return(result); } String MySQL::escape(String raw, char quote_char) { return(mysql_escape(raw, quote_char)); } String mysql_escape(String raw, char quote_char) { DValue request; request["op"] = "escape"; request["raw"] = raw; request["quote_char"] = String(quote_char > 0 ? 1 : 0, quote_char); DValue response = wasm_mysql_call(request); DValue* result = response.key("result"); return(result ? result->to_string() : raw); } String MySQL::parse_query_parameters(String query, StringMap map) { String result; query.append(1, ' '); u8 mode = 0; char quote = 0; String identifier; for(u32 i = 0; i < query.length(); i++) { char c = query[i]; if(mode == 0) { if(c == ':') { mode = 1; identifier = ""; } else if(c == '"' || c == '\'') { result.append(1, c); mode = 2; quote = c; } else result.append(1, c); } else if(mode == 1) { if(isalnum(c) || c == '_') identifier.append(1, c); else { result.append(escape(map[identifier])); result.append(1, c); mode = 0; } } else if(mode == 2) { if(c == quote) mode = 0; result.append(1, c); } } return(result); } static bool wasm_mysql_has_unquoted_positional_placeholder(String query) { bool quoted = false; char quote = 0; bool escaped = false; for(u32 i = 0; i < query.length(); i++) { char c = query[i]; if(quoted) { if(escaped) { escaped = false; continue; } if(c == '\\') { escaped = true; continue; } if(c == quote) quoted = false; continue; } if(c == '\'' || c == '"') { quoted = true; quote = c; continue; } if(c == '?') return(true); } return(false); } DValue MySQL::query(String q) { affected_rows = 0; if(wasm_mysql_has_unquoted_positional_placeholder(q)) { _preload_next_error_code = 2000; statement_info = "mysql positional ? placeholders are not supported; use named :name placeholders"; return(DValue()); } DValue request; request["op"] = "query"; request["handle"] = (f64)(uintptr_t)connection; request["query"] = q; DValue response = wasm_mysql_call(request); insert_id = response["insert_id"].to_u64(); affected_rows = (u32)response["affected"].to_u64(); _preload_next_error_code = (u32)response["error_code"].to_u64(); statement_info = response["statement_info"].to_string(); DValue* result = response.key("result"); return(result ? *result : DValue()); } DValue MySQL::query(String q, StringMap params) { return(query(parse_query_parameters(q, params))); } DValue MySQL::get_pending_result() { return(DValue()); } // sqlite runs host-side (the host links libsqlite and owns the connections in // a per-workspace handle table). One UCEB2-marshalled hostcall carries // {op,handle,path,query,params} in and {handle,result,insert_id,affected, // error_code,statement_info} out. `connection` holds the host handle (>0). extern "C" size_t uce_host_sqlite(const char* in, size_t in_len, char* out, size_t cap); static DValue wasm_sqlite_call(DValue request) { String encoded = ucb_encode(request); size_t need = uce_host_sqlite(encoded.data(), encoded.size(), 0, 0); if(need == 0) return(DValue()); String buffer(need, 0); size_t got = uce_host_sqlite(encoded.data(), encoded.size(), &buffer[0], need); if(got == 0 || got > need) return(DValue()); DValue response; String error; ucb_decode(String(buffer.data(), got), response, &error); return(response); } void SQLite::set_error(s32 code, String info) { error_code = code; statement_info = info; } bool SQLite::connect(String path) { this->path = path; DValue request; request["op"] = "connect"; request["path"] = path; DValue response = wasm_sqlite_call(request); u64 handle = response["handle"].to_u64(); connection = (void*)(uintptr_t)handle; error_code = (s32)response["error_code"].to_s64(); statement_info = response["statement_info"].to_string(); return(handle != 0 && error_code == 0); } void SQLite::disconnect() { if(connection) { DValue request; request["op"] = "disconnect"; request["handle"] = (f64)(uintptr_t)connection; wasm_sqlite_call(request); connection = 0; } } String SQLite::error() { return(statement_info); } DValue SQLite::query(String q, const StringMap& params) { DValue request; request["op"] = "query"; request["handle"] = (f64)(uintptr_t)connection; request["query"] = q; for(auto& entry : params) request["params"][entry.first] = entry.second; DValue response = wasm_sqlite_call(request); insert_id = response["insert_id"].to_u64(); affected_rows = (u32)response["affected"].to_u64(); error_code = (s32)response["error_code"].to_s64(); statement_info = response["statement_info"].to_string(); DValue* result = response.key("result"); return(result ? *result : DValue()); } DValue SQLite::query(String q) { return(query(q, StringMap())); } bool SQLite::apply_default_pragmas() { return(true); } bool SQLite::bind_params(void* statement, const StringMap& params) { (void)statement; (void)params; return(true); } DValue SQLite::collect_rows(void* statement) { (void)statement; return(DValue()); } SQLite* sqlite_connect(String path) { SQLite* db = new SQLite(); db->request_cleanup_delete = true; db->connect(path); return(db); } void sqlite_disconnect(SQLite* db) { if(db) { db->disconnect(); if(db->request_cleanup_delete) delete db; } } String sqlite_error(SQLite* db) { return(db ? db->error() : String(WASM_DB_UNAVAILABLE)); } DValue sqlite_query(SQLite* db, String q) { return(db ? db->query(q) : DValue()); } DValue sqlite_query(SQLite* db, String q, const StringMap& params) { return(db ? db->query(q, params) : DValue()); } u64 sqlite_insert_id(SQLite* db) { return(db ? db->insert_id : 0); } u32 sqlite_affected_rows(SQLite* db) { return(db ? db->affected_rows : 0); } void cleanup_sqlite_connections() { } void cleanup_mysql_connections() { } static ServerState wasm_server; static Request wasm_request; static String wasm_output; static String wasm_response_meta; // ---- vague-linkage link anchors -------------------------------------------- // Units import libc++ template instantiations they use; --export-all only // exports what the core itself instantiated. Some libc++ internals lack the // hide-from-ABI attribute (the Phase 0 lambda finding), so units emit them as // imports rather than binding locally. This function exists purely to make // the core instantiate — and therefore export — the ones the site tree needs. // Extend it when the loader reports "unresolved import env.". extern "C" void uce_wasm_link_anchors() { StringMap string_map; string_map["k"] = "v"; string_map.erase(String("k")); // __tree::__erase_unique std::map dvalue_map; dvalue_map["k"] = DValue(); dvalue_map.erase(String("k")); std::vector string_list = { "a", "b" }; string_list.erase(string_list.begin()); std::set string_set; string_set.insert("k"); string_set.erase(String("k")); // libc functions units may call that the core itself never references — // taking their address forces them into the link (and --export-all) static void* volatile libc_anchors[] = { (void*)&atof, (void*)&atoi, (void*)&atol, (void*)&atoll, (void*)&strtol, (void*)&strtoul, (void*)&strtoll, (void*)&strtoull, (void*)&strtod, (void*)&strtof, (void*)&qsort, (void*)&bsearch, (void*)&snprintf, (void*)&sscanf, (void*)&memmove, (void*)&strncmp, (void*)&strncpy, // memchr/strchr/strrchr/strstr are C++-overloaded; cast to the C shape (void*)(const void* (*)(const void*, int, size_t))&memchr, (void*)(const char* (*)(const char*, int))&strchr, (void*)(const char* (*)(const char*, int))&strrchr, (void*)(const char* (*)(const char*, const char*))&strstr, // ctype family (int(int)); units use these directly (void*)&isalnum, (void*)&isalpha, (void*)&isblank, (void*)&iscntrl, (void*)&isdigit, (void*)&isgraph, (void*)&islower, (void*)&isprint, (void*)&ispunct, (void*)&isspace, (void*)&isupper, (void*)&isxdigit, (void*)&tolower, (void*)&toupper, }; (void)libc_anchors; } // W3 membrane: the host resolves component/render targets to funcref-table // slots (loading units lazily) and writes the resolved unit path back so // nested relative component resolution keeps working. extern "C" int32_t uce_host_component_resolve( const char* target, size_t target_len, const char* handler, size_t handler_len, const char* current_unit, size_t current_unit_len, char* resolved_buf, size_t resolved_cap); // target → table slot, reset per request (workspaces die with the request, // but a single workspace can render the same component many times) static std::map wasm_component_slots; // These mirror small page-runtime pieces that cannot include compiler.cpp in // the wasm core (compiler.cpp owns parser/clang/cache bookkeeping for the host // build). Kept byte-identical where possible. String component_normalize_path(String name) { name = trim(name); if(name.length() >= 4 && name.substr(name.length() - 4) == ".uce") return(name); return(name + ".uce"); } void component_parse_target(String target, String& file_name, String& render_name) { target = trim(target); render_name = ""; auto render_split_pos = target.find(":"); if(render_split_pos != String::npos) { render_name = trim(target.substr(render_split_pos + 1)); target = trim(target.substr(0, render_split_pos)); } file_name = target; } String component_error_banner(String message) { return("
" + html_escape(message) + "
"); } struct RequestPropsScope { Request* context = 0; DValue previous_props; RequestPropsScope(Request* context, const DValue& props) { this->context = context; if(this->context) { previous_props = this->context->props; this->context->props = props; } } ~RequestPropsScope() { if(context) context->props = previous_props; } }; // A unit is a bag of exported handlers; invoking any of them is one operation — // the host resolves __uce_ in the loaded module to a funcref slot. The // handler is just a string: "render", "component:CARD", "render:VARIANT", // "once", "cli", "websocket", "serve_http:named" — or "exists" (an existence // probe that loads nothing). No per-mode kinds. static s32 wasm_resolve_target(String unit_target, String handler, String* resolved_out = 0) { String cache_key = handler + "\t" + unit_target; bool is_exists = (handler == "exists"); auto cached = wasm_component_slots.find(cache_key); if(cached != wasm_component_slots.end() && !is_exists) return(cached->second); char resolved[512]; String current = context ? context->resources.current_unit_file : ""; s32 slot = uce_host_component_resolve( unit_target.data(), unit_target.size(), handler.data(), handler.size(), current.data(), current.size(), resolved, sizeof(resolved)); if(resolved_out && slot) *resolved_out = String(resolved, strnlen(resolved, sizeof(resolved))); if(!is_exists) wasm_component_slots[cache_key] = slot; return(slot); } String component_resolve(String name) { String file_name, render_name; component_parse_target(trim(name), file_name, render_name); String resolved; if(wasm_resolve_target(file_name, "exists", &resolved)) return(resolved); return(""); } bool component_exists(String name) { return(component_resolve(name) != ""); } // Run a unit's ONCE() handler at most once per request (native // compiler_run_unit_once_if_needed semantics): dedup on the resolved unit // path via request.once_units. The handler emits head assets, etc. static void wasm_run_once(const String& resolved, Request& request) { if(resolved == "") return; if(request.once_units.find(resolved) != request.once_units.end()) return; request.once_units.insert(resolved); s32 once_slot = wasm_resolve_target(resolved, "once"); if(once_slot == 0) return; String previous_unit = request.resources.current_unit_file; request.resources.current_unit_file = resolved; WasmRequestHandler once_handler = (WasmRequestHandler)(uintptr_t)once_slot; once_handler(request); request.resources.current_unit_file = previous_unit; } DValue* unit_call(String file_name, String function_name, DValue* call_param) { String macro = to_upper(trim(function_name)); String handler = ""; if(macro == "RENDER" || macro.rfind("RENDER:", 0) == 0) handler = "render" + (macro.length() > 7 ? ":" + trim(function_name.substr(function_name.find(":") + 1)) : String("")); else if(macro == "COMPONENT" || macro.rfind("COMPONENT:", 0) == 0) handler = "component" + (macro.length() > 10 ? ":" + trim(function_name.substr(function_name.find(":") + 1)) : String("")); else if(macro == "ONCE") handler = "once"; else if(macro == "INIT") handler = "init"; if(handler != "") { String resolved; s32 slot = wasm_resolve_target(file_name, handler, &resolved); if(!slot) { print("Error: unit_call() function '", function_name, "' not found"); return(0); } RequestPropsScope props_scope(context, (call_param ? *call_param : DValue())); if((handler == "render" || handler.rfind("render:", 0) == 0 || handler == "component" || handler.rfind("component:", 0) == 0) && resolved != "") wasm_run_once(resolved, *context); String previous_unit = context->resources.current_unit_file; if(resolved != "") context->resources.current_unit_file = resolved; WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)slot; handler_fn(*context); context->resources.current_unit_file = previous_unit; return(0); } String resolved; s32 slot = wasm_resolve_target(file_name, "export:" + function_name, &resolved); if(!slot) { print("Error: unit_call() function '", function_name, "' not found"); return(0); } String previous_unit = context->resources.current_unit_file; if(resolved != "") context->resources.current_unit_file = resolved; WasmDValueCallHandler handler_fn = (WasmDValueCallHandler)(uintptr_t)slot; DValue* result = handler_fn(call_param); context->resources.current_unit_file = previous_unit; if(result) { wasm_unit_call_result = *result; return(&wasm_unit_call_result); } return(0); } void component_render(String name, DValue props, Request& request) { String file_name, render_name; component_parse_target(trim(name), file_name, render_name); String handler = render_name == "" ? String("component") : "component:" + render_name; String resolved; s32 slot = wasm_resolve_target(file_name, handler, &resolved); if(!slot) { print(component_error_banner("component not found: " + trim(name))); return; } wasm_run_once(resolved, request); RequestPropsScope props_scope(&request, props); String previous_unit = request.resources.current_unit_file; if(resolved != "") request.resources.current_unit_file = resolved; // a wasm function pointer is its index in the shared funcref table; the // host returned the handler's slot, so this is a plain call_indirect WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)slot; handler_fn(request); request.resources.current_unit_file = previous_unit; } void component_render(String name) { DValue props; component_render(name, props, *context); } void component_render(String name, Request& request) { DValue props; component_render(name, props, request); } void component_render(String name, DValue props) { component_render(name, props, *context); } String component(String name, DValue props, Request& request) { ob_start(); component_render(name, props, request); return(ob_get_close()); } String component(String name) { DValue props; return(component(name, props, *context)); } String component(String name, Request& request) { DValue props; return(component(name, props, request)); } String component(String name, DValue props) { return(component(name, props, *context)); } void unit_render(String file_name, Request& request) { String unit_name, render_name; component_parse_target(trim(file_name), unit_name, render_name); String handler = render_name == "" ? String("render") : "render:" + render_name; String resolved; s32 slot = wasm_resolve_target(unit_name, handler, &resolved); if(!slot) { print(component_error_banner("unit not found: " + trim(file_name))); return; } wasm_run_once(resolved, request); String previous_unit = request.resources.current_unit_file; if(resolved != "") request.resources.current_unit_file = resolved; WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)slot; handler_fn(request); request.resources.current_unit_file = previous_unit; } void unit_render(String file_name) { unit_render(file_name, *context); } extern "C" { // Host calls this to invoke the request's entry unit through a named handler // ("render"/"cli"/"websocket"/"serve_http:named"). It is the same resolve + // ONCE() + dispatch path as components — the entry handler is just another // export. The host pre-checks the export exists, so a missing slot is a no-op. void uce_wasm_invoke_entry(const char* path, size_t path_len, const char* handler, size_t handler_len) { // the host always runs uce_wasm_core_init + apply_context before this String file_name(path, path_len); String handler_name(handler, handler_len); String resolved; s32 slot = wasm_resolve_target(trim(file_name), handler_name, &resolved); if(!slot) return; wasm_run_once(resolved, *context); String previous_unit = context->resources.current_unit_file; if(resolved != "") context->resources.current_unit_file = resolved; WasmRequestHandler handler_fn = (WasmRequestHandler)(uintptr_t)slot; handler_fn(*context); context->resources.current_unit_file = previous_unit; } void* uce_alloc(size_t len) { return(malloc(len)); } void uce_free(void* ptr) { free(ptr); } u32 uce_wasm_core_abi_version() { return(6); } int uce_wasm_core_init() { wasm_server.config = default_config(); wasm_request.server = &wasm_server; // the primary output stream must live ON ob_stack (native semantics): // ob_get_close()/ob_close() pop and rebalance against the stack, so a // stream outside it would be orphaned by the first component() capture if(wasm_request.ob_stack.empty()) wasm_request.ob_start(); context = &wasm_request; return(0); } void uce_wasm_core_reset_request() { if(context == 0) uce_wasm_core_init(); wasm_request.call = DValue(); wasm_request.props = DValue(); wasm_request.params.clear(); wasm_request.get.clear(); wasm_request.post.clear(); wasm_request.header.clear(); wasm_request.set_cookies.clear(); wasm_request.response_code = "HTTP/1.1 200 OK"; wasm_request.flags = Request::Flags(); wasm_request.stats = Request::Stats(); for(auto* stream : wasm_request.ob_stack) delete stream; wasm_request.ob_stack.clear(); wasm_request.ob_start(); wasm_output = ""; wasm_response_meta = ""; wasm_request.cookies.clear(); wasm_request.session.clear(); wasm_request.session_id = ""; wasm_request.session_name = ""; wasm_request.session_loaded_hash = ""; wasm_request.out = ""; wasm_request.resources.current_unit_file = ""; wasm_component_slots.clear(); } // Host pushes the UCEB2-encoded request context into a guest buffer // (uce_alloc) and applies it here; mirrors the native param population. int uce_wasm_apply_context(const char* buf, size_t len) { if(context == 0) uce_wasm_core_init(); DValue decoded; String error; if(!ucb_decode(String(buf, len), decoded, &error)) { uce_host_log(3, error.data(), error.size()); return(1); } wasm_request.call = decoded; auto apply_map = [](DValue* source, StringMap& dest) { dest.clear(); if(source) source->each([&](const DValue& item, String key) { dest[key] = item.to_string(); }); }; if(decoded.key("server_config")) apply_map(decoded.key("server_config"), wasm_server.config); apply_map(decoded.key("params"), wasm_request.params); apply_map(decoded.key("get"), wasm_request.get); apply_map(decoded.key("post"), wasm_request.post); apply_map(decoded.key("cookies"), wasm_request.cookies); apply_map(decoded.key("session"), wasm_request.session); if(DValue* session_id = decoded.key("session_id")) wasm_request.session_id = session_id->to_string(); if(DValue* session_name = decoded.key("session_name")) wasm_request.session_name = session_name->to_string(); if(DValue* session_loaded_hash = decoded.key("session_loaded_hash")) wasm_request.session_loaded_hash = session_loaded_hash->to_string(); DValue* entry = decoded.key("entry_unit"); if(entry) wasm_request.resources.current_unit_file = entry->to_string(); DValue* raw_in = decoded.key("in"); wasm_request.in = raw_in ? raw_in->to_string() : ""; // websocket event context: ws_send()/ws_close() capture into the dispatch // list (the workspace owns no connections), which collect() carries back to // the broker. Reset per invocation. wasm_request.connection = DValue(); wasm_request.resources.websocket_connection_state_before = DValue(); wasm_request.resources.websocket_dispatch_commands = DValue(); wasm_request.resources.websocket_dispatch_capture = false; DValue* ws = decoded.key("ws"); if(ws) { wasm_request.resources.websocket_connection_id = (*ws)["connection_id"].to_string(); wasm_request.resources.websocket_scope = (*ws)["scope"].to_string(); wasm_request.resources.websocket_opcode = (u8)(*ws)["opcode"].to_u64(); wasm_request.resources.websocket_is_binary = (*ws)["binary"].to_bool(); wasm_request.resources.websocket_scope_connection_ids.clear(); if(DValue* conns = ws->key("connections")) conns->each([&](const DValue& v, String) { wasm_request.resources.websocket_scope_connection_ids.push_back(v.to_string()); }); wasm_request.resources.websocket_dispatch_capture = true; if(DValue* cstate = ws->key("connection_state")) wasm_request.connection = *cstate; wasm_request.resources.websocket_connection_state_before = wasm_request.connection; } return(0); } Request* uce_wasm_request() { if(context == 0) uce_wasm_core_init(); return(&wasm_request); } // After render: response metadata (status line, headers, cookies, session) // goes back to the host as UCEB2. void uce_wasm_finish_response_meta() { DValue meta; meta["status"] = wasm_request.response_code; for(auto& header : wasm_request.header) meta["headers"][header.first] = header.second; for(auto& cookie : wasm_request.set_cookies) { DValue cookie_value; cookie_value = cookie; meta["cookies"].push(cookie_value); } for(auto& entry : wasm_request.session) meta["session"][entry.first] = entry.second; meta["session_id"] = wasm_request.session_id; meta["session_name"] = wasm_request.session_name; meta["session_loaded_hash"] = wasm_request.session_loaded_hash; bool ws_has_commands = !wasm_request.resources.websocket_dispatch_commands._map.empty(); bool ws_state_changed = false; if(wasm_request.resources.websocket_dispatch_capture) { String prior_state = ucb_encode(wasm_request.resources.websocket_connection_state_before); String current_state = ucb_encode(wasm_request.connection); ws_state_changed = (prior_state != current_state); } // Any unit code (not just WS handlers) may call ws_send/ws_close; whenever the // dispatch list is non-empty, carry it back so the worker can flush it to the // broker. If only connection state changed, flush a command-less state-only // batch so the broker can persist the connection mutation. if(ws_has_commands) meta["ws_commands"] = wasm_request.resources.websocket_dispatch_commands; if(ws_state_changed) meta["ws_connection_state"] = wasm_request.connection; wasm_response_meta = ucb_encode(meta); } const char* uce_wasm_response_meta_data() { return(wasm_response_meta.data()); } size_t uce_wasm_response_meta_size() { return(wasm_response_meta.size()); } void uce_print_bytes(const char* data, size_t len) { if(context == 0) uce_wasm_core_init(); if(context->ob && data && len) context->ob->write(data, len); } void uce_wasm_finish_output() { // ob_stack[0] is the request's primary stream; nested captures above it // belong to unbalanced ob_start() calls and are intentionally ignored wasm_output = wasm_request.ob_stack.empty() ? String("") : wasm_request.ob_stack[0]->str(); } const char* uce_wasm_output_data() { return(wasm_output.data()); } size_t uce_wasm_output_size() { return(wasm_output.size()); } }