From 6ebb882073548d0de83715bb65a44eb23bc8e79c Mon Sep 17 00:00:00 2001 From: Elior Hamamy Date: Sun, 4 Jan 2026 14:41:39 +0200 Subject: [PATCH 01/11] Implement dev mode header for API requests Add support for development mode in API requests --- src/modules/entities.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/modules/entities.ts b/src/modules/entities.ts index b59a29c..532da5e 100644 --- a/src/modules/entities.ts +++ b/src/modules/entities.ts @@ -49,6 +49,13 @@ function createEntityHandler( entityName: string ): EntityHandler { const baseURL = `/apps/${appId}/entities/${entityName}`; + const isDevMode = new URLSearchParams(window.location.search).get("use_dev_table") === "true"; + + axios.interceptors.request.use((config) => { + config.headers = config.headers ?? {}; + config.headers["X-Dev-Mode"] = String(isDevMode); + return config; + }); return { // List entities with optional pagination and sorting From b2daa49218f4defda63dc3e7a4d47c60ad610fb1 Mon Sep 17 00:00:00 2001 From: eliorh Date: Sun, 4 Jan 2026 17:22:58 +0200 Subject: [PATCH 02/11] new header for entities prod or dev mode --- src/modules/entities.ts | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/modules/entities.ts b/src/modules/entities.ts index 532da5e..fff7762 100644 --- a/src/modules/entities.ts +++ b/src/modules/entities.ts @@ -49,13 +49,10 @@ function createEntityHandler( entityName: string ): EntityHandler { const baseURL = `/apps/${appId}/entities/${entityName}`; - const isDevMode = new URLSearchParams(window.location.search).get("use_dev_table") === "true"; - - axios.interceptors.request.use((config) => { - config.headers = config.headers ?? {}; - config.headers["X-Dev-Mode"] = String(isDevMode); - return config; - }); + const isDevMode = typeof window !== "undefined" + ? new URLSearchParams(window.location.search).get("use_dev_table") === "true" + : false; + const headers = { "X-Use-Dev-Table": String(isDevMode) }; return { // List entities with optional pagination and sorting @@ -67,7 +64,7 @@ function createEntityHandler( if (fields) params.fields = Array.isArray(fields) ? fields.join(",") : fields; - return axios.get(baseURL, { params }); + return axios.get(baseURL, { params, headers }); }, // Filter entities based on query @@ -88,37 +85,37 @@ function createEntityHandler( if (fields) params.fields = Array.isArray(fields) ? fields.join(",") : fields; - return axios.get(baseURL, { params }); + return axios.get(baseURL, { params, headers }); }, // Get entity by ID async get(id: string) { - return axios.get(`${baseURL}/${id}`); + return axios.get(`${baseURL}/${id}`, { headers }); }, // Create new entity async create(data: Record) { - return axios.post(baseURL, data); + return axios.post(baseURL, data, { headers }); }, // Update entity by ID async update(id: string, data: Record) { - return axios.put(`${baseURL}/${id}`, data); + return axios.put(`${baseURL}/${id}`, data, { headers }); }, // Delete entity by ID async delete(id: string) { - return axios.delete(`${baseURL}/${id}`); + return axios.delete(`${baseURL}/${id}`, { headers }); }, // Delete multiple entities based on query async deleteMany(query: Record) { - return axios.delete(baseURL, { data: query }); + return axios.delete(baseURL, { data: query, headers }); }, // Create multiple entities in a single request async bulkCreate(data: Record[]) { - return axios.post(`${baseURL}/bulk`, data); + return axios.post(`${baseURL}/bulk`, data, { headers }); }, // Import entities from a file @@ -128,6 +125,7 @@ function createEntityHandler( return axios.post(`${baseURL}/import`, formData, { headers: { + ...headers, "Content-Type": "multipart/form-data", }, }); From 4d8b7f793c6b7d24b41cebbcc5b7ba129db2c45d Mon Sep 17 00:00:00 2001 From: eliorh Date: Mon, 5 Jan 2026 10:21:14 +0200 Subject: [PATCH 03/11] new header for entities prod or dev mode --- src/modules/entities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/entities.ts b/src/modules/entities.ts index fff7762..8757908 100644 --- a/src/modules/entities.ts +++ b/src/modules/entities.ts @@ -52,7 +52,7 @@ function createEntityHandler( const isDevMode = typeof window !== "undefined" ? new URLSearchParams(window.location.search).get("use_dev_table") === "true" : false; - const headers = { "X-Use-Dev-Table": String(isDevMode) }; + const headers = { "X-Dev-Mode": String(isDevMode) }; return { // List entities with optional pagination and sorting From 9d02798ea4cf84157f48a74c909635af697e0660 Mon Sep 17 00:00:00 2001 From: eliorh Date: Mon, 5 Jan 2026 19:05:16 +0200 Subject: [PATCH 04/11] new header for entities prod or dev mode --- src/modules/entities.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/entities.ts b/src/modules/entities.ts index 8757908..1206c49 100644 --- a/src/modules/entities.ts +++ b/src/modules/entities.ts @@ -50,9 +50,9 @@ function createEntityHandler( ): EntityHandler { const baseURL = `/apps/${appId}/entities/${entityName}`; const isDevMode = typeof window !== "undefined" - ? new URLSearchParams(window.location.search).get("use_dev_table") === "true" + ? new URLSearchParams(window.location.search).get("use_staging_db") === "true" : false; - const headers = { "X-Dev-Mode": String(isDevMode) }; + const headers = { "X-Use-Staging-DB": String(isDevMode) }; return { // List entities with optional pagination and sorting From bc1979b47a1a36ce1e2644fd3ffe97face890e4d Mon Sep 17 00:00:00 2001 From: eliorh Date: Tue, 6 Jan 2026 17:28:08 +0200 Subject: [PATCH 05/11] new header for entities prod or dev mode --- src/client.ts | 9 +++++++++ src/modules/entities.ts | 21 ++++++++------------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/client.ts b/src/client.ts index 6b9448f..d44ceeb 100644 --- a/src/client.ts +++ b/src/client.ts @@ -56,6 +56,11 @@ export type { Base44Client, CreateClientConfig, CreateClientOptions }; * ``` */ export function createClient(config: CreateClientConfig): Base44Client { + // Auto-detect staging mode from URL if in browser and not explicitly set + const autoDetectStagingDb = typeof window !== "undefined" + ? new URLSearchParams(window.location.search).get("use_staging_db") === "true" + : false; + const { serverUrl = "https://base44.app", appId, @@ -66,6 +71,7 @@ export function createClient(config: CreateClientConfig): Base44Client { options, functionsVersion, headers: optionalHeaders, + useStagingDb = autoDetectStagingDb, } = config; const socketConfig: RoomsSocketConfig = { @@ -90,6 +96,7 @@ export function createClient(config: CreateClientConfig): Base44Client { const headers = { ...optionalHeaders, "X-App-Id": String(appId), + "Base44-Use-Staging-DB": String(useStagingDb), }; const functionHeaders = functionsVersion @@ -346,6 +353,7 @@ export function createClientFromRequest(request: Request): Base44Client { const appId = request.headers.get("Base44-App-Id"); const serverUrlHeader = request.headers.get("Base44-Api-Url"); const functionsVersion = request.headers.get("Base44-Functions-Version"); + const useStagingDb = request.headers.get("Base44-Use-Staging-DB") === "true"; const stateHeader = request.headers.get("Base44-State"); if (!appId) { @@ -396,6 +404,7 @@ export function createClientFromRequest(request: Request): Base44Client { token: userToken, serviceToken: serviceRoleToken, functionsVersion: functionsVersion ?? undefined, + useStagingDb: useStagingDb ?? undefined, headers: additionalHeaders, }); } diff --git a/src/modules/entities.ts b/src/modules/entities.ts index 1206c49..b59a29c 100644 --- a/src/modules/entities.ts +++ b/src/modules/entities.ts @@ -49,10 +49,6 @@ function createEntityHandler( entityName: string ): EntityHandler { const baseURL = `/apps/${appId}/entities/${entityName}`; - const isDevMode = typeof window !== "undefined" - ? new URLSearchParams(window.location.search).get("use_staging_db") === "true" - : false; - const headers = { "X-Use-Staging-DB": String(isDevMode) }; return { // List entities with optional pagination and sorting @@ -64,7 +60,7 @@ function createEntityHandler( if (fields) params.fields = Array.isArray(fields) ? fields.join(",") : fields; - return axios.get(baseURL, { params, headers }); + return axios.get(baseURL, { params }); }, // Filter entities based on query @@ -85,37 +81,37 @@ function createEntityHandler( if (fields) params.fields = Array.isArray(fields) ? fields.join(",") : fields; - return axios.get(baseURL, { params, headers }); + return axios.get(baseURL, { params }); }, // Get entity by ID async get(id: string) { - return axios.get(`${baseURL}/${id}`, { headers }); + return axios.get(`${baseURL}/${id}`); }, // Create new entity async create(data: Record) { - return axios.post(baseURL, data, { headers }); + return axios.post(baseURL, data); }, // Update entity by ID async update(id: string, data: Record) { - return axios.put(`${baseURL}/${id}`, data, { headers }); + return axios.put(`${baseURL}/${id}`, data); }, // Delete entity by ID async delete(id: string) { - return axios.delete(`${baseURL}/${id}`, { headers }); + return axios.delete(`${baseURL}/${id}`); }, // Delete multiple entities based on query async deleteMany(query: Record) { - return axios.delete(baseURL, { data: query, headers }); + return axios.delete(baseURL, { data: query }); }, // Create multiple entities in a single request async bulkCreate(data: Record[]) { - return axios.post(`${baseURL}/bulk`, data, { headers }); + return axios.post(`${baseURL}/bulk`, data); }, // Import entities from a file @@ -125,7 +121,6 @@ function createEntityHandler( return axios.post(`${baseURL}/import`, formData, { headers: { - ...headers, "Content-Type": "multipart/form-data", }, }); From 7ff07124097e4dadb03ebb40ab433c039c10fa9c Mon Sep 17 00:00:00 2001 From: eliorh Date: Tue, 6 Jan 2026 17:29:25 +0200 Subject: [PATCH 06/11] new header for entities prod or dev mode --- tests/unit/client.test.js | 104 +++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/tests/unit/client.test.js b/tests/unit/client.test.js index 74c97aa..2e60bec 100644 --- a/tests/unit/client.test.js +++ b/tests/unit/client.test.js @@ -254,7 +254,46 @@ describe('createClientFromRequest', () => { }; const client = createClientFromRequest(mockRequest); - + + expect(client).toBeDefined(); + const config = client.getConfig(); + expect(config.appId).toBe('test-app-id'); + }); + + test('should propagate Base44-Use-Staging-DB header when present', () => { + const mockRequest = { + headers: { + get: (name) => { + const headers = { + 'Base44-App-Id': 'test-app-id', + 'Base44-Use-Staging-DB': 'true' + }; + return headers[name] || null; + } + } + }; + + const client = createClientFromRequest(mockRequest); + + expect(client).toBeDefined(); + const config = client.getConfig(); + expect(config.appId).toBe('test-app-id'); + }); + + test('should work without Base44-Use-Staging-DB header', () => { + const mockRequest = { + headers: { + get: (name) => { + const headers = { + 'Base44-App-Id': 'test-app-id' + }; + return headers[name] || null; + } + } + }; + + const client = createClientFromRequest(mockRequest); + expect(client).toBeDefined(); const config = client.getConfig(); expect(config.appId).toBe('test-app-id'); @@ -551,4 +590,67 @@ describe('Service Role Authorization Headers', () => { expect(scope.isDone()).toBe(true); }); + test('should propagate Base44-Use-Staging-DB header in API requests when created from request', async () => { + const mockRequest = { + headers: { + get: (name) => { + const headers = { + 'Authorization': 'Bearer user-token-123', + 'Base44-App-Id': appId, + 'Base44-Api-Url': serverUrl, + 'Base44-Use-Staging-DB': 'true' + }; + return headers[name] || null; + } + } + }; + + const client = createClientFromRequest(mockRequest); + + // Mock entities request and verify Base44-Use-Staging-DB header is present + scope.get(`/api/apps/${appId}/entities/Todo`) + .matchHeader('Base44-Use-Staging-DB', 'true') + .matchHeader('Authorization', 'Bearer user-token-123') + .reply(200, { items: [], total: 0 }); + + // Make request + await client.entities.Todo.list(); + + // Verify all mocks were called (including header match) + expect(scope.isDone()).toBe(true); + }); + + test('should propagate Base44-Use-Staging-DB header in service role API requests', async () => { + const mockRequest = { + headers: { + get: (name) => { + const headers = { + 'Base44-Service-Authorization': 'Bearer service-token-123', + 'Base44-App-Id': appId, + 'Base44-Api-Url': serverUrl, + 'Base44-Use-Staging-DB': 'true' + }; + return headers[name] || null; + } + } + }; + + const client = createClientFromRequest(mockRequest); + + // Mock service role entities request and verify Base44-Use-Staging-DB header is present + scope.get(`/api/apps/${appId}/entities/User/123`) + .matchHeader('Base44-Use-Staging-DB', 'true') + .matchHeader('Authorization', 'Bearer service-token-123') + .reply(200, { id: '123', name: 'Test User' }); + + // Make request using service role + const result = await client.asServiceRole.entities.User.get('123'); + + // Verify response + expect(result.id).toBe('123'); + + // Verify all mocks were called (including header match) + expect(scope.isDone()).toBe(true); + }); + }); \ No newline at end of file From 4a0c8f7c06e0a93e342498dd87752f8b88cafa2e Mon Sep 17 00:00:00 2001 From: eliorh Date: Tue, 6 Jan 2026 17:29:35 +0200 Subject: [PATCH 07/11] new header for entities prod or dev mode --- src/client.types.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/client.types.ts b/src/client.types.ts index a86af41..f787665 100644 --- a/src/client.types.ts +++ b/src/client.types.ts @@ -62,6 +62,11 @@ export interface CreateClientConfig { * @internal */ headers?: Record; + /** + * Whether to use the staging database. Defaults to false. + * When true, API requests will use the staging database instead of production. + */ + useStagingDb?: boolean; /** * Additional client options. */ From ea2ab35f4be2283118a9e711ba37e34f654815b6 Mon Sep 17 00:00:00 2001 From: eliorh Date: Tue, 6 Jan 2026 17:30:45 +0200 Subject: [PATCH 08/11] new header for entities prod or dev mode --- tests/unit/client.test.js | 104 +------------------------------------- 1 file changed, 1 insertion(+), 103 deletions(-) diff --git a/tests/unit/client.test.js b/tests/unit/client.test.js index 2e60bec..74c97aa 100644 --- a/tests/unit/client.test.js +++ b/tests/unit/client.test.js @@ -254,46 +254,7 @@ describe('createClientFromRequest', () => { }; const client = createClientFromRequest(mockRequest); - - expect(client).toBeDefined(); - const config = client.getConfig(); - expect(config.appId).toBe('test-app-id'); - }); - - test('should propagate Base44-Use-Staging-DB header when present', () => { - const mockRequest = { - headers: { - get: (name) => { - const headers = { - 'Base44-App-Id': 'test-app-id', - 'Base44-Use-Staging-DB': 'true' - }; - return headers[name] || null; - } - } - }; - - const client = createClientFromRequest(mockRequest); - - expect(client).toBeDefined(); - const config = client.getConfig(); - expect(config.appId).toBe('test-app-id'); - }); - - test('should work without Base44-Use-Staging-DB header', () => { - const mockRequest = { - headers: { - get: (name) => { - const headers = { - 'Base44-App-Id': 'test-app-id' - }; - return headers[name] || null; - } - } - }; - - const client = createClientFromRequest(mockRequest); - + expect(client).toBeDefined(); const config = client.getConfig(); expect(config.appId).toBe('test-app-id'); @@ -590,67 +551,4 @@ describe('Service Role Authorization Headers', () => { expect(scope.isDone()).toBe(true); }); - test('should propagate Base44-Use-Staging-DB header in API requests when created from request', async () => { - const mockRequest = { - headers: { - get: (name) => { - const headers = { - 'Authorization': 'Bearer user-token-123', - 'Base44-App-Id': appId, - 'Base44-Api-Url': serverUrl, - 'Base44-Use-Staging-DB': 'true' - }; - return headers[name] || null; - } - } - }; - - const client = createClientFromRequest(mockRequest); - - // Mock entities request and verify Base44-Use-Staging-DB header is present - scope.get(`/api/apps/${appId}/entities/Todo`) - .matchHeader('Base44-Use-Staging-DB', 'true') - .matchHeader('Authorization', 'Bearer user-token-123') - .reply(200, { items: [], total: 0 }); - - // Make request - await client.entities.Todo.list(); - - // Verify all mocks were called (including header match) - expect(scope.isDone()).toBe(true); - }); - - test('should propagate Base44-Use-Staging-DB header in service role API requests', async () => { - const mockRequest = { - headers: { - get: (name) => { - const headers = { - 'Base44-Service-Authorization': 'Bearer service-token-123', - 'Base44-App-Id': appId, - 'Base44-Api-Url': serverUrl, - 'Base44-Use-Staging-DB': 'true' - }; - return headers[name] || null; - } - } - }; - - const client = createClientFromRequest(mockRequest); - - // Mock service role entities request and verify Base44-Use-Staging-DB header is present - scope.get(`/api/apps/${appId}/entities/User/123`) - .matchHeader('Base44-Use-Staging-DB', 'true') - .matchHeader('Authorization', 'Bearer service-token-123') - .reply(200, { id: '123', name: 'Test User' }); - - // Make request using service role - const result = await client.asServiceRole.entities.User.get('123'); - - // Verify response - expect(result.id).toBe('123'); - - // Verify all mocks were called (including header match) - expect(scope.isDone()).toBe(true); - }); - }); \ No newline at end of file From 29ae9793751318f492d719d90f6888fe35c6f974 Mon Sep 17 00:00:00 2001 From: eliorh Date: Tue, 6 Jan 2026 17:33:17 +0200 Subject: [PATCH 09/11] new header for entities prod or dev mode --- tests/unit/client.test.js | 41 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/unit/client.test.js b/tests/unit/client.test.js index 74c97aa..09eee00 100644 --- a/tests/unit/client.test.js +++ b/tests/unit/client.test.js @@ -254,7 +254,46 @@ describe('createClientFromRequest', () => { }; const client = createClientFromRequest(mockRequest); - + + expect(client).toBeDefined(); + const config = client.getConfig(); + expect(config.appId).toBe('test-app-id'); + }); + + test('should propagate Base44-Use-Staging-DB header when present', () => { + const mockRequest = { + headers: { + get: (name) => { + const headers = { + 'Base44-App-Id': 'test-app-id', + 'Base44-Use-Staging-DB': 'true' + }; + return headers[name] || null; + } + } + }; + + const client = createClientFromRequest(mockRequest); + + expect(client).toBeDefined(); + const config = client.getConfig(); + expect(config.appId).toBe('test-app-id'); + }); + + test('should work without Base44-Use-Staging-DB header', () => { + const mockRequest = { + headers: { + get: (name) => { + const headers = { + 'Base44-App-Id': 'test-app-id' + }; + return headers[name] || null; + } + } + }; + + const client = createClientFromRequest(mockRequest); + expect(client).toBeDefined(); const config = client.getConfig(); expect(config.appId).toBe('test-app-id'); From 8063c5f20e828b389eb14e31b12cb9ebc639c345 Mon Sep 17 00:00:00 2001 From: eliorh Date: Tue, 6 Jan 2026 19:55:40 +0200 Subject: [PATCH 10/11] new header for entities prod or dev mode --- src/client.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client.ts b/src/client.ts index d44ceeb..3ce5709 100644 --- a/src/client.ts +++ b/src/client.ts @@ -56,11 +56,6 @@ export type { Base44Client, CreateClientConfig, CreateClientOptions }; * ``` */ export function createClient(config: CreateClientConfig): Base44Client { - // Auto-detect staging mode from URL if in browser and not explicitly set - const autoDetectStagingDb = typeof window !== "undefined" - ? new URLSearchParams(window.location.search).get("use_staging_db") === "true" - : false; - const { serverUrl = "https://base44.app", appId, @@ -71,9 +66,14 @@ export function createClient(config: CreateClientConfig): Base44Client { options, functionsVersion, headers: optionalHeaders, - useStagingDb = autoDetectStagingDb, + useStagingDb: configUseStagingDb = false, } = config; + // URL param is source of truth for staging DB in browser + const urlHasStagingDb = typeof window !== "undefined" + && new URLSearchParams(window.location.search).get("use_staging_db") === "true"; + const useStagingDb = urlHasStagingDb || configUseStagingDb; + const socketConfig: RoomsSocketConfig = { serverUrl, mountPath: "/ws-user-apps/socket.io/", From 829da82e5c45743e05fa43bc347c019ce1e61884 Mon Sep 17 00:00:00 2001 From: eliorh Date: Wed, 7 Jan 2026 11:03:11 +0200 Subject: [PATCH 11/11] update staging DB configuration precedence logic --- src/client.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.ts b/src/client.ts index 3ce5709..acb5b01 100644 --- a/src/client.ts +++ b/src/client.ts @@ -66,13 +66,13 @@ export function createClient(config: CreateClientConfig): Base44Client { options, functionsVersion, headers: optionalHeaders, - useStagingDb: configUseStagingDb = false, + useStagingDb: configUseStagingDb, } = config; - // URL param is source of truth for staging DB in browser + // Config takes precedence, fallback to URL query param in browser const urlHasStagingDb = typeof window !== "undefined" && new URLSearchParams(window.location.search).get("use_staging_db") === "true"; - const useStagingDb = urlHasStagingDb || configUseStagingDb; + const useStagingDb = configUseStagingDb ?? urlHasStagingDb; const socketConfig: RoomsSocketConfig = { serverUrl,