Skip to content

Commit

Permalink
fix: fix several issues with JS bindings (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
sd2k authored Oct 16, 2024
1 parent 7877fde commit 1a360b7
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/wasmstan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
targets: wasm32-unknown-unknown,wasm32-wasip1
- uses: taiki-e/install-action@v2
with:
tool: cargo-binstall,just,wasmtime
tool: cargo-binstall,just,ripgrep,wasmtime
- name: Install deps
run: just components/install-deps
- uses: actions/setup-node@v4
Expand Down
21 changes: 21 additions & 0 deletions components/js/prophet-wasmstan/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,24 @@ prophet.predict({ ds: [ 1713717414 ]})
```

See the documentation for `@bsull/augurs` for more details.

## Troubleshooting

### Webpack

The generated Javascript bindings in this package may require some additional Webpack configuration to work.
Adding this to your `webpack.config.js` should be enough:

```javascript
{
experiments: {
// Required to load WASM modules.
asyncWebAssembly: true,
},
resolve: {
fallback: {
fs: false,
},
},
}
```
17 changes: 17 additions & 0 deletions components/js/prophet-wasmstan/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Note: this function comes from https://stackoverflow.com/questions/30401486/ecma6-generators-yield-promise.
// It is used to convert a generator function into a promise.
// `jco transpile` generates a similar function but it didn't work for me.
// I'm not sure why, but I'll raise an issue on the `jco` repo.
// See the `justfile` for how this gets shimmed into the transpiled code;
// in short, we use `ripgrep` as in
// https://unix.stackexchange.com/questions/181180/replace-multiline-string-in-files
// (it was a Stack-Overflow heavy day...)
// The indentation is intentional so the function matches the original.
function run(g) {
return Promise.resolve(function step(v) {
const res = g.next(v);
if (res.done) return res.value;
return res.value.then(step);
}());
}
return run(gen);
22 changes: 9 additions & 13 deletions components/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,17 @@ transpile: build
--name prophet-wasmstan \
--out-dir js/prophet-wasmstan \
cpp/prophet-wasmstan/wit/prophet-wasmstan.wit

transpile-min: build
jco transpile \
--name prophet-wasmstan \
--minify \
--optimize \
--out-dir js/prophet-wasmstan \
cpp/prophet-wasmstan/prophet-wasmstan-component.wasm
jco types \
--name prophet-wasmstan \
--out-dir js/prophet-wasmstan \
cpp/prophet-wasmstan/wit/prophet-wasmstan.wit
rg --replace="$(rg --invert-match --no-line-number '//' js/prophet-wasmstan/run.js)" \
--multiline --multiline-dotall \
--passthru \
--no-line-number \
' let promise, resolve, reject;.+?return promise \|\| maybeSyncReturn;' \
js/prophet-wasmstan/prophet-wasmstan.js \
> js/prophet-wasmstan/prophet-wasmstan.fixed.js
mv js/prophet-wasmstan/prophet-wasmstan.fixed.js js/prophet-wasmstan/prophet-wasmstan.js

test: transpile
cd js/prophet-wasmstan && npm ci && npm run test:ci

publish: transpile-min
publish: transpile
cd js/prophet-wasmstan && npm ci && npm publish --access public
28 changes: 27 additions & 1 deletion crates/augurs-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Javascript bindings to the [`augurs`][repo] time series framework.

```json
"dependencies": {
"@bsull/augurs": "^0.3.0"
"@bsull/augurs": "^0.4.1"
}
```

Expand Down Expand Up @@ -36,4 +36,30 @@ const { point, lower, upper } = model.predictInSample(predictionInterval);
const { point: futurePoint, lower: futureLower, upper: futureUpper } = model.predict(10, predictionInterval);
```

## Troubleshooting

### Webpack

Some of the dependencies of `augurs` require a few changes to the Webpack configuration to work correctly.
Adding this to your `webpack.config.js` should be enough:

```javascript
{
experiments: {
// Required to load WASM modules.
asyncWebAssembly: true,
},
module: {
rules: [
{
test: /\@bsull\/augurs\/.*\.js$/,
resolve: {
fullySpecified: false
}
},
]
},
}
```

[repo]: https:/grafana/augurs
29 changes: 8 additions & 21 deletions crates/augurs-js/src/prophet.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{collections::HashMap, mem, num::TryFromIntError};
use std::{collections::HashMap, num::TryFromIntError};

use augurs_prophet::PositiveFloat;
use js_sys::{Float64Array, Int32Array};
Expand Down Expand Up @@ -414,28 +414,14 @@ struct Logs {
pub error: String,
/// Fatal logs.
pub fatal: String,

#[serde(default, skip)]
emitted_header: bool,
}

impl Logs {
fn emit(mut self) {
let debug = mem::take(&mut self.debug);
let info = mem::take(&mut self.info);
let warn = mem::take(&mut self.warn);
let error = mem::take(&mut self.error);
let fatal = mem::take(&mut self.fatal);
for line in debug.lines() {
fn emit(self) {
for line in self.debug.lines() {
tracing::trace!(target: "augurs::prophet::stan::optimize", "{}", line);
}
for line in info.lines() {
if line.contains("Iter") {
if self.emitted_header {
return;
}
self.emitted_header = true;
}
for line in self.info.lines().filter(|line| !line.contains("Iter")) {
match ConvergenceLog::new(line) {
Some(log) => {
tracing::debug!(
Expand All @@ -455,13 +441,13 @@ impl Logs {
}
}
}
for line in warn.lines() {
for line in self.warn.lines() {
tracing::warn!(target: "augurs::prophet::stan::optimize", "{}", line);
}
for line in error.lines() {
for line in self.error.lines() {
tracing::error!(target: "augurs::prophet::stan::optimize", "{}", line);
}
for line in fatal.lines() {
for line in self.fatal.lines() {
tracing::error!(target: "augurs::prophet::stan::optimize", "{}", line);
}
}
Expand All @@ -488,6 +474,7 @@ struct OptimizedParams {
/// Trend offset.
pub m: f64,
/// Observation noise.
#[tsify(type = "number")]
pub sigma_obs: PositiveFloat,
/// Trend rate adjustments.
#[tsify(type = "Float64Array")]
Expand Down
13 changes: 12 additions & 1 deletion crates/augurs-prophet/src/positive_float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PositiveFloat(f64);

/// An invalid float was provided when trying to create a [`PositiveFloat`].
Expand Down Expand Up @@ -48,3 +48,14 @@ impl From<PositiveFloat> for f64 {
value.0
}
}

#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for PositiveFloat {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let f = f64::deserialize(deserializer)?;
Self::try_new(f).map_err(serde::de::Error::custom)
}
}

0 comments on commit 1a360b7

Please sign in to comment.