1// Client for the stan-wasm-server compile endpoint (the same protocol as
2// stan-playground): POST the .stan source, get a model id, and reference the
3// compiled emscripten module at /download/{model_id}/main.js. The server
4// caches compilations by source hash; on top of that we keep a small
5// in-session cache so re-running an unchanged model skips the round trip
6// (validated with a HEAD request, since server redeploys invalidate ids).
8export interface CompileResult {
9 mainJsUrl?: string;
10 error?: string;
11}
13const cache = new Map<string, string>();
15export async function compileStanProgram(
16 serverUrl: string,
17 stanProgram: string,
18 onStatus: (message: string) => void,
19): Promise<CompileResult> {
20 const cacheKey = `${serverUrl}\0${stanProgram}`;
22 const cached = cache.get(cacheKey);
23 if (cached && await urlExists(cached)) {
24 onStatus('compiled (cached)');
25 return { mainJsUrl: cached };
26 }
28 try {
29 onStatus('compiling...');
30 const response = await fetch(`${serverUrl}/compile`, {
31 method: 'POST',
32 headers: {
33 'Content-Type': 'text/plain',
34 // the stan-wasm-server passcode (fixed, same as stan-playground)
35 'Authorization': 'Bearer 1234',
36 },
37 body: stanProgram,
38 });
39 if (!response.ok) {
40 return { error: `compilation failed: ${await messageOrStatus(response)}` };
41 }
42 const { model_id } = await response.json();
43 const mainJsUrl = `${serverUrl}/download/${model_id}/main.js`;
45 onStatus('checking download of main.js');
46 if (!await urlExists(mainJsUrl)) {
47 return { error: `compiled, but main.js is not downloadable from ${mainJsUrl}` };
48 }
50 cache.set(cacheKey, mainJsUrl);
51 onStatus('compiled');
52 return { mainJsUrl };
53 } catch (error) {
54 return { error: `compilation request failed: ${error} (is the compile server at ${serverUrl} running, and does its CORS allowlist include this origin?)` };
55 }
56}
58async function urlExists(url: string): Promise<boolean> {
59 try {
60 const response = await fetch(url, { method: 'HEAD' });
61 return response.ok;
62 } catch {
63 return false;
64 }
65}
67async function messageOrStatus(response: Response): Promise<string> {
68 try {
69 const body = await response.json();
70 return body?.message ?? response.statusText;
71 } catch {
72 return response.statusText;
73 }
74}