From 4fe20ca71d04d7410939084c5d92a2011f45cf81 Mon Sep 17 00:00:00 2001
From: Ben Sully <ben.sully88@gmail.com>
Date: Wed, 16 Oct 2024 14:11:29 +0100
Subject: [PATCH 1/6] chore: don't bother with minifying transpiled code

---
 components/justfile | 14 +-------------
 1 file changed, 1 insertion(+), 13 deletions(-)

diff --git a/components/justfile b/components/justfile
index 0eb2028..3d7958f 100644
--- a/components/justfile
+++ b/components/justfile
@@ -75,20 +75,8 @@ transpile: build
     --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
-
 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

From ee9b876a32c4637622bbc4d1603b4ce5ef29f83e Mon Sep 17 00:00:00 2001
From: Ben Sully <ben.sully88@gmail.com>
Date: Wed, 16 Oct 2024 14:13:28 +0100
Subject: [PATCH 2/6] fix: never emit Stan logs starting containing 'Iter'

These are just the header row but we parse the rows and emit
them as structured logs anyway.
---
 crates/augurs-js/src/prophet.rs | 28 +++++++---------------------
 1 file changed, 7 insertions(+), 21 deletions(-)

diff --git a/crates/augurs-js/src/prophet.rs b/crates/augurs-js/src/prophet.rs
index 2010477..5fbc03e 100644
--- a/crates/augurs-js/src/prophet.rs
+++ b/crates/augurs-js/src/prophet.rs
@@ -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};
@@ -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!(
@@ -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);
         }
     }

From f73e0f2d520ea9a4664c2fd2286183190acaeda2 Mon Sep 17 00:00:00 2001
From: Ben Sully <ben.sully88@gmail.com>
Date: Wed, 16 Oct 2024 14:16:36 +0100
Subject: [PATCH 3/6] Fix invalid deserialize impl for PositiveFloat

---
 crates/augurs-prophet/src/positive_float.rs | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/crates/augurs-prophet/src/positive_float.rs b/crates/augurs-prophet/src/positive_float.rs
index fb65f19..b6aed6e 100644
--- a/crates/augurs-prophet/src/positive_float.rs
+++ b/crates/augurs-prophet/src/positive_float.rs
@@ -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`].
@@ -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)
+    }
+}

From 89fa1e3ddd1a69e03363efbdcadb5f4c36ddf07c Mon Sep 17 00:00:00 2001
From: Ben Sully <ben.sully88@gmail.com>
Date: Wed, 16 Oct 2024 14:17:01 +0100
Subject: [PATCH 4/6] Fix generated Typescript type for sigmaObs field

---
 crates/augurs-js/src/prophet.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/crates/augurs-js/src/prophet.rs b/crates/augurs-js/src/prophet.rs
index 5fbc03e..baae8ba 100644
--- a/crates/augurs-js/src/prophet.rs
+++ b/crates/augurs-js/src/prophet.rs
@@ -474,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")]

From e539ae7fa7f87cf599548ffe853512268e6bb38e Mon Sep 17 00:00:00 2001
From: Ben Sully <ben.sully88@gmail.com>
Date: Wed, 16 Oct 2024 14:40:34 +0100
Subject: [PATCH 5/6] Shim in a different function to run the init generator to
 completion

When I tried to use the existing module within a Webpack project
I found that the generator was continuing with promises and trying
to initialize WebAssembly using them, which obviously failed.
This function seems to work a lot better for some reason, although
I haven't digged into why, exactly.
---
 .github/workflows/wasmstan.yml        |  2 +-
 components/js/prophet-wasmstan/run.js | 17 +++++++++++++++++
 components/justfile                   |  8 ++++++++
 3 files changed, 26 insertions(+), 1 deletion(-)
 create mode 100644 components/js/prophet-wasmstan/run.js

diff --git a/.github/workflows/wasmstan.yml b/.github/workflows/wasmstan.yml
index b2913ff..186ff93 100644
--- a/.github/workflows/wasmstan.yml
+++ b/.github/workflows/wasmstan.yml
@@ -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
diff --git a/components/js/prophet-wasmstan/run.js b/components/js/prophet-wasmstan/run.js
new file mode 100644
index 0000000..181107d
--- /dev/null
+++ b/components/js/prophet-wasmstan/run.js
@@ -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);
diff --git a/components/justfile b/components/justfile
index 3d7958f..23fc310 100644
--- a/components/justfile
+++ b/components/justfile
@@ -74,6 +74,14 @@ transpile: build
     --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

From a1f6631369c90b03d5dfe824726b9653f3a45981 Mon Sep 17 00:00:00 2001
From: Ben Sully <ben.sully88@gmail.com>
Date: Wed, 16 Oct 2024 15:44:00 +0100
Subject: [PATCH 6/6] Update READMEs of JS packages to mention Webpack config

---
 components/js/prophet-wasmstan/README.md | 21 ++++++++++++++++++
 crates/augurs-js/README.md               | 28 +++++++++++++++++++++++-
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/components/js/prophet-wasmstan/README.md b/components/js/prophet-wasmstan/README.md
index 5d7f6e7..8418145 100644
--- a/components/js/prophet-wasmstan/README.md
+++ b/components/js/prophet-wasmstan/README.md
@@ -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,
+    },
+  },
+}
+```
diff --git a/crates/augurs-js/README.md b/crates/augurs-js/README.md
index 6b9e885..449cdc3 100644
--- a/crates/augurs-js/README.md
+++ b/crates/augurs-js/README.md
@@ -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"
 }
 ```
 
@@ -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