import userWasm from "./__WASM_NAME__.wasm";
let userInstance = null;
let userMemory = null;
let userExports = null;
let pending = null;
let cachedBuffer = null;
let cachedDataView = null;
let cachedUint8 = null;
function refreshViews() {
if (cachedBuffer !== userMemory.buffer) {
cachedBuffer = userMemory.buffer;
cachedDataView = new DataView(cachedBuffer);
cachedUint8 = new Uint8Array(cachedBuffer);
}
}
const encoder = new TextEncoder();
const decoder = new TextDecoder("utf-8");
function writeAverString(text) {
const upper = text.length * 3;
const ptr = userExports.alloc(upper + 8);
refreshViews();
const { written } = encoder.encodeInto(
text,
cachedUint8.subarray(ptr + 8, ptr + 8 + upper),
);
cachedDataView.setUint32(ptr, written, true);
cachedDataView.setUint32(ptr + 4, 0, true);
return ptr;
}
function readString(ptr, len) {
refreshViews();
return decoder.decode(cachedUint8.subarray(ptr, ptr + len));
}
function makeAverImports() {
return {
console_print: (ptr, len) => console.log(readString(ptr, len)),
console_error: (ptr, len) => console.error(readString(ptr, len)),
console_warn: (ptr, len) => console.warn(readString(ptr, len)),
time_unixMs: () => BigInt(Date.now()),
random_int: (lo, hi) => {
const span = hi - lo + 1n;
return lo + BigInt(Math.floor(Math.random() * Number(span)));
},
random_float: () => Math.random(),
request_method: () => writeAverString(pending?.req?.method ?? "GET"),
request_url: () => writeAverString(
pending?.req ? new URL(pending.req.url).pathname : "/",
),
request_query: () => writeAverString(
pending?.req ? new URL(pending.req.url).search.slice(1) : "",
),
request_body: () => {
return writeAverString(pending?.body ?? "");
},
request_headers_load: () => {
const h = pending?.req?.headers;
if (!h) return 0;
return buildHeadersMap(h);
},
response_text: (status, ptr, len) => {
const existingHeaders = pending.response?.headers ?? [];
pending.response = {
status,
body: readString(ptr, len),
headers: existingHeaders,
};
return 1;
},
response_set_header: (namePtr, nameLen, valuePtr, valueLen) => {
if (!pending.response) pending.response = { headers: [] };
if (!pending.response.headers) pending.response.headers = [];
pending.response.headers.push([
readString(namePtr, nameLen),
readString(valuePtr, valueLen),
]);
},
env_get: (namePtr, nameLen) => {
const name = readString(namePtr, nameLen);
const value = pending?.env?.[name];
if (typeof value !== "string") return -1;
return writeAverString(value);
},
env_set: (_namePtr, _nameLen, _valuePtr, _valueLen) => {},
http_clear_request_headers: () => {
pending.requestHeaders = [];
},
http_add_request_header: (namePtr, nameLen, valuePtr, valueLen) => {
if (!pending.requestHeaders) pending.requestHeaders = [];
pending.requestHeaders.push([
readString(namePtr, nameLen),
readString(valuePtr, valueLen),
]);
},
http_send: makeSuspendingHttpSend(),
print_value: (tag, val) => console.log(formatValueForHost(tag, val)),
format_value: (tag, val) => {
const text = formatValueForHost(tag, val);
const ptr = writeAverString(text);
const len = cachedDataView.getUint32(ptr, true);
return [ptr + 8, len];
},
};
}
function makeSuspendingHttpSend() {
if (typeof WebAssembly.Suspending !== "function") {
return (_m, _ml, _u, _ul, _b, _bl, _c, _cl) => {
const err = writeAverString(
"Http.send: host has no JSPI; cannot await fetch synchronously",
);
return [0n, 0, 0, err];
};
}
return new WebAssembly.Suspending(async (
methodPtr, methodLen, urlPtr, urlLen,
bodyPtr, bodyLen, ctPtr, ctLen,
) => {
const method = readString(methodPtr, methodLen);
const url = readString(urlPtr, urlLen);
const body = bodyLen > 0 ? readString(bodyPtr, bodyLen) : null;
const contentType = ctLen > 0 ? readString(ctPtr, ctLen) : null;
const init = { method };
const headers = new Headers();
if (contentType) headers.set("content-type", contentType);
for (const [name, value] of pending.requestHeaders ?? []) {
headers.append(name, value);
}
if ([...headers].length > 0) init.headers = headers;
if (body !== null && method !== "GET" && method !== "HEAD") {
init.body = body;
}
try {
const resp = await fetch(url, init);
const respBody = await resp.text();
const bodyHandle = writeAverString(respBody);
const headersHandle = buildHeadersMap(resp.headers);
return [BigInt(resp.status), bodyHandle, headersHandle, 0];
} catch (e) {
const msg = e?.message ?? String(e);
const errHandle = writeAverString(msg);
return [0n, 0, 0, errHandle];
}
});
}
const OBJ_LIST_CONS = 4n;
const OBJ_TUPLE = 7n;
const KIND_STR = 3;
function consCell(headValue, tail, headPtrFlag) {
const ptr = userExports.alloc(24);
refreshViews(); const high = (Number(OBJ_LIST_CONS) << 24) | (headPtrFlag & 0xffff);
cachedDataView.setUint32(ptr, 2, true);
cachedDataView.setUint32(ptr + 4, high, true);
cachedDataView.setBigUint64(ptr + 8, headValue, true);
cachedDataView.setInt32(ptr + 16, tail | 0, true);
cachedDataView.setInt32(ptr + 20, 0, true);
return ptr;
}
function tupleStrList(namePtr, valuesListPtr) {
const ptr = userExports.alloc(24);
refreshViews(); const high = (Number(OBJ_TUPLE) << 24) | 0x3;
cachedDataView.setUint32(ptr, 2, true);
cachedDataView.setUint32(ptr + 4, high, true);
cachedDataView.setUint32(ptr + 8, namePtr, true);
cachedDataView.setUint32(ptr + 12, 0, true);
cachedDataView.setUint32(ptr + 16, valuesListPtr, true);
cachedDataView.setUint32(ptr + 20, 0, true);
return ptr;
}
function buildHeadersMap(jsHeaders) {
if (typeof userExports.rt_map_from_list !== "function") {
return 0;
}
let listTail = 0;
let cookies = [];
if (typeof jsHeaders.getSetCookie === "function") {
cookies = jsHeaders.getSetCookie();
}
if (cookies.length > 0) {
let valueList = 0;
for (let i = cookies.length - 1; i >= 0; i--) {
const cookieStr = writeAverString(cookies[i]);
valueList = consCell(BigInt(cookieStr), valueList, 1);
}
const nameStr = writeAverString("set-cookie");
const tuple = tupleStrList(nameStr, valueList);
listTail = consCell(BigInt(tuple), listTail, 1);
}
for (const [name, value] of jsHeaders) {
if (name.toLowerCase() === "set-cookie") continue;
const nameStr = writeAverString(name);
const valueStr = writeAverString(value);
const valueList = consCell(BigInt(valueStr), 0, 1);
const tuple = tupleStrList(nameStr, valueList);
listTail = consCell(BigInt(tuple), listTail, 1);
}
return userExports.rt_map_from_list(listTail, KIND_STR, 1);
}
function formatValueForHost(tag, val) {
switch (tag) {
case 0: return String(val); case 1: {
const buf = new ArrayBuffer(8);
new BigInt64Array(buf)[0] = val;
return String(new Float64Array(buf)[0]);
}
case 2: return Number(val) ? "true" : "false";
case 3: {
const ptr = Number(val & 0xffffffffn);
if (ptr === 0) return "";
refreshViews();
const len = cachedDataView.getUint32(ptr, true);
return decoder.decode(cachedUint8.subarray(ptr + 8, ptr + 8 + len));
}
default: return "<heap>";
}
}
async function instantiate() {
const imports = { aver: makeAverImports() };
const instance = await WebAssembly.instantiate(userWasm, imports);
userInstance = instance;
userExports = instance.exports;
userMemory = instance.exports.memory;
if (
typeof instance.exports.aver_http_handle === "function" &&
typeof WebAssembly.promising === "function"
) {
globalThis.__aver_http_handle = WebAssembly.promising(
instance.exports.aver_http_handle,
);
} else if (typeof instance.exports.aver_http_handle === "function") {
globalThis.__aver_http_handle = instance.exports.aver_http_handle;
}
return instance;
}
let instancePromise = null;
export default {
async fetch(request, env, ctx) {
if (!instancePromise) instancePromise = instantiate();
const user = await instancePromise;
if (typeof user.exports.aver_http_handle === "function") {
const body = await request.text();
pending = { req: request, body, env, response: null, requestHeaders: [] };
try {
await globalThis.__aver_http_handle(0);
} catch (e) {
console.error("aver_http_handle threw:", e);
pending = null;
return new Response("Internal error\n", { status: 500 });
}
const resp = pending.response;
pending = null;
if (!resp) {
return new Response(
"Handler returned without calling Response builder\n",
{ status: 500 },
);
}
const headers = new Headers();
for (const [name, value] of resp.headers ?? []) {
headers.append(name, value);
}
if (!headers.has("content-type")) {
headers.set("content-type", "text/plain;charset=utf-8");
}
return new Response(resp.body, { status: resp.status, headers });
}
if (typeof user.exports._start === "function") {
user.exports._start();
} else if (typeof user.exports.main === "function") {
user.exports.main();
}
return new Response("Aver program ran. Output went to wrangler logs.\n", {
headers: { "content-type": "text/plain;charset=utf-8" },
});
},
};