From 5303ba1195515f783d83211f58199e982cace64a Mon Sep 17 00:00:00 2001 From: "hannes.oswald" Date: Thu, 30 Oct 2025 16:40:01 +0100 Subject: [PATCH 1/8] feat(client-core): provide cache option to load method --- packages/cubejs-client-core/src/index.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/cubejs-client-core/src/index.ts b/packages/cubejs-client-core/src/index.ts index 5e91a1e8a2899..7f7fa8467e45d 100644 --- a/packages/cubejs-client-core/src/index.ts +++ b/packages/cubejs-client-core/src/index.ts @@ -50,6 +50,10 @@ export type LoadMethodOptions = { * AbortSignal to cancel requests */ signal?: AbortSignal; + /** + * Cache mode for query execution + */ + cache?: CacheMode; }; export type DeeplyReadonly = { @@ -108,10 +112,6 @@ export type CubeSqlOptions = LoadMethodOptions & { * Query timeout in milliseconds */ timeout?: number; - /** - * Cache mode for query execution - */ - cache?: CacheMode; }; export type CubeSqlSchemaColumn = { @@ -574,7 +574,8 @@ class CubeApi { () => this.request('load', { query, queryType: 'multi', - signal: options?.signal + cache: options?.cache, + signal: options?.signal, }), (response: any) => this.loadResponseInternal(response, options), options, From 5dc0b0a07d9970a957571a29e1d2bff8337f3b0d Mon Sep 17 00:00:00 2001 From: "hannes.oswald" Date: Thu, 30 Oct 2025 16:51:48 +0100 Subject: [PATCH 2/8] feat(client-core): provide cache option to load method --- .../cubejs-client-core/test/CubeApi.test.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/cubejs-client-core/test/CubeApi.test.ts b/packages/cubejs-client-core/test/CubeApi.test.ts index d1097fe206259..bb49a9a86d86d 100644 --- a/packages/cubejs-client-core/test/CubeApi.test.ts +++ b/packages/cubejs-client-core/test/CubeApi.test.ts @@ -217,6 +217,30 @@ describe('CubeApi with Abort Signal', () => { expect(requestSpy.mock.calls[0]?.[1]?.signal).toBe(signal); }); + test('should pass cache from options to request', async () => { + // Mock for this specific test + const requestSpy = jest.spyOn(HttpTransport.prototype, 'request').mockImplementation(() => ({ + subscribe: (cb) => Promise.resolve(cb({ + status: 200, + text: () => Promise.resolve('{"results":[]}'), + json: () => Promise.resolve({ results: [] }) + } as any, + async () => undefined as any)) + })); + + const cubeApi = new CubeApi('token', { + apiUrl: 'http://localhost:4000/cubejs-api/v1' + }); + + await cubeApi.load( + { measures: ['Orders.count'] }, + { cache: "stale-if-slow" } + ); + + expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy.mock.calls[0]?.[1]?.cache).toBe("stale-if-slow"); + }); + test('options signal should override constructor signal', async () => { const constructorController = new AbortController(); const optionsController = new AbortController(); From e0ad5912d6ac6f853b32e50c0ee0d38be3d9fd6f Mon Sep 17 00:00:00 2001 From: "hannes.oswald" Date: Sat, 8 Nov 2025 10:14:27 +0100 Subject: [PATCH 3/8] run lint --- packages/cubejs-client-core/test/CubeApi.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/cubejs-client-core/test/CubeApi.test.ts b/packages/cubejs-client-core/test/CubeApi.test.ts index bb49a9a86d86d..230971e0b4f60 100644 --- a/packages/cubejs-client-core/test/CubeApi.test.ts +++ b/packages/cubejs-client-core/test/CubeApi.test.ts @@ -221,11 +221,11 @@ describe('CubeApi with Abort Signal', () => { // Mock for this specific test const requestSpy = jest.spyOn(HttpTransport.prototype, 'request').mockImplementation(() => ({ subscribe: (cb) => Promise.resolve(cb({ - status: 200, - text: () => Promise.resolve('{"results":[]}'), - json: () => Promise.resolve({ results: [] }) - } as any, - async () => undefined as any)) + status: 200, + text: () => Promise.resolve('{"results":[]}'), + json: () => Promise.resolve({ results: [] }) + } as any, + async () => undefined as any)) })); const cubeApi = new CubeApi('token', { @@ -234,11 +234,11 @@ describe('CubeApi with Abort Signal', () => { await cubeApi.load( { measures: ['Orders.count'] }, - { cache: "stale-if-slow" } + { cache: 'stale-if-slow' } ); expect(requestSpy).toHaveBeenCalled(); - expect(requestSpy.mock.calls[0]?.[1]?.cache).toBe("stale-if-slow"); + expect(requestSpy.mock.calls[0]?.[1]?.cache).toBe('stale-if-slow'); }); test('options signal should override constructor signal', async () => { From 11f29cad2df1e1b7c3b96ace08b2f16a19ad9f3d Mon Sep 17 00:00:00 2001 From: "hannes.oswald" Date: Sat, 8 Nov 2025 10:50:48 +0100 Subject: [PATCH 4/8] fix cache is part of query --- packages/cubejs-client-core/src/index.ts | 9 ++++--- packages/cubejs-client-core/src/types.ts | 1 + .../cubejs-client-core/test/CubeApi.test.ts | 24 ------------------- 3 files changed, 5 insertions(+), 29 deletions(-) diff --git a/packages/cubejs-client-core/src/index.ts b/packages/cubejs-client-core/src/index.ts index 7f7fa8467e45d..7e4cf669bad02 100644 --- a/packages/cubejs-client-core/src/index.ts +++ b/packages/cubejs-client-core/src/index.ts @@ -50,10 +50,6 @@ export type LoadMethodOptions = { * AbortSignal to cancel requests */ signal?: AbortSignal; - /** - * Cache mode for query execution - */ - cache?: CacheMode; }; export type DeeplyReadonly = { @@ -112,6 +108,10 @@ export type CubeSqlOptions = LoadMethodOptions & { * Query timeout in milliseconds */ timeout?: number; + /** + * Cache mode for query execution + */ + cache?: CacheMode; }; export type CubeSqlSchemaColumn = { @@ -574,7 +574,6 @@ class CubeApi { () => this.request('load', { query, queryType: 'multi', - cache: options?.cache, signal: options?.signal, }), (response: any) => this.loadResponseInternal(response, options), diff --git a/packages/cubejs-client-core/src/types.ts b/packages/cubejs-client-core/src/types.ts index db50bd61fcd59..398df7c5511df 100644 --- a/packages/cubejs-client-core/src/types.ts +++ b/packages/cubejs-client-core/src/types.ts @@ -115,6 +115,7 @@ export interface Query { timezone?: string; // @deprecated renewQuery?: boolean; + cache?: CacheMode; ungrouped?: boolean; responseFormat?: 'compact' | 'default'; total?: boolean; diff --git a/packages/cubejs-client-core/test/CubeApi.test.ts b/packages/cubejs-client-core/test/CubeApi.test.ts index 230971e0b4f60..d1097fe206259 100644 --- a/packages/cubejs-client-core/test/CubeApi.test.ts +++ b/packages/cubejs-client-core/test/CubeApi.test.ts @@ -217,30 +217,6 @@ describe('CubeApi with Abort Signal', () => { expect(requestSpy.mock.calls[0]?.[1]?.signal).toBe(signal); }); - test('should pass cache from options to request', async () => { - // Mock for this specific test - const requestSpy = jest.spyOn(HttpTransport.prototype, 'request').mockImplementation(() => ({ - subscribe: (cb) => Promise.resolve(cb({ - status: 200, - text: () => Promise.resolve('{"results":[]}'), - json: () => Promise.resolve({ results: [] }) - } as any, - async () => undefined as any)) - })); - - const cubeApi = new CubeApi('token', { - apiUrl: 'http://localhost:4000/cubejs-api/v1' - }); - - await cubeApi.load( - { measures: ['Orders.count'] }, - { cache: 'stale-if-slow' } - ); - - expect(requestSpy).toHaveBeenCalled(); - expect(requestSpy.mock.calls[0]?.[1]?.cache).toBe('stale-if-slow'); - }); - test('options signal should override constructor signal', async () => { const constructorController = new AbortController(); const optionsController = new AbortController(); From b339bd4d2f42e708da65f7715e1232bbcbe73ce8 Mon Sep 17 00:00:00 2001 From: "hannes.oswald" Date: Sat, 8 Nov 2025 10:57:53 +0100 Subject: [PATCH 5/8] fix lint error --- packages/cubejs-client-core/src/types.ts | 55 ++++++++++++------------ 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/packages/cubejs-client-core/src/types.ts b/packages/cubejs-client-core/src/types.ts index 398df7c5511df..0750883e7e2ad 100644 --- a/packages/cubejs-client-core/src/types.ts +++ b/packages/cubejs-client-core/src/types.ts @@ -1,6 +1,5 @@ import Meta from './Meta'; import { TimeDimensionGranularity } from './time'; -import { TransportOptions } from './HttpTransport'; export type QueryOrder = 'asc' | 'desc' | 'none'; @@ -82,6 +81,33 @@ export type BinaryOperator = | 'afterDate' | 'afterOrOnDate'; +// NOTE: This type must be kept in sync with CacheMode in @cubejs-backend/shared (packages/cubejs-backend-shared/src/shared-types.ts) +// This duplication is intentional as @cubejs-client/core does not depend on @cubejs-backend/shared +/** + * Cache mode options for query execution. + * + * - **stale-if-slow** (default): Equivalent to previously used `renewQuery: false`. + * If refresh keys are up-to-date, returns the value from cache. + * If refresh keys are expired, tries to return the value from the database. + * Returns fresh value from the database if the query executed until the first "Continue wait" interval is reached. + * Returns stale value from cache otherwise. + * + * - **stale-while-revalidate**: AKA "backgroundRefresh". + * If refresh keys are up-to-date, returns the value from cache. + * If refresh keys are expired, returns stale data from cache. + * Updates the cache in background. + * + * - **must-revalidate**: Equivalent to previously used `renewQuery: true`. + * If refresh keys are up-to-date, returns the value from cache. + * If refresh keys are expired, tries to return the value from the database. + * Returns fresh value from the database even if it takes minutes and many "Continue wait" intervals. + * + * - **no-cache**: AKA "forceRefresh". + * Skips refresh key checks. + * Returns fresh data from the database, even if it takes minutes and many "Continue wait" intervals. + */ +export type CacheMode = 'stale-if-slow' | 'stale-while-revalidate' | 'must-revalidate' | 'no-cache'; + export interface BinaryFilter { /** * @deprecated Use `member` instead. @@ -528,29 +554,4 @@ export type ProgressResponse = { timeElapsed: number; }; -// NOTE: This type must be kept in sync with CacheMode in @cubejs-backend/shared (packages/cubejs-backend-shared/src/shared-types.ts) -// This duplication is intentional as @cubejs-client/core does not depend on @cubejs-backend/shared -/** - * Cache mode options for query execution. - * - * - **stale-if-slow** (default): Equivalent to previously used `renewQuery: false`. - * If refresh keys are up-to-date, returns the value from cache. - * If refresh keys are expired, tries to return the value from the database. - * Returns fresh value from the database if the query executed until the first "Continue wait" interval is reached. - * Returns stale value from cache otherwise. - * - * - **stale-while-revalidate**: AKA "backgroundRefresh". - * If refresh keys are up-to-date, returns the value from cache. - * If refresh keys are expired, returns stale data from cache. - * Updates the cache in background. - * - * - **must-revalidate**: Equivalent to previously used `renewQuery: true`. - * If refresh keys are up-to-date, returns the value from cache. - * If refresh keys are expired, tries to return the value from the database. - * Returns fresh value from the database even if it takes minutes and many "Continue wait" intervals. - * - * - **no-cache**: AKA "forceRefresh". - * Skips refresh key checks. - * Returns fresh data from the database, even if it takes minutes and many "Continue wait" intervals. - */ -export type CacheMode = 'stale-if-slow' | 'stale-while-revalidate' | 'must-revalidate' | 'no-cache'; + From 4b7d565ad44e08a13ece274afb3a1a6f2df1116a Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Wed, 19 Nov 2025 14:05:49 +0200 Subject: [PATCH 6/8] Remove empty line in types.ts --- packages/cubejs-client-core/src/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cubejs-client-core/src/types.ts b/packages/cubejs-client-core/src/types.ts index 0750883e7e2ad..bdd8a9aa70884 100644 --- a/packages/cubejs-client-core/src/types.ts +++ b/packages/cubejs-client-core/src/types.ts @@ -554,4 +554,3 @@ export type ProgressResponse = { timeElapsed: number; }; - From ea841a72c218f2eee619319134570863bc742eed Mon Sep 17 00:00:00 2001 From: Konstantin Burkalev Date: Wed, 19 Nov 2025 14:40:43 +0200 Subject: [PATCH 7/8] Update types.ts --- packages/cubejs-client-core/src/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cubejs-client-core/src/types.ts b/packages/cubejs-client-core/src/types.ts index bdd8a9aa70884..dc40db28def9b 100644 --- a/packages/cubejs-client-core/src/types.ts +++ b/packages/cubejs-client-core/src/types.ts @@ -553,4 +553,3 @@ export type ProgressResponse = { stage: string; timeElapsed: number; }; - From 94650ecf996609ae8d887281688471d8d808a411 Mon Sep 17 00:00:00 2001 From: "hannes.oswald" Date: Mon, 24 Nov 2025 19:43:18 +0100 Subject: [PATCH 8/8] move `cache` to `LoadMethodOptions` --- packages/cubejs-client-core/src/index.ts | 9 ++-- packages/cubejs-client-core/src/types.ts | 55 ++++++++++++------------ 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/cubejs-client-core/src/index.ts b/packages/cubejs-client-core/src/index.ts index 7e4cf669bad02..a23e1b89dc342 100644 --- a/packages/cubejs-client-core/src/index.ts +++ b/packages/cubejs-client-core/src/index.ts @@ -46,6 +46,10 @@ export type LoadMethodOptions = { * Function that receives `ProgressResult` on each `Continue wait` message. */ progressCallback?(result: ProgressResult): void; + /** + * Cache mode for query execution + */ + cache?: CacheMode; /** * AbortSignal to cancel requests */ @@ -108,10 +112,6 @@ export type CubeSqlOptions = LoadMethodOptions & { * Query timeout in milliseconds */ timeout?: number; - /** - * Cache mode for query execution - */ - cache?: CacheMode; }; export type CubeSqlSchemaColumn = { @@ -575,6 +575,7 @@ class CubeApi { query, queryType: 'multi', signal: options?.signal, + cache: options?.cache, }), (response: any) => this.loadResponseInternal(response, options), options, diff --git a/packages/cubejs-client-core/src/types.ts b/packages/cubejs-client-core/src/types.ts index dc40db28def9b..c73c3dc290c83 100644 --- a/packages/cubejs-client-core/src/types.ts +++ b/packages/cubejs-client-core/src/types.ts @@ -81,33 +81,6 @@ export type BinaryOperator = | 'afterDate' | 'afterOrOnDate'; -// NOTE: This type must be kept in sync with CacheMode in @cubejs-backend/shared (packages/cubejs-backend-shared/src/shared-types.ts) -// This duplication is intentional as @cubejs-client/core does not depend on @cubejs-backend/shared -/** - * Cache mode options for query execution. - * - * - **stale-if-slow** (default): Equivalent to previously used `renewQuery: false`. - * If refresh keys are up-to-date, returns the value from cache. - * If refresh keys are expired, tries to return the value from the database. - * Returns fresh value from the database if the query executed until the first "Continue wait" interval is reached. - * Returns stale value from cache otherwise. - * - * - **stale-while-revalidate**: AKA "backgroundRefresh". - * If refresh keys are up-to-date, returns the value from cache. - * If refresh keys are expired, returns stale data from cache. - * Updates the cache in background. - * - * - **must-revalidate**: Equivalent to previously used `renewQuery: true`. - * If refresh keys are up-to-date, returns the value from cache. - * If refresh keys are expired, tries to return the value from the database. - * Returns fresh value from the database even if it takes minutes and many "Continue wait" intervals. - * - * - **no-cache**: AKA "forceRefresh". - * Skips refresh key checks. - * Returns fresh data from the database, even if it takes minutes and many "Continue wait" intervals. - */ -export type CacheMode = 'stale-if-slow' | 'stale-while-revalidate' | 'must-revalidate' | 'no-cache'; - export interface BinaryFilter { /** * @deprecated Use `member` instead. @@ -141,7 +114,6 @@ export interface Query { timezone?: string; // @deprecated renewQuery?: boolean; - cache?: CacheMode; ungrouped?: boolean; responseFormat?: 'compact' | 'default'; total?: boolean; @@ -553,3 +525,30 @@ export type ProgressResponse = { stage: string; timeElapsed: number; }; + +// NOTE: This type must be kept in sync with CacheMode in @cubejs-backend/shared (packages/cubejs-backend-shared/src/shared-types.ts) +// This duplication is intentional as @cubejs-client/core does not depend on @cubejs-backend/shared +/** + * Cache mode options for query execution. + * + * - **stale-if-slow** (default): Equivalent to previously used `renewQuery: false`. + * If refresh keys are up-to-date, returns the value from cache. + * If refresh keys are expired, tries to return the value from the database. + * Returns fresh value from the database if the query executed until the first "Continue wait" interval is reached. + * Returns stale value from cache otherwise. + * + * - **stale-while-revalidate**: AKA "backgroundRefresh". + * If refresh keys are up-to-date, returns the value from cache. + * If refresh keys are expired, returns stale data from cache. + * Updates the cache in background. + * + * - **must-revalidate**: Equivalent to previously used `renewQuery: true`. + * If refresh keys are up-to-date, returns the value from cache. + * If refresh keys are expired, tries to return the value from the database. + * Returns fresh value from the database even if it takes minutes and many "Continue wait" intervals. + * + * - **no-cache**: AKA "forceRefresh". + * Skips refresh key checks. + * Returns fresh data from the database, even if it takes minutes and many "Continue wait" intervals. + */ +export type CacheMode = 'stale-if-slow' | 'stale-while-revalidate' | 'must-revalidate' | 'no-cache';