Skip to content

feat(wren-core-wasm): cubeQuery + listCubes browser API#2278

Merged
goldmedal merged 3 commits into
feat/wasm-cubefrom
feat/cube-wasm
May 14, 2026
Merged

feat(wren-core-wasm): cubeQuery + listCubes browser API#2278
goldmedal merged 3 commits into
feat/wasm-cubefrom
feat/cube-wasm

Conversation

@goldmedal
Copy link
Copy Markdown
Collaborator

@goldmedal goldmedal commented May 14, 2026

Summary

Phase F of the cube rollout (impl-cube-wasm.md): expose the cube translator to the browser through @wrenai/wren-core-wasm.

F0 — store the analyzed MDL

  • WrenEngine now holds analyzed_mdl: Option<Arc<AnalyzedWrenMDL>>.
  • loadMDL Arc::clones the analyzed manifest into the struct after apply_wren_on_ctx, so the cube API can read the manifest without re-analyzing.

F1 — Rust wasm-bindgen API

  • cubeQuery(json) -> Promise<string> — deserialize CubeQuery via serde, translate through wren_core::mdl::cube_query_to_sql, execute via the existing query() path. Curated re-export, not wren_core::mdl::cube::* (which is pub(crate) since PR refactor(wren-core): cube Phase C cleanup nits #2276).
  • listCubes() -> string — emit a JSON array of { name, baseObject, measures, dimensions, timeDimensions, hierarchies }.
  • Clean error messages when called before loadMDL.

F2 + F5 — TypeScript SDK

  • New exported types: CubeQueryInput, TimeDimensionInput, CubeFilterInput, CubeInfo, plus Granularity and FilterOperator string unions.
  • cubeQuery(query) returns Promise<Record<string, unknown>[]>; listCubes() returns CubeInfo[].
  • Hand-maintained wren_core_wasm.d.ts mirror updated to match.

F3 — Integration tests

  • 5 new SDK tests under sdk/tests/index.test.mjs: listCubes shape, cubeQuery aggregation, unknown cube rejection, and "missing MDL" errors for both methods.

F4 — AGENT_GUIDE

  • New "Cube Query API" section with discovery, query, filter operators, and granularity reference.
  • Bumped the unpkg CDN URL from @0.1.0 to @0.3.0 so the doc matches the current npm release.

Test plan

  • just build-wasm-dev — WASM dev build compiles
  • just build-dist + just typecheck — TypeScript SDK builds and type-checks clean
  • just test — 19 SDK integration tests pass (5 new + 14 prior)
  • cargo clippy --target wasm32-unknown-unknown -- -D warnings — clean
  • CI runs the release WASM build and the size check
  • CI runs cargo fmt --all -- --check — note: there is a pre-existing fmt diff in extract_bare_table_name test on feat/wasm-cube HEAD that this PR doesn't introduce or touch; left alone to keep the diff minimal.

Phase G (skills wiring + npm release) is the last step before merging feat/wasm-cube into main.

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced Cube Query API with cubeQuery() and listCubes() methods for structured data querying, supporting measures, dimensions, filters, time bucketing, and granularity options.
    • Added interactive demo pages (Cube Quickstart and Cube Explorer) for exploring and querying cubes in the browser.
  • Documentation

    • Updated Cube Query API documentation with usage examples, filter operators, and time dimension semantics.
    • Added Examples section to README with instructions for running demo applications.

Review Change Stack

WrenEngine now keeps the AnalyzedWrenMDL produced by loadMDL so the
cube API can read the manifest after load. New JS-facing methods:

  * cubeQuery(query)  — translate a structured CubeQuery to SQL via
                        wren-core and execute it through the same path
                        as query(). Returns rows as JSON.
  * listCubes()       — return cubes from the loaded MDL (name,
                        baseObject, measures, dimensions, time
                        dimensions, hierarchies).

TypeScript wrapper adds CubeQueryInput / TimeDimensionInput /
CubeFilterInput / CubeInfo (plus Granularity and FilterOperator string
unions) so agents get end-to-end typing. The hand-maintained
wren_core_wasm.d.ts mirror is updated to match.

Both Rust methods route through `wren_core::mdl::{cube_query_to_sql,
CubeQuery}` — the curated re-export — since `pub(crate) mod cube`
(PR #2276) makes the inner module unreachable from outside the crate.

AGENT_GUIDE gains a Cube Query API section and the unpkg CDN URL is
bumped from 0.1.0 to 0.3.0 to match the current npm release.

Adds 5 SDK integration tests covering the happy path, an unknown cube,
calling either method before loadMDL, and listCubes shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 14, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 12320832-6df6-43d5-a87b-99fc4c3a5d6c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cube-wasm

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added documentation Improvements or additions to documentation rust Pull requests that update rust code core wasm labels May 14, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
core/wren-core-wasm/sdk/tests/index.test.mjs (2)

377-390: 💤 Low value

Consider asserting dimensions array length before accessing elements.

Line 388 accesses cubes[0].dimensions[0].name without first verifying cubes[0].dimensions.length. While the test data guarantees one dimension, adding an explicit length check improves test clarity and prevents confusing failures if the fixture changes.

✨ Optional assertion to add
 assert.equal(cubes[0].measures.length, 2);
 assert.equal(cubes[0].measures[0].name, "total");
+assert.equal(cubes[0].dimensions.length, 1);
 assert.equal(cubes[0].dimensions[0].name, "status");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/wren-core-wasm/sdk/tests/index.test.mjs` around lines 377 - 390, Add an
explicit assertion on the dimensions array length before accessing its first
element: after calling engine.listCubes() and validating measures, assert
cubes[0].dimensions.length === 1 (or the expected count) before referencing
cubes[0].dimensions[0].name; update the test in index.test.mjs around the
listCubes assertions to include this dimensions length check so future fixture
changes won’t cause unclear indexing errors.

376-445: ⚖️ Poor tradeoff

Consider adding test coverage for filters, time dimensions, and empty results.

The current test suite covers core cube functionality well, but the PR introduces CubeFilterInput, TimeDimensionInput, Granularity, and FilterOperator types that aren't exercised. Consider adding tests for:

  1. Filters: Test cubeQuery with filter conditions using various operators
  2. Time dimensions: Test queries with time granularity (day/week/month) and date ranges
  3. Empty results: Test cubeQuery when no rows match the criteria
  4. Hierarchies: Exercise the hierarchies field if it's part of the public API

These would increase confidence that the full API surface works end-to-end.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/wren-core-wasm/sdk/tests/index.test.mjs` around lines 376 - 445, Add
unit tests exercising the new CubeFilterInput, TimeDimensionInput, Granularity,
and FilterOperator logic by extending the existing suite around
cubeQuery/listCubes: create a test that applies CubeFilterInput with multiple
FilterOperator types (equals, gt, in, isNull) against the "order_metrics" cube
and assert filtered aggregates; add tests that pass TimeDimensionInput with
different Granularity values (day, week, month) and date ranges to verify
grouping and boundaries; add a test that issues a cubeQuery whose filters yield
no matching rows and assert an empty result set; and, if the public cube
metadata exposes hierarchies, add a listCubes or cubeQuery case that validates
the hierarchies field is populated. Use existing helpers like WrenEngine.init,
registerJson, loadMDL, and cubeMDL() to set up data and MDL in each test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/wren-core-wasm/AGENT_GUIDE.md`:
- Around line 91-123: Add an explicit precondition note that engine.listCubes()
and engine.cubeQuery() require loadMDL() to have been called first: update the
AGENT_GUIDE.md section around the cube API examples to state that both
engine.listCubes() and engine.cubeQuery() will throw or fail if loadMDL() has
not been run, and show a tiny example or single-line reminder to call await
engine.loadMDL() before using those methods (reference the symbols loadMDL,
engine.listCubes, and engine.cubeQuery in the note).

---

Nitpick comments:
In `@core/wren-core-wasm/sdk/tests/index.test.mjs`:
- Around line 377-390: Add an explicit assertion on the dimensions array length
before accessing its first element: after calling engine.listCubes() and
validating measures, assert cubes[0].dimensions.length === 1 (or the expected
count) before referencing cubes[0].dimensions[0].name; update the test in
index.test.mjs around the listCubes assertions to include this dimensions length
check so future fixture changes won’t cause unclear indexing errors.
- Around line 376-445: Add unit tests exercising the new CubeFilterInput,
TimeDimensionInput, Granularity, and FilterOperator logic by extending the
existing suite around cubeQuery/listCubes: create a test that applies
CubeFilterInput with multiple FilterOperator types (equals, gt, in, isNull)
against the "order_metrics" cube and assert filtered aggregates; add tests that
pass TimeDimensionInput with different Granularity values (day, week, month) and
date ranges to verify grouping and boundaries; add a test that issues a
cubeQuery whose filters yield no matching rows and assert an empty result set;
and, if the public cube metadata exposes hierarchies, add a listCubes or
cubeQuery case that validates the hierarchies field is populated. Use existing
helpers like WrenEngine.init, registerJson, loadMDL, and cubeMDL() to set up
data and MDL in each test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 36a3355d-a489-43c9-b72f-00ffa7973bd2

📥 Commits

Reviewing files that changed from the base of the PR and between 889dd3f and 9409eee.

📒 Files selected for processing (5)
  • core/wren-core-wasm/AGENT_GUIDE.md
  • core/wren-core-wasm/sdk/src/index.ts
  • core/wren-core-wasm/sdk/src/wren_core_wasm.d.ts
  • core/wren-core-wasm/sdk/tests/index.test.mjs
  • core/wren-core-wasm/src/lib.rs

Comment thread core/wren-core-wasm/AGENT_GUIDE.md
* AGENT_GUIDE Cube Query section calls out that listCubes() and
  cubeQuery() require a prior loadMDL() — common integration footgun.
* listCubes test now asserts dimensions length, timeDimensions, and
  the hierarchies dict so future fixture changes show up here, not
  via a confusing IndexError.
* Adds two cubeQuery cases that exercise the new SDK types:
  - dimension filter via `in` (CubeFilterInput / FilterOperator)
  - month time-dimension with dateRange window (TimeDimensionInput /
    Granularity), verifying both the bucket column and the date-range
    WHERE filter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
core/wren-core-wasm/sdk/tests/index.test.mjs (1)

480-513: ⚡ Quick win

Consider verifying actual bucket values for more precise validation.

The test validates bucketing behavior and dateRange filtering well, but lines 509-510 only check that two totals (7 and 35) exist without verifying which month corresponds to which total. If the implementation were to return incorrect bucket labels (e.g., both labeled "2024-01"), this wouldn't be caught.

📊 More precise assertion example

Replace lines 507-510 with:

-    const totals = Object.fromEntries(rows.map((r) => [r[bucketCol], r.total]));
-    // Two distinct months — Jan totals 35, Feb totals 7.
-    const values = Object.values(totals).sort((a, b) => a - b);
-    assert.deepEqual(values, [7, 35]);
+    const totals = Object.fromEntries(rows.map((r) => [r[bucketCol], r.total]));
+    // Verify bucket labels and totals
+    assert.ok("2024-01" in totals || "2024-01-01" in totals, "Expected January bucket");
+    assert.ok("2024-02" in totals || "2024-02-01" in totals, "Expected February bucket");
+    const janTotal = totals["2024-01"] || totals["2024-01-01"];
+    const febTotal = totals["2024-02"] || totals["2024-02-01"];
+    assert.equal(janTotal, 35);
+    assert.equal(febTotal, 7);

Note: Adjust the expected bucket format based on the actual implementation's date formatting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/wren-core-wasm/sdk/tests/index.test.mjs` around lines 480 - 513, The
test currently only checks sorted totals; update it to assert the actual bucket
labels and their corresponding totals using the bucket column created_at__month
and the totals mapping (const totals = Object.fromEntries(rows.map(r =>
[r[bucketCol], r.total]))); specifically verify that the expected month bucket
keys (e.g., "2024-01-01" or the project's month-label format) exist and map to
total 35 and "2024-02-01" maps to 7 so mislabelled buckets will fail the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@core/wren-core-wasm/sdk/tests/index.test.mjs`:
- Around line 480-513: The test currently only checks sorted totals; update it
to assert the actual bucket labels and their corresponding totals using the
bucket column created_at__month and the totals mapping (const totals =
Object.fromEntries(rows.map(r => [r[bucketCol], r.total]))); specifically verify
that the expected month bucket keys (e.g., "2024-01-01" or the project's
month-label format) exist and map to total 35 and "2024-02-01" maps to 7 so
mislabelled buckets will fail the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 31de4491-f9f3-4800-8a78-e382864bdf6d

📥 Commits

Reviewing files that changed from the base of the PR and between 9409eee and 97aebe4.

📒 Files selected for processing (2)
  • core/wren-core-wasm/AGENT_GUIDE.md
  • core/wren-core-wasm/sdk/tests/index.test.mjs
✅ Files skipped from review due to trivial changes (1)
  • core/wren-core-wasm/AGENT_GUIDE.md

Two new example pages alongside inline.html / url-mode.html:

  * cube-quickstart.html — minimal cubeQuery() demo. Three preset
    queries (group-by, filter, time bucket) over an order_metrics cube
    with 7 rows of embedded data. Read the source for the smallest
    end-to-end cube example.

  * cube-explorer.html — interactive form-driven builder. Checkboxes
    for measures/dimensions, dropdown granularity + date-range box for
    time dimensions, and a repeatable filter row that exposes all 12
    FilterOperator values. The generated CubeQuery JSON re-renders
    live next to the result table; demo data is spread across regions
    / customers / months so groupings produce non-trivial numbers.

Both pages import directly from pkg/wren_core_wasm.js so they always
reflect the local build — re-run `just build-wasm-dev` after Rust
changes and refresh.

serve.mjs prints the new URLs on startup. README gains an Examples
section pointing at all five demos and explaining when to use
quickstart vs explorer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
core/wren-core-wasm/examples/cube-explorer.html (2)

292-298: ⚡ Quick win

Escape HTML in table cell values.

Line 296 interpolates r[k] directly into <td> tags without escaping. If query results contain HTML characters, they will be rendered as markup rather than text. Use textContent or escape HTML entities to prevent potential XSS.

🛡️ Proposed fix using textContent

Replace the string concatenation approach with DOM construction:

 function renderTable(rows) {
     if (!rows.length) return '<p>No rows.</p>';
     const keys = Object.keys(rows[0]);
-    return '<table><tr>' + keys.map(k => `<th>${k}</th>`).join('')
-        + '</tr>' + rows.map(r => '<tr>' + keys.map(k => `<td>${r[k] ?? ''}</td>`).join('') + '</tr>').join('')
-        + '</table>';
+    const table = document.createElement('table');
+    const headerRow = document.createElement('tr');
+    keys.forEach(k => {
+        const th = document.createElement('th');
+        th.textContent = k;
+        headerRow.appendChild(th);
+    });
+    table.appendChild(headerRow);
+    rows.forEach(r => {
+        const row = document.createElement('tr');
+        keys.forEach(k => {
+            const td = document.createElement('td');
+            td.textContent = r[k] ?? '';
+            row.appendChild(td);
+        });
+        table.appendChild(row);
+    });
+    return table.outerHTML;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/wren-core-wasm/examples/cube-explorer.html` around lines 292 - 298, The
renderTable function currently injects cell values directly (r[k]) into HTML
strings, enabling markup/XSS; modify renderTable to escape HTML entities or
build the table via DOM APIs and set each cell's textContent instead of string
interpolation. Specifically, update the function that produces the <td> content
(referencing renderTable, rows, keys, and r[k]) to either run r[k] through an
HTML-escape utility (escape &, <, >, ", ') before concatenation or construct
elements with document.createElement('td') and assign cell.textContent =
String(r[k] ?? '') so values render as text not markup.

195-229: ⚡ Quick win

Use textContent or sanitize HTML to prevent XSS.

Lines 201 and 216-227 use innerHTML with unsanitized data (item.name, dimension names, filter values). While the demo data is controlled, this demonstrates an insecure pattern that readers might copy to production code where data could come from untrusted sources.

Consider using textContent for text content, createElement/setAttribute for dynamic elements, or a sanitization library like DOMPurify for any HTML content.

🛡️ Example fix for line 201
 const label = document.createElement('label');
-label.innerHTML = `<input type="checkbox" id="${id}" value="${item.name}"> ${item.name} <span style="color:`#999`;font-size:0.85em;">${item.type || ''}</span>`;
+const checkbox = document.createElement('input');
+checkbox.type = 'checkbox';
+checkbox.id = id;
+checkbox.value = item.name;
+label.appendChild(checkbox);
+label.appendChild(document.createTextNode(` ${item.name} `));
+const typeSpan = document.createElement('span');
+typeSpan.style.cssText = 'color:`#999`;font-size:0.85em;';
+typeSpan.textContent = item.type || '';
+label.appendChild(typeSpan);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/wren-core-wasm/examples/cube-explorer.html` around lines 195 - 229, The
code uses innerHTML with unsanitized values in checkboxGroup (label.innerHTML)
and renderFilters (row.innerHTML for select/option/input/button) which risks
XSS; replace these innerHTML assignments by building elements programmatically:
create input/label/span via document.createElement and set text via textContent
and attributes via setAttribute for checkboxGroup (use id/namePrefix and
item.name safely), and for renderFilters create option elements with
option.value and option.textContent, create input and set
placeholder/textContent safely (or run values through a sanitizer like
DOMPurify) so no unescaped user data is injected into HTML strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@core/wren-core-wasm/examples/cube-explorer.html`:
- Around line 292-298: The renderTable function currently injects cell values
directly (r[k]) into HTML strings, enabling markup/XSS; modify renderTable to
escape HTML entities or build the table via DOM APIs and set each cell's
textContent instead of string interpolation. Specifically, update the function
that produces the <td> content (referencing renderTable, rows, keys, and r[k])
to either run r[k] through an HTML-escape utility (escape &, <, >, ", ') before
concatenation or construct elements with document.createElement('td') and assign
cell.textContent = String(r[k] ?? '') so values render as text not markup.
- Around line 195-229: The code uses innerHTML with unsanitized values in
checkboxGroup (label.innerHTML) and renderFilters (row.innerHTML for
select/option/input/button) which risks XSS; replace these innerHTML assignments
by building elements programmatically: create input/label/span via
document.createElement and set text via textContent and attributes via
setAttribute for checkboxGroup (use id/namePrefix and item.name safely), and for
renderFilters create option elements with option.value and option.textContent,
create input and set placeholder/textContent safely (or run values through a
sanitizer like DOMPurify) so no unescaped user data is injected into HTML
strings.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c000b1e9-9e5a-4d3b-a689-f6bb3e4879bc

📥 Commits

Reviewing files that changed from the base of the PR and between 97aebe4 and 8a5c6a5.

📒 Files selected for processing (4)
  • core/wren-core-wasm/README.md
  • core/wren-core-wasm/examples/cube-explorer.html
  • core/wren-core-wasm/examples/cube-quickstart.html
  • core/wren-core-wasm/examples/serve.mjs
✅ Files skipped from review due to trivial changes (1)
  • core/wren-core-wasm/README.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core documentation Improvements or additions to documentation rust Pull requests that update rust code wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant