aver-lang 0.18.0

VM and transpiler for Aver, a statically-typed language designed for AI-assisted development
Documentation
// Generated by `aver compile --preset cloudflare`. Edit only if you
// know the wasm-gc string transport — `aver/*` host imports below
// match the ABI the compiler emits with `--target wasm-gc --handler
// <fn>`. The companion `aver_http_handle()` synthesised wrapper:
//
//   1. reads request fields via `Request.*` host imports,
//   2. allocates an `HttpRequest` struct,
//   3. calls the user's `<handler>(req: HttpRequest) -> HttpResponse`,
//   4. walks the response's `headers: Map<String, List<String>>`
//      and dispatches one `Response.setHeader(name, value)` per
//      (key, value) pair,
//   5. finalises with `Response.text(status, body)`.
//
// Routing, response shape, and header semantics are decided in Aver
// — this file's only job is translating JS strings ↔ wasm-gc
// `(array i8)` carriers via the LM transport (`__rt_string_*`) and
// constructing the request-headers map via the per-instance Map
// helpers (`__rt_map_string_list_string_*` + `__rt_list_string_cons`).

import userWasm from "./__WASM_NAME__.wasm";

let exports = null;
let instancePromise = null;
let pendingReq = null;
let pendingBody = "";
let pendingResponse = null;

const encoder = new TextEncoder();
const decoder = new TextDecoder("utf-8");

function memU8() {
  // ArrayBuffer detaches on memory.grow, so re-view per call.
  return new Uint8Array(exports.memory.buffer);
}

function ensurePages(needed) {
  const current = exports.__rt_memory_pages();
  if (needed > current) exports.__rt_memory_grow(needed - current);
}

function jsToAver(text) {
  const s = text ?? "";
  // 3-byte worst-case UTF-8 per JS char (4 with surrogates, but those
  // span two JS chars so the bound holds). Grow LM upfront so
  // `encodeInto` never trips the buffer end.
  const upperBytes = s.length * 3;
  ensurePages(((upperBytes + 65535) >> 16) || 1);
  const { written } = encoder.encodeInto(s, memU8());
  return exports.__rt_string_from_lm(written);
}

function averToJs(s) {
  const len = exports.__rt_string_to_lm(s);
  return decoder.decode(memU8().subarray(0, len));
}

function buildHeadersMap(jsHeaders) {
  // Collapse the JS Headers iterator into [name, [values...]] pairs.
  // Same-name fields per RFC 9110 §5.3 collapse into one comma-joined
  // entry by the standard iterator; for Set-Cookie (RFC 6265, must
  // stay distinct) we use `getSetCookie()` when the host provides it.
  const grouped = new Map();
  for (const [name, value] of jsHeaders) {
    const lower = name.toLowerCase();
    if (lower === "set-cookie") continue; // handled below
    if (!grouped.has(lower)) grouped.set(lower, [value]);
    else grouped.get(lower).push(value);
  }
  if (typeof jsHeaders.getSetCookie === "function") {
    const cookies = jsHeaders.getSetCookie();
    if (cookies.length > 0) grouped.set("set-cookie", cookies);
  }

  let map = exports.__rt_map_string_list_string_empty();
  for (const [name, values] of grouped) {
    // Build the `List<String>` LIFO — cons stacks head onto tail —
    // then walk the JS values in reverse so the resulting Aver list
    // surfaces them in source order.
    let list = null;
    for (let i = values.length - 1; i >= 0; i--) {
      list = exports.__rt_list_string_cons(jsToAver(values[i]), list);
    }
    map = exports.__rt_map_string_list_string_set(map, jsToAver(name), list);
  }
  return map;
}

async function init() {
  // Every aver-host import declared by `--target wasm-gc` carries a
  // trailing `i32 caller_fn_idx` — the index into the exported
  // `__caller_fn_name(idx)` table that identifies which Aver fn
  // emitted the call (used by record/replay for caller_fn
  // attribution). The bridge here doesn't surface that to JS, so
  // every stub accepts and ignores the trailing arg. Host arity
  // validation in V8 is forgiving (extra args drop on the floor),
  // but writing the param explicitly keeps the stubs in sync with
  // the wasm-gc ABI for stricter hosts (wasmtime CLI, Bun) and
  // documents the contract.
  const imports = {
    aver: {
      time_unix_ms: (_caller) => BigInt(Date.now()),
      request_method: (_caller) => jsToAver(pendingReq.method),
      request_url: (_caller) => jsToAver(new URL(pendingReq.url).pathname),
      request_query: (_caller) => jsToAver(new URL(pendingReq.url).search.slice(1)),
      request_body: (_caller) => jsToAver(pendingBody),
      request_headers_load: (_caller) => buildHeadersMap(pendingReq.headers),
      response_text: (status, bodyRef, _caller) => {
        pendingResponse.status = Number(status);
        pendingResponse.body = averToJs(bodyRef);
      },
      response_set_header: (nameRef, valueRef, _caller) => {
        pendingResponse.headers.push([averToJs(nameRef), averToJs(valueRef)]);
      },
      // Console.* — handlers that reach into Console.print() for
      // debugging would otherwise fail at instantiate with
      // "missing import". Forward to Workers' built-in console;
      // Cloudflare ships a logs sidecar that surfaces these.
      console_print: (msgRef, _caller) => { console.log(averToJs(msgRef)); },
      console_error: (msgRef, _caller) => { console.error(averToJs(msgRef)); },
      console_warn:  (msgRef, _caller) => { console.warn(averToJs(msgRef)); },
    },
  };
  const instance = await WebAssembly.instantiate(userWasm, imports);
  exports = instance.exports;
}

export default {
  async fetch(request) {
    if (!instancePromise) instancePromise = init();
    await instancePromise;

    pendingBody = await request.text();
    pendingReq = request;
    pendingResponse = { status: 200, body: "", headers: [] };
    try {
      exports.aver_http_handle();
    } finally {
      pendingReq = null;
      pendingBody = "";
    }

    const r = pendingResponse;
    pendingResponse = null;
    const headers = new Headers();
    for (const [name, value] of r.headers) headers.append(name, value);
    if (!headers.has("content-type")) {
      headers.set("content-type", "text/plain;charset=utf-8");
    }
    return new Response(r.body, { status: r.status, headers });
  },
};