@mcmcjs/stan-wasm 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/dist/chunk-3CMLUNUW.js +206 -0
- package/dist/chunk-3CMLUNUW.js.map +1 -0
- package/dist/index.cjs +229 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.cjs +377 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +17 -0
- package/dist/react/index.d.ts +17 -0
- package/dist/react/index.js +163 -0
- package/dist/react/index.js.map +1 -0
- package/dist/sampler-4wflmHRA.d.cts +76 -0
- package/dist/sampler-4wflmHRA.d.ts +76 -0
- package/dist/worker.js +360 -0
- package/dist/worker.js.map +1 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shravan Goswami
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# @mcmcjs/stan-wasm
|
|
2
|
+
|
|
3
|
+
Run Stan models in the browser.
|
|
4
|
+
This package wraps [TinyStan](https://github.com/WardBrian/tinystan) compiled to WebAssembly in a Web Worker, adds a typed client for a Stan-to-WASM compile server, and ships an optional React adapter.
|
|
5
|
+
|
|
6
|
+
The design of the worker protocol and progress parsing follows the approach of [stan-playground](https://github.com/flatironinstitute/stan-playground) by the Flatiron Institute.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @mcmcjs/stan-wasm
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
React is an optional peer dependency, only needed for the `./react` entry point.
|
|
15
|
+
|
|
16
|
+
## How it works
|
|
17
|
+
|
|
18
|
+
Stan models are compiled to WebAssembly by a compile server: the client posts Stan source to `POST /compile` and receives a model id, then the sampler loads the compiled module from `GET /download/<id>/main.js` inside a Web Worker and runs NUTS via TinyStan.
|
|
19
|
+
The compile server URL is always explicit; nothing is baked in.
|
|
20
|
+
|
|
21
|
+
## Quickstart
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { StanSampler } from "@mcmcjs/stan-wasm";
|
|
25
|
+
|
|
26
|
+
const sampler = new StanSampler({
|
|
27
|
+
compileServerUrl: "http://localhost:8080",
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
await sampler.compile(stanCode);
|
|
31
|
+
const run = await sampler.sample({
|
|
32
|
+
data,
|
|
33
|
+
inits,
|
|
34
|
+
num_chains: 4,
|
|
35
|
+
num_warmup: 1000,
|
|
36
|
+
num_samples: 1000,
|
|
37
|
+
init_radius: 2,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
console.log(run.paramNames, run.draws);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
With Vite, pass the worker URL explicitly:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import workerUrl from "@mcmcjs/stan-wasm/worker?url";
|
|
47
|
+
|
|
48
|
+
const sampler = new StanSampler({ compileServerUrl, workerUrl });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## React
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { useStanWasmSampler, StanWasmSamplerProvider } from "@mcmcjs/stan-wasm/react";
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The hook wraps the sampler in a compile/sample state machine; the provider shares one sampler through context.
|
|
58
|
+
|
|
59
|
+
## Browser requirements
|
|
60
|
+
|
|
61
|
+
Multithreaded WebAssembly needs `SharedArrayBuffer`, so the page must be served with:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
Cross-Origin-Embedder-Policy: require-corp
|
|
65
|
+
Cross-Origin-Opener-Policy: same-origin
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
MIT
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// src/compile-client.ts
|
|
2
|
+
var trimTrailingSlash = (url) => url.replace(/\/+$/, "");
|
|
3
|
+
async function compileStanCode(req) {
|
|
4
|
+
const base = trimTrailingSlash(req.serverUrl);
|
|
5
|
+
const headers = { "Content-Type": "text/plain" };
|
|
6
|
+
if (req.passcode) {
|
|
7
|
+
headers.Authorization = `Bearer ${req.passcode}`;
|
|
8
|
+
}
|
|
9
|
+
const res = await fetch(`${base}/compile`, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers,
|
|
12
|
+
body: req.stanCode,
|
|
13
|
+
signal: req.signal
|
|
14
|
+
});
|
|
15
|
+
if (!res.ok) {
|
|
16
|
+
const text = await res.text().catch(() => "");
|
|
17
|
+
throw new Error(`Stan WASM compile failed (HTTP ${res.status}): ${text || res.statusText}`);
|
|
18
|
+
}
|
|
19
|
+
const json = await res.json();
|
|
20
|
+
if (typeof json.model_id !== "string" || json.model_id.length === 0) {
|
|
21
|
+
throw new Error("Stan WASM compile response did not include a model_id");
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
modelId: json.model_id,
|
|
25
|
+
mainJsUrl: `${base}/download/${json.model_id}/main.js`
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async function probeServer(opts) {
|
|
29
|
+
const base = trimTrailingSlash(opts.serverUrl);
|
|
30
|
+
try {
|
|
31
|
+
const res = await fetch(`${base}/probe`, { method: "POST" });
|
|
32
|
+
return res.ok;
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/sampler.ts
|
|
39
|
+
var defaultWorkerUrl = () => {
|
|
40
|
+
try {
|
|
41
|
+
return new URL("./worker.js", import.meta.url);
|
|
42
|
+
} catch {
|
|
43
|
+
throw new Error(
|
|
44
|
+
'@mcmcjs/stan-wasm could not resolve its worker URL automatically. Pass `workerUrl` explicitly to the StanSampler constructor (e.g. import workerUrl from "@mcmcjs/stan-wasm/worker?url" in Vite).'
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var calculateRefreshRate = (cfg) => {
|
|
49
|
+
const total = (cfg.num_warmup + cfg.num_samples) * cfg.num_chains;
|
|
50
|
+
const twoHalfPercent = Math.floor(total / 40);
|
|
51
|
+
const nearestTen = Math.round(twoHalfPercent / 10) * 10;
|
|
52
|
+
return Math.max(15, nearestTen);
|
|
53
|
+
};
|
|
54
|
+
var StanSampler = class {
|
|
55
|
+
#compileServerUrl;
|
|
56
|
+
#passcode;
|
|
57
|
+
#workerUrl;
|
|
58
|
+
#worker = null;
|
|
59
|
+
#lastCompile = null;
|
|
60
|
+
#samplingStartTimeSec = 0;
|
|
61
|
+
#loadResolve = null;
|
|
62
|
+
#loadReject = null;
|
|
63
|
+
#sampleResolve = null;
|
|
64
|
+
#sampleReject = null;
|
|
65
|
+
#activeListener = null;
|
|
66
|
+
constructor(opts) {
|
|
67
|
+
this.#compileServerUrl = opts.compileServerUrl;
|
|
68
|
+
this.#passcode = opts.passcode;
|
|
69
|
+
this.#workerUrl = opts.workerUrl ?? defaultWorkerUrl();
|
|
70
|
+
}
|
|
71
|
+
get isReady() {
|
|
72
|
+
return this.#worker !== null && this.#lastCompile !== null;
|
|
73
|
+
}
|
|
74
|
+
get lastCompile() {
|
|
75
|
+
return this.#lastCompile;
|
|
76
|
+
}
|
|
77
|
+
async compile(stanCode, signal) {
|
|
78
|
+
this.#teardown(new Error("Superseded by a new compile()"));
|
|
79
|
+
const result = await compileStanCode({
|
|
80
|
+
serverUrl: this.#compileServerUrl,
|
|
81
|
+
passcode: this.#passcode,
|
|
82
|
+
stanCode,
|
|
83
|
+
signal
|
|
84
|
+
});
|
|
85
|
+
await this.#startWorker(result.mainJsUrl);
|
|
86
|
+
this.#lastCompile = result;
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
sample(config, listener) {
|
|
90
|
+
if (!this.#worker) {
|
|
91
|
+
return Promise.reject(
|
|
92
|
+
new Error("No model loaded \u2014 call compile() and await it before sample()")
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (this.#sampleResolve !== null) {
|
|
96
|
+
return Promise.reject(new Error("A sampling run is already in progress on this sampler"));
|
|
97
|
+
}
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
this.#sampleResolve = resolve;
|
|
100
|
+
this.#sampleReject = reject;
|
|
101
|
+
this.#activeListener = listener ?? null;
|
|
102
|
+
this.#samplingStartTimeSec = performance.now() / 1e3;
|
|
103
|
+
const fullConfig = {
|
|
104
|
+
...config,
|
|
105
|
+
seed: config.seed ?? Math.floor(Math.random() * 2 ** 32),
|
|
106
|
+
refresh: config.refresh ?? calculateRefreshRate(config)
|
|
107
|
+
};
|
|
108
|
+
this.#worker?.postMessage({ kind: "sample" /* Sample */, sampleConfig: fullConfig });
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
cancel() {
|
|
112
|
+
this.#teardown(new Error("Cancelled"));
|
|
113
|
+
}
|
|
114
|
+
#teardown(rejectionReason) {
|
|
115
|
+
if (this.#worker) {
|
|
116
|
+
this.#worker.terminate();
|
|
117
|
+
this.#worker = null;
|
|
118
|
+
}
|
|
119
|
+
this.#loadReject?.(rejectionReason);
|
|
120
|
+
this.#sampleReject?.(rejectionReason);
|
|
121
|
+
this.#loadResolve = null;
|
|
122
|
+
this.#loadReject = null;
|
|
123
|
+
this.#sampleResolve = null;
|
|
124
|
+
this.#sampleReject = null;
|
|
125
|
+
this.#activeListener = null;
|
|
126
|
+
this.#lastCompile = null;
|
|
127
|
+
}
|
|
128
|
+
#startWorker(url) {
|
|
129
|
+
const worker = new Worker(this.#workerUrl, { type: "module" });
|
|
130
|
+
this.#worker = worker;
|
|
131
|
+
worker.onmessage = (event) => {
|
|
132
|
+
this.#onWorkerMessage(event.data);
|
|
133
|
+
};
|
|
134
|
+
worker.onerror = (event) => {
|
|
135
|
+
const message = event.message || "Worker error";
|
|
136
|
+
const err = new Error(`Stan WASM worker error: ${message}`);
|
|
137
|
+
if (this.#loadReject) {
|
|
138
|
+
this.#loadReject(err);
|
|
139
|
+
this.#loadResolve = null;
|
|
140
|
+
this.#loadReject = null;
|
|
141
|
+
} else if (this.#sampleReject) {
|
|
142
|
+
this.#sampleReject(err);
|
|
143
|
+
this.#sampleResolve = null;
|
|
144
|
+
this.#sampleReject = null;
|
|
145
|
+
this.#activeListener = null;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
return new Promise((resolve, reject) => {
|
|
149
|
+
this.#loadResolve = resolve;
|
|
150
|
+
this.#loadReject = reject;
|
|
151
|
+
worker.postMessage({ kind: "load" /* Load */, url });
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
#onWorkerMessage(msg) {
|
|
155
|
+
switch (msg.kind) {
|
|
156
|
+
case "modelLoaded" /* ModelLoaded */: {
|
|
157
|
+
const resolve = this.#loadResolve;
|
|
158
|
+
this.#loadResolve = null;
|
|
159
|
+
this.#loadReject = null;
|
|
160
|
+
resolve?.();
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
case "modelLoadError" /* ModelLoadError */: {
|
|
164
|
+
const reject = this.#loadReject;
|
|
165
|
+
this.#loadResolve = null;
|
|
166
|
+
this.#loadReject = null;
|
|
167
|
+
reject?.(new Error(`Stan WASM model load failed: ${msg.error}`));
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case "progress" /* Progress */: {
|
|
171
|
+
this.#activeListener?.({ type: "progress", report: msg.report });
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
case "stanReturn" /* StanReturn */: {
|
|
175
|
+
const resolve = this.#sampleResolve;
|
|
176
|
+
const reject = this.#sampleReject;
|
|
177
|
+
const listener = this.#activeListener;
|
|
178
|
+
this.#sampleResolve = null;
|
|
179
|
+
this.#sampleReject = null;
|
|
180
|
+
this.#activeListener = null;
|
|
181
|
+
if (msg.error !== null) {
|
|
182
|
+
listener?.({ type: "error", error: msg.error });
|
|
183
|
+
reject?.(new Error(`Stan sampling failed: ${msg.error}`));
|
|
184
|
+
} else {
|
|
185
|
+
const run = {
|
|
186
|
+
draws: msg.draws,
|
|
187
|
+
paramNames: msg.paramNames,
|
|
188
|
+
computeTimeSec: performance.now() / 1e3 - this.#samplingStartTimeSec,
|
|
189
|
+
consoleMessages: msg.consoleMessages,
|
|
190
|
+
sampleConfig: msg.sampleConfig
|
|
191
|
+
};
|
|
192
|
+
listener?.({ type: "done", run });
|
|
193
|
+
resolve?.(run);
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export {
|
|
202
|
+
compileStanCode,
|
|
203
|
+
probeServer,
|
|
204
|
+
StanSampler
|
|
205
|
+
};
|
|
206
|
+
//# sourceMappingURL=chunk-3CMLUNUW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/compile-client.ts","../src/sampler.ts"],"sourcesContent":["import type { CompileResult } from \"./types\";\n\nexport interface CompileRequest {\n serverUrl: string;\n passcode?: string;\n stanCode: string;\n signal?: AbortSignal;\n}\n\nconst trimTrailingSlash = (url: string): string => url.replace(/\\/+$/, \"\");\n\nexport async function compileStanCode(req: CompileRequest): Promise<CompileResult> {\n const base = trimTrailingSlash(req.serverUrl);\n const headers: Record<string, string> = { \"Content-Type\": \"text/plain\" };\n if (req.passcode) {\n headers.Authorization = `Bearer ${req.passcode}`;\n }\n const res = await fetch(`${base}/compile`, {\n method: \"POST\",\n headers,\n body: req.stanCode,\n signal: req.signal,\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(`Stan WASM compile failed (HTTP ${res.status}): ${text || res.statusText}`);\n }\n const json = (await res.json()) as { model_id?: unknown };\n if (typeof json.model_id !== \"string\" || json.model_id.length === 0) {\n throw new Error(\"Stan WASM compile response did not include a model_id\");\n }\n return {\n modelId: json.model_id,\n mainJsUrl: `${base}/download/${json.model_id}/main.js`,\n };\n}\n\nexport async function probeServer(opts: { serverUrl: string }): Promise<boolean> {\n const base = trimTrailingSlash(opts.serverUrl);\n try {\n const res = await fetch(`${base}/probe`, { method: \"POST\" });\n return res.ok;\n } catch {\n return false;\n }\n}\n","import { compileStanCode } from \"./compile-client\";\nimport {\n type CompileResult,\n type SampleConfig,\n type SamplerListener,\n type StanRun,\n WorkerReply,\n type WorkerReplyMessage,\n WorkerRequest,\n} from \"./types\";\n\nexport interface StanSamplerOptions {\n compileServerUrl: string;\n passcode?: string;\n workerUrl?: string | URL;\n}\n\nconst defaultWorkerUrl = (): URL => {\n try {\n return new URL(\"./worker.js\", import.meta.url);\n } catch {\n throw new Error(\n '@mcmcjs/stan-wasm could not resolve its worker URL automatically. Pass `workerUrl` explicitly to the StanSampler constructor (e.g. import workerUrl from \"@mcmcjs/stan-wasm/worker?url\" in Vite).',\n );\n }\n};\n\nconst calculateRefreshRate = (cfg: SampleConfig): number => {\n const total = (cfg.num_warmup + cfg.num_samples) * cfg.num_chains;\n const twoHalfPercent = Math.floor(total / 40);\n const nearestTen = Math.round(twoHalfPercent / 10) * 10;\n return Math.max(15, nearestTen);\n};\n\nexport class StanSampler {\n readonly #compileServerUrl: string;\n readonly #passcode: string | undefined;\n readonly #workerUrl: string | URL;\n\n #worker: Worker | null = null;\n #lastCompile: CompileResult | null = null;\n #samplingStartTimeSec: number = 0;\n\n #loadResolve: (() => void) | null = null;\n #loadReject: ((err: Error) => void) | null = null;\n #sampleResolve: ((run: StanRun) => void) | null = null;\n #sampleReject: ((err: Error) => void) | null = null;\n #activeListener: SamplerListener | null = null;\n\n constructor(opts: StanSamplerOptions) {\n this.#compileServerUrl = opts.compileServerUrl;\n this.#passcode = opts.passcode;\n this.#workerUrl = opts.workerUrl ?? defaultWorkerUrl();\n }\n\n get isReady(): boolean {\n return this.#worker !== null && this.#lastCompile !== null;\n }\n\n get lastCompile(): CompileResult | null {\n return this.#lastCompile;\n }\n\n async compile(stanCode: string, signal?: AbortSignal): Promise<CompileResult> {\n this.#teardown(new Error(\"Superseded by a new compile()\"));\n const result = await compileStanCode({\n serverUrl: this.#compileServerUrl,\n passcode: this.#passcode,\n stanCode,\n signal,\n });\n await this.#startWorker(result.mainJsUrl);\n this.#lastCompile = result;\n return result;\n }\n\n sample(config: SampleConfig, listener?: SamplerListener): Promise<StanRun> {\n if (!this.#worker) {\n return Promise.reject(\n new Error(\"No model loaded — call compile() and await it before sample()\"),\n );\n }\n if (this.#sampleResolve !== null) {\n return Promise.reject(new Error(\"A sampling run is already in progress on this sampler\"));\n }\n return new Promise<StanRun>((resolve, reject) => {\n this.#sampleResolve = resolve;\n this.#sampleReject = reject;\n this.#activeListener = listener ?? null;\n this.#samplingStartTimeSec = performance.now() / 1000;\n const fullConfig: SampleConfig = {\n ...config,\n seed: config.seed ?? Math.floor(Math.random() * 2 ** 32),\n refresh: config.refresh ?? calculateRefreshRate(config),\n };\n this.#worker?.postMessage({ kind: WorkerRequest.Sample, sampleConfig: fullConfig });\n });\n }\n\n cancel(): void {\n this.#teardown(new Error(\"Cancelled\"));\n }\n\n #teardown(rejectionReason: Error): void {\n if (this.#worker) {\n this.#worker.terminate();\n this.#worker = null;\n }\n this.#loadReject?.(rejectionReason);\n this.#sampleReject?.(rejectionReason);\n this.#loadResolve = null;\n this.#loadReject = null;\n this.#sampleResolve = null;\n this.#sampleReject = null;\n this.#activeListener = null;\n this.#lastCompile = null;\n }\n\n #startWorker(url: string): Promise<void> {\n const worker = new Worker(this.#workerUrl, { type: \"module\" });\n this.#worker = worker;\n worker.onmessage = (event: MessageEvent<WorkerReplyMessage>) => {\n this.#onWorkerMessage(event.data);\n };\n worker.onerror = (event: ErrorEvent) => {\n const message = event.message || \"Worker error\";\n const err = new Error(`Stan WASM worker error: ${message}`);\n if (this.#loadReject) {\n this.#loadReject(err);\n this.#loadResolve = null;\n this.#loadReject = null;\n } else if (this.#sampleReject) {\n this.#sampleReject(err);\n this.#sampleResolve = null;\n this.#sampleReject = null;\n this.#activeListener = null;\n }\n };\n return new Promise<void>((resolve, reject) => {\n this.#loadResolve = resolve;\n this.#loadReject = reject;\n worker.postMessage({ kind: WorkerRequest.Load, url });\n });\n }\n\n #onWorkerMessage(msg: WorkerReplyMessage): void {\n switch (msg.kind) {\n case WorkerReply.ModelLoaded: {\n const resolve = this.#loadResolve;\n this.#loadResolve = null;\n this.#loadReject = null;\n resolve?.();\n break;\n }\n case WorkerReply.ModelLoadError: {\n const reject = this.#loadReject;\n this.#loadResolve = null;\n this.#loadReject = null;\n reject?.(new Error(`Stan WASM model load failed: ${msg.error}`));\n break;\n }\n case WorkerReply.Progress: {\n this.#activeListener?.({ type: \"progress\", report: msg.report });\n break;\n }\n case WorkerReply.StanReturn: {\n const resolve = this.#sampleResolve;\n const reject = this.#sampleReject;\n const listener = this.#activeListener;\n this.#sampleResolve = null;\n this.#sampleReject = null;\n this.#activeListener = null;\n if (msg.error !== null) {\n listener?.({ type: \"error\", error: msg.error });\n reject?.(new Error(`Stan sampling failed: ${msg.error}`));\n } else {\n const run: StanRun = {\n draws: msg.draws,\n paramNames: msg.paramNames,\n computeTimeSec: performance.now() / 1000 - this.#samplingStartTimeSec,\n consoleMessages: msg.consoleMessages,\n sampleConfig: msg.sampleConfig,\n };\n listener?.({ type: \"done\", run });\n resolve?.(run);\n }\n break;\n }\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
StanSampler: () => StanSampler,
|
|
24
|
+
compileStanCode: () => compileStanCode,
|
|
25
|
+
probeServer: () => probeServer
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(src_exports);
|
|
28
|
+
|
|
29
|
+
// src/compile-client.ts
|
|
30
|
+
var trimTrailingSlash = (url) => url.replace(/\/+$/, "");
|
|
31
|
+
async function compileStanCode(req) {
|
|
32
|
+
const base = trimTrailingSlash(req.serverUrl);
|
|
33
|
+
const headers = { "Content-Type": "text/plain" };
|
|
34
|
+
if (req.passcode) {
|
|
35
|
+
headers.Authorization = `Bearer ${req.passcode}`;
|
|
36
|
+
}
|
|
37
|
+
const res = await fetch(`${base}/compile`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers,
|
|
40
|
+
body: req.stanCode,
|
|
41
|
+
signal: req.signal
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
const text = await res.text().catch(() => "");
|
|
45
|
+
throw new Error(`Stan WASM compile failed (HTTP ${res.status}): ${text || res.statusText}`);
|
|
46
|
+
}
|
|
47
|
+
const json = await res.json();
|
|
48
|
+
if (typeof json.model_id !== "string" || json.model_id.length === 0) {
|
|
49
|
+
throw new Error("Stan WASM compile response did not include a model_id");
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
modelId: json.model_id,
|
|
53
|
+
mainJsUrl: `${base}/download/${json.model_id}/main.js`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function probeServer(opts) {
|
|
57
|
+
const base = trimTrailingSlash(opts.serverUrl);
|
|
58
|
+
try {
|
|
59
|
+
const res = await fetch(`${base}/probe`, { method: "POST" });
|
|
60
|
+
return res.ok;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/sampler.ts
|
|
67
|
+
var import_meta = {};
|
|
68
|
+
var defaultWorkerUrl = () => {
|
|
69
|
+
try {
|
|
70
|
+
return new URL("./worker.js", import_meta.url);
|
|
71
|
+
} catch {
|
|
72
|
+
throw new Error(
|
|
73
|
+
'@mcmcjs/stan-wasm could not resolve its worker URL automatically. Pass `workerUrl` explicitly to the StanSampler constructor (e.g. import workerUrl from "@mcmcjs/stan-wasm/worker?url" in Vite).'
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
var calculateRefreshRate = (cfg) => {
|
|
78
|
+
const total = (cfg.num_warmup + cfg.num_samples) * cfg.num_chains;
|
|
79
|
+
const twoHalfPercent = Math.floor(total / 40);
|
|
80
|
+
const nearestTen = Math.round(twoHalfPercent / 10) * 10;
|
|
81
|
+
return Math.max(15, nearestTen);
|
|
82
|
+
};
|
|
83
|
+
var StanSampler = class {
|
|
84
|
+
#compileServerUrl;
|
|
85
|
+
#passcode;
|
|
86
|
+
#workerUrl;
|
|
87
|
+
#worker = null;
|
|
88
|
+
#lastCompile = null;
|
|
89
|
+
#samplingStartTimeSec = 0;
|
|
90
|
+
#loadResolve = null;
|
|
91
|
+
#loadReject = null;
|
|
92
|
+
#sampleResolve = null;
|
|
93
|
+
#sampleReject = null;
|
|
94
|
+
#activeListener = null;
|
|
95
|
+
constructor(opts) {
|
|
96
|
+
this.#compileServerUrl = opts.compileServerUrl;
|
|
97
|
+
this.#passcode = opts.passcode;
|
|
98
|
+
this.#workerUrl = opts.workerUrl ?? defaultWorkerUrl();
|
|
99
|
+
}
|
|
100
|
+
get isReady() {
|
|
101
|
+
return this.#worker !== null && this.#lastCompile !== null;
|
|
102
|
+
}
|
|
103
|
+
get lastCompile() {
|
|
104
|
+
return this.#lastCompile;
|
|
105
|
+
}
|
|
106
|
+
async compile(stanCode, signal) {
|
|
107
|
+
this.#teardown(new Error("Superseded by a new compile()"));
|
|
108
|
+
const result = await compileStanCode({
|
|
109
|
+
serverUrl: this.#compileServerUrl,
|
|
110
|
+
passcode: this.#passcode,
|
|
111
|
+
stanCode,
|
|
112
|
+
signal
|
|
113
|
+
});
|
|
114
|
+
await this.#startWorker(result.mainJsUrl);
|
|
115
|
+
this.#lastCompile = result;
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
sample(config, listener) {
|
|
119
|
+
if (!this.#worker) {
|
|
120
|
+
return Promise.reject(
|
|
121
|
+
new Error("No model loaded \u2014 call compile() and await it before sample()")
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
if (this.#sampleResolve !== null) {
|
|
125
|
+
return Promise.reject(new Error("A sampling run is already in progress on this sampler"));
|
|
126
|
+
}
|
|
127
|
+
return new Promise((resolve, reject) => {
|
|
128
|
+
this.#sampleResolve = resolve;
|
|
129
|
+
this.#sampleReject = reject;
|
|
130
|
+
this.#activeListener = listener ?? null;
|
|
131
|
+
this.#samplingStartTimeSec = performance.now() / 1e3;
|
|
132
|
+
const fullConfig = {
|
|
133
|
+
...config,
|
|
134
|
+
seed: config.seed ?? Math.floor(Math.random() * 2 ** 32),
|
|
135
|
+
refresh: config.refresh ?? calculateRefreshRate(config)
|
|
136
|
+
};
|
|
137
|
+
this.#worker?.postMessage({ kind: "sample" /* Sample */, sampleConfig: fullConfig });
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
cancel() {
|
|
141
|
+
this.#teardown(new Error("Cancelled"));
|
|
142
|
+
}
|
|
143
|
+
#teardown(rejectionReason) {
|
|
144
|
+
if (this.#worker) {
|
|
145
|
+
this.#worker.terminate();
|
|
146
|
+
this.#worker = null;
|
|
147
|
+
}
|
|
148
|
+
this.#loadReject?.(rejectionReason);
|
|
149
|
+
this.#sampleReject?.(rejectionReason);
|
|
150
|
+
this.#loadResolve = null;
|
|
151
|
+
this.#loadReject = null;
|
|
152
|
+
this.#sampleResolve = null;
|
|
153
|
+
this.#sampleReject = null;
|
|
154
|
+
this.#activeListener = null;
|
|
155
|
+
this.#lastCompile = null;
|
|
156
|
+
}
|
|
157
|
+
#startWorker(url) {
|
|
158
|
+
const worker = new Worker(this.#workerUrl, { type: "module" });
|
|
159
|
+
this.#worker = worker;
|
|
160
|
+
worker.onmessage = (event) => {
|
|
161
|
+
this.#onWorkerMessage(event.data);
|
|
162
|
+
};
|
|
163
|
+
worker.onerror = (event) => {
|
|
164
|
+
const message = event.message || "Worker error";
|
|
165
|
+
const err = new Error(`Stan WASM worker error: ${message}`);
|
|
166
|
+
if (this.#loadReject) {
|
|
167
|
+
this.#loadReject(err);
|
|
168
|
+
this.#loadResolve = null;
|
|
169
|
+
this.#loadReject = null;
|
|
170
|
+
} else if (this.#sampleReject) {
|
|
171
|
+
this.#sampleReject(err);
|
|
172
|
+
this.#sampleResolve = null;
|
|
173
|
+
this.#sampleReject = null;
|
|
174
|
+
this.#activeListener = null;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
return new Promise((resolve, reject) => {
|
|
178
|
+
this.#loadResolve = resolve;
|
|
179
|
+
this.#loadReject = reject;
|
|
180
|
+
worker.postMessage({ kind: "load" /* Load */, url });
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
#onWorkerMessage(msg) {
|
|
184
|
+
switch (msg.kind) {
|
|
185
|
+
case "modelLoaded" /* ModelLoaded */: {
|
|
186
|
+
const resolve = this.#loadResolve;
|
|
187
|
+
this.#loadResolve = null;
|
|
188
|
+
this.#loadReject = null;
|
|
189
|
+
resolve?.();
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
case "modelLoadError" /* ModelLoadError */: {
|
|
193
|
+
const reject = this.#loadReject;
|
|
194
|
+
this.#loadResolve = null;
|
|
195
|
+
this.#loadReject = null;
|
|
196
|
+
reject?.(new Error(`Stan WASM model load failed: ${msg.error}`));
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case "progress" /* Progress */: {
|
|
200
|
+
this.#activeListener?.({ type: "progress", report: msg.report });
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
case "stanReturn" /* StanReturn */: {
|
|
204
|
+
const resolve = this.#sampleResolve;
|
|
205
|
+
const reject = this.#sampleReject;
|
|
206
|
+
const listener = this.#activeListener;
|
|
207
|
+
this.#sampleResolve = null;
|
|
208
|
+
this.#sampleReject = null;
|
|
209
|
+
this.#activeListener = null;
|
|
210
|
+
if (msg.error !== null) {
|
|
211
|
+
listener?.({ type: "error", error: msg.error });
|
|
212
|
+
reject?.(new Error(`Stan sampling failed: ${msg.error}`));
|
|
213
|
+
} else {
|
|
214
|
+
const run = {
|
|
215
|
+
draws: msg.draws,
|
|
216
|
+
paramNames: msg.paramNames,
|
|
217
|
+
computeTimeSec: performance.now() / 1e3 - this.#samplingStartTimeSec,
|
|
218
|
+
consoleMessages: msg.consoleMessages,
|
|
219
|
+
sampleConfig: msg.sampleConfig
|
|
220
|
+
};
|
|
221
|
+
listener?.({ type: "done", run });
|
|
222
|
+
resolve?.(run);
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/compile-client.ts","../src/sampler.ts"],"sourcesContent":["export {\n type CompileRequest,\n compileStanCode,\n probeServer,\n} from \"./compile-client\";\nexport {\n StanSampler,\n type StanSamplerOptions,\n} from \"./sampler\";\n\nexport type {\n CompileResult,\n CompileStatus,\n ConsoleMessage,\n Progress,\n SampleConfig,\n SamplerEvent,\n SamplerListener,\n SamplerState,\n SampleStatus,\n SamplingOpts,\n StanRun,\n StanVariableInputs,\n} from \"./types\";\n","import type { CompileResult } from \"./types\";\n\nexport interface CompileRequest {\n serverUrl: string;\n passcode?: string;\n stanCode: string;\n signal?: AbortSignal;\n}\n\nconst trimTrailingSlash = (url: string): string => url.replace(/\\/+$/, \"\");\n\nexport async function compileStanCode(req: CompileRequest): Promise<CompileResult> {\n const base = trimTrailingSlash(req.serverUrl);\n const headers: Record<string, string> = { \"Content-Type\": \"text/plain\" };\n if (req.passcode) {\n headers.Authorization = `Bearer ${req.passcode}`;\n }\n const res = await fetch(`${base}/compile`, {\n method: \"POST\",\n headers,\n body: req.stanCode,\n signal: req.signal,\n });\n if (!res.ok) {\n const text = await res.text().catch(() => \"\");\n throw new Error(`Stan WASM compile failed (HTTP ${res.status}): ${text || res.statusText}`);\n }\n const json = (await res.json()) as { model_id?: unknown };\n if (typeof json.model_id !== \"string\" || json.model_id.length === 0) {\n throw new Error(\"Stan WASM compile response did not include a model_id\");\n }\n return {\n modelId: json.model_id,\n mainJsUrl: `${base}/download/${json.model_id}/main.js`,\n };\n}\n\nexport async function probeServer(opts: { serverUrl: string }): Promise<boolean> {\n const base = trimTrailingSlash(opts.serverUrl);\n try {\n const res = await fetch(`${base}/probe`, { method: \"POST\" });\n return res.ok;\n } catch {\n return false;\n }\n}\n","import { compileStanCode } from \"./compile-client\";\nimport {\n type CompileResult,\n type SampleConfig,\n type SamplerListener,\n type StanRun,\n WorkerReply,\n type WorkerReplyMessage,\n WorkerRequest,\n} from \"./types\";\n\nexport interface StanSamplerOptions {\n compileServerUrl: string;\n passcode?: string;\n workerUrl?: string | URL;\n}\n\nconst defaultWorkerUrl = (): URL => {\n try {\n return new URL(\"./worker.js\", import.meta.url);\n } catch {\n throw new Error(\n '@mcmcjs/stan-wasm could not resolve its worker URL automatically. Pass `workerUrl` explicitly to the StanSampler constructor (e.g. import workerUrl from \"@mcmcjs/stan-wasm/worker?url\" in Vite).',\n );\n }\n};\n\nconst calculateRefreshRate = (cfg: SampleConfig): number => {\n const total = (cfg.num_warmup + cfg.num_samples) * cfg.num_chains;\n const twoHalfPercent = Math.floor(total / 40);\n const nearestTen = Math.round(twoHalfPercent / 10) * 10;\n return Math.max(15, nearestTen);\n};\n\nexport class StanSampler {\n readonly #compileServerUrl: string;\n readonly #passcode: string | undefined;\n readonly #workerUrl: string | URL;\n\n #worker: Worker | null = null;\n #lastCompile: CompileResult | null = null;\n #samplingStartTimeSec: number = 0;\n\n #loadResolve: (() => void) | null = null;\n #loadReject: ((err: Error) => void) | null = null;\n #sampleResolve: ((run: StanRun) => void) | null = null;\n #sampleReject: ((err: Error) => void) | null = null;\n #activeListener: SamplerListener | null = null;\n\n constructor(opts: StanSamplerOptions) {\n this.#compileServerUrl = opts.compileServerUrl;\n this.#passcode = opts.passcode;\n this.#workerUrl = opts.workerUrl ?? defaultWorkerUrl();\n }\n\n get isReady(): boolean {\n return this.#worker !== null && this.#lastCompile !== null;\n }\n\n get lastCompile(): CompileResult | null {\n return this.#lastCompile;\n }\n\n async compile(stanCode: string, signal?: AbortSignal): Promise<CompileResult> {\n this.#teardown(new Error(\"Superseded by a new compile()\"));\n const result = await compileStanCode({\n serverUrl: this.#compileServerUrl,\n passcode: this.#passcode,\n stanCode,\n signal,\n });\n await this.#startWorker(result.mainJsUrl);\n this.#lastCompile = result;\n return result;\n }\n\n sample(config: SampleConfig, listener?: SamplerListener): Promise<StanRun> {\n if (!this.#worker) {\n return Promise.reject(\n new Error(\"No model loaded — call compile() and await it before sample()\"),\n );\n }\n if (this.#sampleResolve !== null) {\n return Promise.reject(new Error(\"A sampling run is already in progress on this sampler\"));\n }\n return new Promise<StanRun>((resolve, reject) => {\n this.#sampleResolve = resolve;\n this.#sampleReject = reject;\n this.#activeListener = listener ?? null;\n this.#samplingStartTimeSec = performance.now() / 1000;\n const fullConfig: SampleConfig = {\n ...config,\n seed: config.seed ?? Math.floor(Math.random() * 2 ** 32),\n refresh: config.refresh ?? calculateRefreshRate(config),\n };\n this.#worker?.postMessage({ kind: WorkerRequest.Sample, sampleConfig: fullConfig });\n });\n }\n\n cancel(): void {\n this.#teardown(new Error(\"Cancelled\"));\n }\n\n #teardown(rejectionReason: Error): void {\n if (this.#worker) {\n this.#worker.terminate();\n this.#worker = null;\n }\n this.#loadReject?.(rejectionReason);\n this.#sampleReject?.(rejectionReason);\n this.#loadResolve = null;\n this.#loadReject = null;\n this.#sampleResolve = null;\n this.#sampleReject = null;\n this.#activeListener = null;\n this.#lastCompile = null;\n }\n\n #startWorker(url: string): Promise<void> {\n const worker = new Worker(this.#workerUrl, { type: \"module\" });\n this.#worker = worker;\n worker.onmessage = (event: MessageEvent<WorkerReplyMessage>) => {\n this.#onWorkerMessage(event.data);\n };\n worker.onerror = (event: ErrorEvent) => {\n const message = event.message || \"Worker error\";\n const err = new Error(`Stan WASM worker error: ${message}`);\n if (this.#loadReject) {\n this.#loadReject(err);\n this.#loadResolve = null;\n this.#loadReject = null;\n } else if (this.#sampleReject) {\n this.#sampleReject(err);\n this.#sampleResolve = null;\n this.#sampleReject = null;\n this.#activeListener = null;\n }\n };\n return new Promise<void>((resolve, reject) => {\n this.#loadResolve = resolve;\n this.#loadReject = reject;\n worker.postMessage({ kind: WorkerRequest.Load, url });\n });\n }\n\n #onWorkerMessage(msg: WorkerReplyMessage): void {\n switch (msg.kind) {\n case WorkerReply.ModelLoaded: {\n const resolve = this.#loadResolve;\n this.#loadResolve = null;\n this.#loadReject = null;\n resolve?.();\n break;\n }\n case WorkerReply.ModelLoadError: {\n const reject = this.#loadReject;\n this.#loadResolve = null;\n this.#loadReject = null;\n reject?.(new Error(`Stan WASM model load failed: ${msg.error}`));\n break;\n }\n case WorkerReply.Progress: {\n this.#activeListener?.({ type: \"progress\", report: msg.report });\n break;\n }\n case WorkerReply.StanReturn: {\n const resolve = this.#sampleResolve;\n const reject = this.#sampleReject;\n const listener = this.#activeListener;\n this.#sampleResolve = null;\n this.#sampleReject = null;\n this.#activeListener = null;\n if (msg.error !== null) {\n listener?.({ type: \"error\", error: msg.error });\n reject?.(new Error(`Stan sampling failed: ${msg.error}`));\n } else {\n const run: StanRun = {\n draws: msg.draws,\n paramNames: msg.paramNames,\n computeTimeSec: performance.now() / 1000 - this.#samplingStartTimeSec,\n consoleMessages: msg.consoleMessages,\n sampleConfig: msg.sampleConfig,\n };\n listener?.({ type: \"done\", run });\n resolve?.(run);\n }\n break;\n }\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { C as CompileResult } from './sampler-4wflmHRA.cjs';
|
|
2
|
+
export { a as CompileStatus, b as ConsoleMessage, P as Progress, S as SampleConfig, c as SampleStatus, d as SamplerEvent, e as SamplerListener, f as SamplerState, g as SamplingOpts, h as StanRun, i as StanSampler, j as StanSamplerOptions, k as StanVariableInputs } from './sampler-4wflmHRA.cjs';
|
|
3
|
+
|
|
4
|
+
interface CompileRequest {
|
|
5
|
+
serverUrl: string;
|
|
6
|
+
passcode?: string;
|
|
7
|
+
stanCode: string;
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
}
|
|
10
|
+
declare function compileStanCode(req: CompileRequest): Promise<CompileResult>;
|
|
11
|
+
declare function probeServer(opts: {
|
|
12
|
+
serverUrl: string;
|
|
13
|
+
}): Promise<boolean>;
|
|
14
|
+
|
|
15
|
+
export { type CompileRequest, CompileResult, compileStanCode, probeServer };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { C as CompileResult } from './sampler-4wflmHRA.js';
|
|
2
|
+
export { a as CompileStatus, b as ConsoleMessage, P as Progress, S as SampleConfig, c as SampleStatus, d as SamplerEvent, e as SamplerListener, f as SamplerState, g as SamplingOpts, h as StanRun, i as StanSampler, j as StanSamplerOptions, k as StanVariableInputs } from './sampler-4wflmHRA.js';
|
|
3
|
+
|
|
4
|
+
interface CompileRequest {
|
|
5
|
+
serverUrl: string;
|
|
6
|
+
passcode?: string;
|
|
7
|
+
stanCode: string;
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
}
|
|
10
|
+
declare function compileStanCode(req: CompileRequest): Promise<CompileResult>;
|
|
11
|
+
declare function probeServer(opts: {
|
|
12
|
+
serverUrl: string;
|
|
13
|
+
}): Promise<boolean>;
|
|
14
|
+
|
|
15
|
+
export { type CompileRequest, CompileResult, compileStanCode, probeServer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|