Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fix several issues with JS bindings #131

Merged
merged 6 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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://github.com/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)
}
}
Loading