Skip to content

feat(cubejs-client-core): add onlyViews option to meta() - #11391

Merged
vasilev-alex merged 2 commits into
masterfrom
feat/client-only-views-meta
Jul 28, 2026
Merged

feat(cubejs-client-core): add onlyViews option to meta()#11391
vasilev-alex merged 2 commits into
masterfrom
feat/client-only-views-meta

Conversation

@vasilev-alex

Copy link
Copy Markdown
Member

Summary

GET /v1/meta has accepted ?onlyViews=true since #10809 — the gateway reads
req.query.onlyViews === 'true' and passes it to both meta() and metaExtended(),
each of which narrows the model with metaConfig.filter((c) => c.config?.type === 'view')
before the usual visibility filtering.

No client could reach it. CubeApi.meta() hard-coded its request params to
signal / baseRequestId, so anyone wanting views-only had to drop down to the
private loadMethod / request pair. This PR adds the missing client half.

Motivation: on large data models the cubes are the bulk of the /meta payload, and
UI surfaces that render only views pay for all of it on every load.

Changes

@cubejs-client/core

  • New MetaMethodOptions = LoadMethodOptions & { onlyViews?: boolean }, exported from src/index.ts.
  • All three meta() signatures (both overloads + the implementation) widened from LoadMethodOptions to MetaMethodOptions.
  • The flag is forwarded as the string 'true', matching the gateway's req.query.onlyViews === 'true' check. URLSearchParams would stringify a boolean identically, but being explicit keeps the wire format visible at the call site.
  • Spread conditionally, so with the option absent (or false) the request is byte-identical to today — GET /meta with no query string.

@cubejs-client/react

  • useCubeFetch forwards onlyViews into coreOptions for the meta method, so useCubeMeta({ onlyViews: true }) works.
  • index.d.ts: useCubeMeta now takes a meta-specific CubeMetaFetchOptions (Omit<CubeFetchOptions, 'query'> & { onlyViews?: boolean }) instead of silently accepting an untyped extra key.

Docsdocs-mintlify/.../cubejs-client-core.mdx: meta() now references MetaMethodOptions, with a new type section and a usage snippet. The meta() docblock in src/index.ts documents the option too.

Compatibility

  • No option passed → identical request to today (covered by tests).
  • Server side untouched; metaExtended behavior unchanged.
  • Older servers ignore the unknown query param and return the full model. Both the docblock and the docs say so explicitly, so callers don't assume the response is filtered.

Testing

New tests in packages/cubejs-client-core:

  • CubeApi.test.tsonlyViews: true reaches the transport as 'true'; omitted and false send nothing; the flag coexists with signal / baseRequestId.
  • HttpTransport.test.ts — wire format: GET /meta stays query-string-free by default, and becomes GET /meta?onlyViews=true with the flag.
$ yarn unit   # packages/cubejs-client-core
 Test Files  15 passed (15)
      Tests  184 passed (184)

$ yarn lint   # packages/cubejs-client-core
Done in 3.09s.

$ npx tsc --noEmit   # packages/cubejs-client-core
(clean)

$ yarn lint   # packages/cubejs-client-react
Done in 2.59s.

On the React side

packages/cubejs-client-react has no test harness at all (no test script, no runner in
devDependencies), so I did not add committed tests there rather than introduce test infra
in this PR — happy to add it if you'd prefer.

I did verify the hook behavior rather than assume it, with a throwaway jsdom + react-dom
render driving useCubeMeta against a stub cubeApi (not committed). useCubeFetch's
effect re-runs on a ramda deep-compare of options, and toggling the flag does refetch:

call 1 args: [{"mutexObj":{},"mutexKey":"meta"}]              # default — unchanged
call count after toggle: 2
call 2 args: [{"mutexObj":{},"mutexKey":"meta","onlyViews":true}]
call count after no-op re-render: 2                            # equal options — no refetch
call count after toggling back: 3
call 3 args: [{"mutexObj":{},"mutexKey":"meta"}]               # flag dropped again

Note eslint src/hooks/cube-fetch.js reports 4 pre-existing errors (lines 23, 42, 48, 65 —
operator-linebreak, no-shadow) on lines this PR does not touch; the package's lint
script only globs src/*.js / src/*.jsx, so src/hooks isn't covered today. My changed
line is clean.

🤖 Generated with Claude Code

The `/v1/meta` endpoint has supported `?onlyViews=true` since #10809, but no
client could reach it — `CubeApi.meta()` hard-coded its request params, so
consumers had to go through the private `loadMethod`/`request` pair to filter
the data model down to views.

Add a `meta()`-specific `MetaMethodOptions` type and forward the flag as the
string `'true'`, matching the gateway's `req.query.onlyViews === 'true'` check.
The param is spread conditionally, so a `meta()` call without the option
produces a byte-identical request to before. Older servers ignore the unknown
query param and return the full model.

`useCubeMeta` forwards the option through `useCubeFetch` as well.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vasilev-alex
vasilev-alex requested review from a team and keydunov as code owners July 28, 2026 06:11
@github-actions github-actions Bot added client:core Issues relating to the JavaScript client SDK client:react Issues relating to the React client SDK javascript Pull requests that update Javascript code labels Jul 28, 2026
@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Claude finished @vasilev-alex's task in 3m 4s —— View job


Code review: onlyViews on meta()

Well-scoped change, good tests on the HTTP path. 1 medium, 1 low — the medium is that the flag breaks meta() over WebSocketTransport (hard 400, not a silent ignore).

Full review

Todos

  • Gather context (diff, gateway /v1/meta handler, both transports, React hook)
  • Review code quality / API design
  • Security & performance check
  • Test coverage review
  • Documentation review
  • Post inline comments + summary

Findings

# Severity Where Issue
1 Medium client-core/src/index.ts:735 onlyViews is added to params for every transport. Over WS, params becomes {"onlyViews":"true"}, and ws/message-schema.ts validates meta params with z.object({}).strict() → 400 Invalid message format. Today meta() works over WS only because {signal: undefined} serializes to {}. Needs onlyViews in the WS schema and in methodParams.meta (ws/subscription-server.ts:21, currently [], which strips unknown keys) — plus a docs note that older servers 400 on WS rather than ignoring. Also suggested sending the boolean instead of 'true': URLSearchParams renders it identically for HTTP, and ApiGateway.meta({ onlyViews?: boolean }) is what the non-HTTP paths receive.
2 Low client-react/src/hooks/cube-fetch.js:37 refetch({ onlyViews }) is ignored — load() only reads loadOptions.query. Prop-toggle refetch works, so not a blocker.

What's good

  • Conditional spread keeps the no-option request byte-identical — and there's a test asserting exactly that, plus one for false. That's the right thing to pin down.
  • MetaMethodOptions as LoadMethodOptions & {…} matches the existing CubeSqlOptions pattern; all three overloads widened consistently.
  • HttpTransport tests assert the actual URL, not just that a param was forwarded — the no-query-string case would catch an accidental ?onlyViews=false or ?signal=undefined regression.
  • Docblock and docs both state that older servers return the full model, so callers don't treat the response as trusted-filtered. Worth keeping that framing — it's the kind of thing clients otherwise assume.
  • React typing tightened from Omit<CubeFetchOptions, 'query'> (which silently accepted the extra key) to an explicit type. Non-exported CubeMetaFetchOptions is consistent with the unexported CubeFetchOptions next to it, so no complaint there.

Security / performance

No concerns. The flag only narrows the model server-side; filterVisibleItemsInMeta still runs after the type === 'view' filter (gateway.ts:714-717), so visibility/security-context filtering is unaffected — this can't widen what a caller sees. Payload reduction is the point, and it's real on large models.

Testing

HTTP coverage is solid. Two gaps:

I could not run the suites here: this checkout has no node_modules (no install step in the review job), so yarn unit / yarn lint / tsc --noEmit were not executed. The reported results in the PR body are unverified by me; nothing I found suggests they're wrong.

Documentation

docs-mintlify (correct site — /docs is deprecated) edits look right: new MetaMethodOptions section, meta() signatures updated, usage snippet, and the #metamethodoptions anchor matches the ### \MetaMethodOptions`` heading. If finding #1 is addressed, the WS caveat belongs here too.

· branch [`feat/client-only-views-meta`](https://github.com/cube-js/cube/tree/feat/client-only-views-meta)

@mintlify

mintlify Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
cubed3 🟢 Ready View Preview Jul 28, 2026, 6:12 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Comment thread packages/cubejs-client-core/src/index.ts Outdated
Comment thread packages/cubejs-client-react/src/hooks/cube-fetch.js Outdated
Addresses review on #11391.

`meta({ onlyViews: true })` over `WebSocketTransport` was a hard 400 rather
than a silently-ignored flag: `WebSocketTransport.request` puts the whole
params object into the message, and `message-schema.ts` validated `meta`
params with `z.object({}).strict()`. Today `meta()` works over WS only because
`{ signal: undefined }` serializes to `{}`.

Allow `onlyViews` in the `meta` params schema and add it to `methodParams` in
`subscription-server.ts` — with `[]` the key was stripped before reaching
`ApiGateway.meta` even once the schema accepted it. Unknown keys are still
rejected.

Also send the boolean instead of `'true'` from the client. `URLSearchParams`
renders it identically, so the HTTP wire format is unchanged, and the boolean
is what `ApiGateway.meta({ onlyViews?: boolean })` expects on the non-HTTP
paths.

Older servers still reject the flag over WS, so the docblock and docs now
distinguish the two transports.

`useCubeFetch`'s `refetch({ onlyViews })` now overrides the hook-level value
instead of being ignored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vasilev-alex
vasilev-alex requested a review from a team as a code owner July 28, 2026 06:31
@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Claude finished @vasilev-alex's task in 2m 2s —— View job


Re-review: both prior findings fixed. No blockers; 3 nits, none requiring action.

Full review

Todos

  • Gather context (new commit 8f4de9a)
  • Verify WS schema + methodParams fix
  • Verify boolean-vs-string switch and HTTP wire format
  • Check the message.params?.query narrowing in the catch block
  • Review new WS tests
  • Docs re-check
  • Resolve stale threads + post summary

Prior findings — verified fixed

#1 (medium) — WS 400. Both halves landed, and the second half was the easy one to miss:

  • message-schema.ts:48params: z.object({ onlyViews: z.boolean().optional() }).strict().optional(). Still .strict(), so unknown keys keep rejecting.
  • subscription-server.ts:21meta: ['onlyViews']. Necessary because collectedParams only copies keys listed in methodParams (subscription-server.ts:178-182); schema alone would have validated the message and then silently dropped the flag.
  • client-core/src/index.ts:737 now sends the boolean. Confirmed the HTTP path is unaffected: URLSearchParams renders true as onlyViews=true, which is what gateway.ts:452's req.query.onlyViews === 'true' compares against, and HttpTransport.test.ts pins the resulting URL.
  • End-to-end on WS: WebSocketTransport.request destructures baseRequestId and passes the rest as params, so {onlyViews: true} (with signal: undefined dropped by JSON.stringify) validates, survives collectedParams, and reaches ApiGateway.meta({ onlyViews?: boolean }) (gateway.ts:689-694) with the right type. The meta params schema being .optional() means a bare meta message with no params still works, so nothing regressed for existing WS callers.

#2 (low) — refetch override. cube-fetch.js:22 uses 'onlyViews' in loadOptions rather than ||, so refetch({ onlyViews: false }) correctly overrides a hook-level true instead of falling through. That's the right shape.

Both threads resolved.

The incidental type fix

subscription-server.ts:213-215 was forced by the schema change and the narrowing is correct, not a workaround: with meta params no longer Record<string, never>, message.params?.query stops type-checking, and 'params' in message && message.params && 'query' in message.params narrows the discriminated union to the branches that actually carry a query. Behavior is identical for every method that had a query before, and undefined for meta as before. Worth noting the catch-block value is only used for error logging, so there's no behavior surface here at all.

New tests

The three subscription-server.test.ts cases target exactly the failure mode and its boundaries:

  • onlyViews: true reaches ApiGateway.meta — and asserts handleError was not called, which is what distinguishes "forwarded" from "rejected but mock still resolved."
  • 'true' (string) rejected — pins that the wire type is boolean now, so a future revert of the client-side 'true'true change fails loudly instead of silently 400-ing users.
  • unknown key still rejected — guards against someone reaching for .passthrough() to fix a similar report later.

Remaining nits (all optional)

  1. refetch is untyped. CubeFetchResult<T> (client-react/index.d.ts:512-516) declares isLoading / error / response but not refetch, even though the hook returns it. Pre-existing, but this PR is the first change that makes refetch's argument meaningful (refetch({ onlyViews: true })), so TS consumers can't reach the thing you just enabled without an as any. Adding refetch: (options?: { query?: Query; onlyViews?: boolean }) => Promise<void> would close it — arguably its own PR, since it affects all the useCubeFetch-based hooks.
  2. Docs inconsistency. The meta() section's <Info> block splits compatibility by transport, but the MetaMethodOptions table entry (line ~897) still only mentions the HTTP behavior. Someone reading the type table alone gets the pre-fix story. One clause would sync them.
  3. No client-side WS test. Gateway-side coverage is where the bug actually lived, so this isn't a gap in the fix — but a cubejs-client-ws-transport test asserting the serialized params would pin the client half of the contract too. Low value given the schema test exists.

Security / performance

Unchanged from my first pass, and the WS addition doesn't alter it: onlyViews only narrows the model, and filterVisibleItemsInMeta still runs after the type === 'view' filter (gateway.ts:714-717), so visibility and security-context filtering are untouched. The WS path now accepts one extra boolean, validated by zod before it reaches the gateway — no new untrusted surface.

Verification caveat

I could not run any suite: this checkout has no node_modules at the root or in packages/cubejs-api-gateway (the review job doesn't install), so yarn test / yarn lint / tsc --noEmit were not executed. The PR body's 17 passed and the earlier results are unverified by me. Everything above is from reading the code; nothing I found suggests the reported results are wrong, and the type narrowing in particular I reasoned through rather than compiled — worth a CI confirmation on the cubejs-api-gateway build.

· branch [`feat/client-only-views-meta`](https://github.com/cube-js/cube/tree/feat/client-only-views-meta)

@vasilev-alex

Copy link
Copy Markdown
Member Author

Both findings addressed in bc91954...feat/client-only-views-meta — details in the inline replies.

Medium (WS 400). Real bug, fixed on the server side as suggested: onlyViews: z.boolean().optional() in the meta params schema plus meta: ['onlyViews'] in methodParams (the schema alone wasn't enough — collectedParams only copies listed keys). Unknown keys are still rejected. The client now sends the boolean rather than 'true'; URLSearchParams renders it identically so the HTTP wire format is unchanged.

This does mean the PR now touches the gateway, which it deliberately didn't before. The original scoping assumed "server side already works" — true for HTTP, not for WS.

Low (refetch). Applied.

Docs. The compatibility note is now split by transport: older servers ignore the param over HTTP, but reject the message with a 400 over WS. Same split in the meta() docblock and the React onlyViews docblock.

Test results (you noted you couldn't run these — for the record, from a checkout with node_modules installed):

packages/cubejs-client-core
$ yarn unit          →  15 files, 184 tests passed
$ yarn lint          →  clean
$ npx tsc --noEmit   →  clean

packages/cubejs-api-gateway
$ npx tsc            →  no new errors (2 pre-existing, identical on a stashed
                        baseline: a stale @cubejs-backend/native build in this
                        checkout — gateway.ts:22 rowsToColumnar, sql-server.ts:131)
$ npx jest dist/test/ws/subscription-server.test.js  →  17 passed
$ npx eslint src/ws/ test/ws/ --ext .ts              →  clean

packages/cubejs-client-react
$ yarn lint          →  clean

eslint src/hooks/cube-fetch.js still reports the same 4 pre-existing errors on untouched lines (the package's lint glob doesn't reach src/hooks); my lines are clean.

On the WS test gap you flagged: the new tests are gateway-side (subscription-server.test.ts) rather than in cubejs-client-ws-transport, since that's where the rejection actually happened. Happy to add a transport-side serialization test too if you'd prefer it pinned from both ends.

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 58.99%. Comparing base (c05b9d2) to head (8f4de9a).

Files with missing lines Patch % Lines
...s/cubejs-api-gateway/src/ws/subscription-server.ts 0.00% 1 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (c05b9d2) and HEAD (8f4de9a). Click for more details.

HEAD has 1 upload less than BASE
Flag BASE (c05b9d2) HEAD (8f4de9a)
cubesql 1 0
Additional details and impacted files
@@             Coverage Diff             @@
##           master   #11391       +/-   ##
===========================================
- Coverage   83.93%   58.99%   -24.95%     
===========================================
  Files         257      223       -34     
  Lines       80755    17891    -62864     
  Branches        0     3636     +3636     
===========================================
- Hits        67782    10554    -57228     
+ Misses      12973     6818     -6155     
- Partials        0      519      +519     
Flag Coverage Δ
cube-backend 58.99% <0.00%> (?)
cubesql ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@vasilev-alex
vasilev-alex merged commit ef119be into master Jul 28, 2026
50 checks passed
@vasilev-alex
vasilev-alex deleted the feat/client-only-views-meta branch July 28, 2026 06:55
@mintlify

mintlify Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
cubed3 🟡 Building Jul 28, 2026, 6:11 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

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

Labels

client:core Issues relating to the JavaScript client SDK client:react Issues relating to the React client SDK javascript Pull requests that update Javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant