diff --git a/.changeset/pink-fans-serve.md b/.changeset/pink-fans-serve.md new file mode 100644 index 00000000000..53d58102cd4 --- /dev/null +++ b/.changeset/pink-fans-serve.md @@ -0,0 +1,6 @@ +--- +"@builder.io/sdk": major +"@builder.io/react": major +--- + +Permanently removes the `apiEndpoint` prop from `builder.get()` and `builder.getAll()` which had options `'content'` and `'query'`. Content API is now the only possible API endpoint for content fetching. diff --git a/packages/core/src/builder.class.test.ts b/packages/core/src/builder.class.test.ts index 113f31d9b3a..f6924b58b92 100644 --- a/packages/core/src/builder.class.test.ts +++ b/packages/core/src/builder.class.test.ts @@ -499,7 +499,7 @@ describe('flushGetContentQueue', () => { ); }); - test("hits query url when apiEndpoint is undefined and format is 'html'", async () => { + test("hits content url when format is 'html'", async () => { const expectedFormat = 'html'; const result = await builder['flushGetContentQueue'](true, [ @@ -510,35 +510,6 @@ describe('flushGetContentQueue', () => { userAttributes: { respectScheduling: true }, omit: OMIT, fields: 'data', - }, - ]); - - const observerNextMock = builder.observersByKey[MODEL]?.next as jest.Mock; - - expect(observerNextMock).toBeCalledTimes(1); - expect(observerNextMock.mock.calls[0][0][0]).toStrictEqual({ - ...codegenOrQueryApiResult[MODEL][0], - variationId: expect.any(String), - }); - expect(builder['makeFetchApiCall']).toBeCalledTimes(1); - expect(builder['makeFetchApiCall']).toBeCalledWith( - `https://cdn.builder.io/api/v3/query/${API_KEY}/${MODEL}?omit=${OMIT}&apiKey=${API_KEY}&fields=data&format=${expectedFormat}&userAttributes=%7B%22respectScheduling%22%3Atrue%7D&options.${MODEL}.model=%22${MODEL}%22`, - { headers: { Authorization: `Bearer ${AUTH_TOKEN}` } } - ); - }); - - test("hits content url when apiEndpoint is 'content' and format is 'html'", async () => { - const expectedFormat = 'html'; - - const result = await builder['flushGetContentQueue'](true, [ - { - apiEndpoint: 'content', - model: MODEL, - format: expectedFormat, - key: MODEL, - userAttributes: { respectScheduling: true }, - omit: OMIT, - fields: 'data', limit: 10, }, ]); @@ -565,7 +536,7 @@ describe('flushGetContentQueue', () => { ); }); - test("hits query url when apiEndpoint is undefined and format is 'amp'", async () => { + test("hits content url when format is 'amp'", async () => { const expectedFormat = 'amp'; const result = await builder['flushGetContentQueue'](true, [ @@ -576,35 +547,6 @@ describe('flushGetContentQueue', () => { userAttributes: { respectScheduling: true }, omit: OMIT, fields: 'data', - }, - ]); - - const observerNextMock = builder.observersByKey[MODEL]?.next as jest.Mock; - - expect(observerNextMock).toBeCalledTimes(1); - expect(observerNextMock.mock.calls[0][0][0]).toStrictEqual({ - ...codegenOrQueryApiResult[MODEL][0], - variationId: expect.any(String), - }); - expect(builder['makeFetchApiCall']).toBeCalledTimes(1); - expect(builder['makeFetchApiCall']).toBeCalledWith( - `https://cdn.builder.io/api/v3/query/${API_KEY}/${MODEL}?omit=${OMIT}&apiKey=${API_KEY}&fields=data&format=${expectedFormat}&userAttributes=%7B%22respectScheduling%22%3Atrue%7D&options.${MODEL}.model=%22${MODEL}%22`, - { headers: { Authorization: `Bearer ${AUTH_TOKEN}` } } - ); - }); - - test("hits content url when apiEndpoint is 'content' and format is 'amp'", async () => { - const expectedFormat = 'amp'; - - const result = await builder['flushGetContentQueue'](true, [ - { - apiEndpoint: 'content', - model: MODEL, - format: expectedFormat, - key: MODEL, - userAttributes: { respectScheduling: true }, - omit: OMIT, - fields: 'data', limit: 10, }, ]); @@ -631,7 +573,7 @@ describe('flushGetContentQueue', () => { ); }); - test("hits query url when apiEndpoint is undefined and format is 'email'", async () => { + test("hits content url when format is 'email'", async () => { const expectedFormat = 'email'; const result = await builder['flushGetContentQueue'](true, [ @@ -642,63 +584,6 @@ describe('flushGetContentQueue', () => { userAttributes: { respectScheduling: true }, omit: OMIT, fields: 'data', - }, - ]); - - const observerNextMock = builder.observersByKey[MODEL]?.next as jest.Mock; - - expect(observerNextMock).toBeCalledTimes(1); - expect(observerNextMock.mock.calls[0][0][0]).toStrictEqual({ - ...codegenOrQueryApiResult[MODEL][0], - variationId: expect.any(String), - }); - expect(builder['makeFetchApiCall']).toBeCalledTimes(1); - expect(builder['makeFetchApiCall']).toBeCalledWith( - `https://cdn.builder.io/api/v3/query/${API_KEY}/${MODEL}?omit=${OMIT}&apiKey=${API_KEY}&fields=data&format=${expectedFormat}&userAttributes=%7B%22respectScheduling%22%3Atrue%7D&options.${MODEL}.model=%22${MODEL}%22`, - { headers: { Authorization: `Bearer ${AUTH_TOKEN}` } } - ); - }); - - test("hits query url when apiEndpoint is undefined and format is 'email' and url is passed instead of userAttributes", async () => { - const expectedFormat = 'email'; - - const result = await builder['flushGetContentQueue'](true, [ - { - model: MODEL, - format: expectedFormat, - key: MODEL, - url: '/test-page', - omit: OMIT, - fields: 'data', - }, - ]); - - const observerNextMock = builder.observersByKey[MODEL]?.next as jest.Mock; - - expect(observerNextMock).toBeCalledTimes(1); - expect(observerNextMock.mock.calls[0][0][0]).toStrictEqual({ - ...codegenOrQueryApiResult[MODEL][0], - variationId: expect.any(String), - }); - expect(builder['makeFetchApiCall']).toBeCalledTimes(1); - expect(builder['makeFetchApiCall']).toBeCalledWith( - `https://cdn.builder.io/api/v3/query/${API_KEY}/${MODEL}?omit=${OMIT}&apiKey=${API_KEY}&fields=data&format=${expectedFormat}&userAttributes=%7B%22urlPath%22%3A%22%2Ftest-page%22%2C%22host%22%3A%22localhost%22%2C%22device%22%3A%22desktop%22%7D&options.${MODEL}.model=%22${MODEL}%22`, - { headers: { Authorization: `Bearer ${AUTH_TOKEN}` } } - ); - }); - - test("hits content url when apiEndpoint is 'content' and format is 'email'", async () => { - const expectedFormat = 'email'; - - const result = await builder['flushGetContentQueue'](true, [ - { - apiEndpoint: 'content', - model: MODEL, - format: expectedFormat, - key: MODEL, - userAttributes: { respectScheduling: true }, - omit: OMIT, - fields: 'data', limit: 10, }, ]); @@ -724,42 +609,4 @@ describe('flushGetContentQueue', () => { { headers: { Authorization: `Bearer ${AUTH_TOKEN}` } } ); }); - - test("hits content url when apiEndpoint is 'content' and format is 'email' and url is passed instead of userAttributes", async () => { - const expectedFormat = 'email'; - - const result = await builder['flushGetContentQueue'](true, [ - { - apiEndpoint: 'content', - model: MODEL, - format: expectedFormat, - key: MODEL, - url: '/test-page', - omit: OMIT, - fields: 'data', - limit: 10, - }, - ]); - - const observerNextMock = builder.observersByKey[MODEL]?.next as jest.Mock; - - expect(observerNextMock).toBeCalledTimes(1); - expect(observerNextMock.mock.calls[0][0][0]).toStrictEqual({ - ...contentApiResult.results[0], - variationId: expect.any(String), - }); - expect(observerNextMock.mock.calls[0][0][1]).toStrictEqual({ - ...contentApiResult.results[1], - }); - expect(observerNextMock.mock.calls[0][0][2]).toStrictEqual({ - ...contentApiResult.results[2], - variationId: expect.any(String), - }); - - expect(builder['makeFetchApiCall']).toBeCalledTimes(1); - expect(builder['makeFetchApiCall']).toBeCalledWith( - `https://cdn.builder.io/api/v3/content/${MODEL}?omit=data.blocks&apiKey=${API_KEY}&fields=data&format=${expectedFormat}&userAttributes=%7B%22urlPath%22%3A%22%2Ftest-page%22%2C%22host%22%3A%22localhost%22%2C%22device%22%3A%22desktop%22%7D&limit=10&model=%22${MODEL}%22&enrich=true`, - { headers: { Authorization: `Bearer ${AUTH_TOKEN}` } } - ); - }); }); diff --git a/packages/core/src/builder.class.ts b/packages/core/src/builder.class.ts index 875e1b44fcd..1965611c5d2 100644 --- a/packages/core/src/builder.class.ts +++ b/packages/core/src/builder.class.ts @@ -275,12 +275,6 @@ type AllowEnrich = | { apiVersion?: never; enrich?: boolean }; export type GetContentOptions = AllowEnrich & { - /** - * Dictates which API endpoint is used when fetching content. Allows `'content'` and `'query'`. - * Defaults to `'query'`. - */ - apiEndpoint?: 'content' | 'query'; - /** * Optional fetch options to be passed as the second argument to the `fetch` function. */ @@ -2447,8 +2441,6 @@ export class Builder { const queue = useQueue || (usePastQueue ? this.priorContentQueue : this.getContentQueue) || []; - const apiEndpoint = queue[0].apiEndpoint || 'query'; - // TODO: do this on every request send? this.getOverridesFromQueryString(); @@ -2542,6 +2534,9 @@ export class Builder { } } + const isApiCallForCodegen = + queue[0].options?.format === 'solid' || queue[0].options?.format === 'react'; + for (const options of queue) { const format = options.format; @@ -2580,7 +2575,7 @@ export class Builder { for (const key of properties) { const value = options[key]; if (value !== undefined) { - if (apiEndpoint === 'query') { + if (isApiCallForCodegen) { queryParams.options = queryParams.options || {}; queryParams.options[options.key!] = queryParams.options[options.key!] || {}; queryParams.options[options.key!][key] = JSON.stringify(value); @@ -2606,10 +2601,8 @@ export class Builder { } const format = queryParams.format; - const isApiCallForCodegen = format === 'solid' || format === 'react'; - const isApiCallForCodegenOrQuery = isApiCallForCodegen || apiEndpoint === 'query'; - if (apiEndpoint === 'content') { + if (!isApiCallForCodegen) { queryParams.enrich = true; if (queue[0].query) { const flattened = this.flattenMongoQuery({ query: queue[0].query }); @@ -2633,8 +2626,6 @@ export class Builder { let url; if (isApiCallForCodegen) { url = `${host}/api/v1/codegen/${this.apiKey}/${keyNames}`; - } else if (apiEndpoint === 'query') { - url = `${host}/api/v3/query/${this.apiKey}/${keyNames}`; } else { url = `${host}/api/v3/content/${queue[0].model}`; } @@ -2660,7 +2651,7 @@ export class Builder { if (!observer) { return; } - const data = isApiCallForCodegenOrQuery ? result[keyName] : result.results; + const data = isApiCallForCodegen ? result[keyName] : result.results; const sorted = data; // sortBy(data, item => item.priority); if (data) { const testModifiedResults = Builder.isServer diff --git a/packages/sdks-tests/src/e2e-tests/hit-content-api.spec.ts b/packages/sdks-tests/src/e2e-tests/hit-content-api.spec.ts index 27b07dc6473..e1a84e0dd3a 100644 --- a/packages/sdks-tests/src/e2e-tests/hit-content-api.spec.ts +++ b/packages/sdks-tests/src/e2e-tests/hit-content-api.spec.ts @@ -20,7 +20,7 @@ test.describe('Get Content', () => { }); await page.goto('/get-content', { waitUntil: 'networkidle' }); - expect(contentApiInvocations).toBe(1); + expect(contentApiInvocations).toBeGreaterThan(0); // Check for new SDK headers expect(headers?.['x-builder-sdk']).toBe(mapSdkName(sdk)); @@ -30,7 +30,7 @@ test.describe('Get Content', () => { test('passes fetch options', async ({ page, packageName }) => { test.skip(packageName !== 'gen1-next'); - const urlMatch = /https:\/\/cdn\.builder\.io\/api\/v3\/query/; + const urlMatch = /https:\/\/cdn\.builder\.io\/api\/v3\/content/; const responsePromise = page.waitForResponse(urlMatch); await page.goto('/with-fetch-options', { waitUntil: 'networkidle' }); diff --git a/packages/sdks-tests/src/e2e-tests/hit-query-api.spec.ts b/packages/sdks-tests/src/e2e-tests/hit-query-api.spec.ts deleted file mode 100644 index c1196e3295a..00000000000 --- a/packages/sdks-tests/src/e2e-tests/hit-query-api.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { expect } from '@playwright/test'; -import { excludeGen1, test } from '../helpers/index.js'; - -test.describe('Get Query', () => { - test('call query API only once - in page', async ({ page, sdk }) => { - test.skip(!excludeGen1(sdk)); - - const urlMatch = /https:\/\/cdn\.builder\.io\/api\/v3\/query/; - - let queryApiInvocations = 0; - - await page.route(urlMatch, route => { - queryApiInvocations++; - return route.fulfill({ - status: 200, - json: {}, - }); - }); - - await page.goto('/get-query', { waitUntil: 'networkidle' }); - expect(queryApiInvocations).toBe(1); - }); -}); diff --git a/packages/sdks-tests/src/e2e-tests/slot.spec.ts b/packages/sdks-tests/src/e2e-tests/slot.spec.ts index 4bfe486e842..6867369fcd6 100644 --- a/packages/sdks-tests/src/e2e-tests/slot.spec.ts +++ b/packages/sdks-tests/src/e2e-tests/slot.spec.ts @@ -31,7 +31,7 @@ test.describe('Slot', () => { await expect(page.locator('text=This is called recursion!')).toBeVisible(); }); - test('slot should render with symbol (without content)', async ({ page, packageName, sdk }) => { + test('slot should render with symbol (without content)', async ({ page, packageName }) => { // gen1-remix and gen1-next skipped because React.useContext is not recognized // ssr packages skipped because it fetches the slot content from the server test.fail( @@ -40,23 +40,15 @@ test.describe('Slot', () => { let x = 0; - const urlMatch = - sdk === 'oldReact' - ? 'https://cdn.builder.io/api/v3/query/abcd/symbol*' - : /https:\/\/cdn\.builder\.io\/api\/v3\/content\/symbol\.*/; + const urlMatch = /https:\/\/cdn\.builder\.io\/api\/v3\/content\/symbol\.*/; await page.route(urlMatch, route => { x++; - const url = new URL(route.request().url()); - - const keyName = - sdk === 'oldReact' ? decodeURIComponent(url.pathname).split('/').reverse()[0] : 'results'; - return route.fulfill({ status: 200, json: { - [keyName]: [x === 0 ? FIRST_SYMBOL_CONTENT : SECOND_SYMBOL_CONTENT], + results: [x === 0 ? FIRST_SYMBOL_CONTENT : SECOND_SYMBOL_CONTENT], }, }); }); diff --git a/packages/sdks-tests/src/e2e-tests/symbols.spec.ts b/packages/sdks-tests/src/e2e-tests/symbols.spec.ts index 142890a2ac7..fafbe89967a 100644 --- a/packages/sdks-tests/src/e2e-tests/symbols.spec.ts +++ b/packages/sdks-tests/src/e2e-tests/symbols.spec.ts @@ -2,14 +2,7 @@ import type { Page } from '@playwright/test'; import { expect } from '@playwright/test'; import { DEFAULT_TEXT_SYMBOL, FRENCH_TEXT_SYMBOL } from '../specs/symbol-with-locale.js'; import { FIRST_SYMBOL_CONTENT, SECOND_SYMBOL_CONTENT } from '../specs/symbols.js'; -import { - excludeGen2, - checkIsGen1React, - checkIsRN, - test, - mapSdkName, - getSdkGeneration, -} from '../helpers/index.js'; +import { excludeGen2, checkIsRN, test, mapSdkName, getSdkGeneration } from '../helpers/index.js'; import type { ServerName } from '../helpers/sdk.js'; /** @@ -72,24 +65,16 @@ test.describe('Symbols', () => { let x = 0; let headers; - const urlMatch = - sdk === 'oldReact' - ? 'https://cdn.builder.io/api/v3/query/abcd/symbol*' - : /https:\/\/cdn\.builder\.io\/api\/v3\/content\/symbol\.*/; + const urlMatch = /https:\/\/cdn\.builder\.io\/api\/v3\/content\/symbol\.*/; await page.route(urlMatch, route => { x++; headers = route.request().headers(); - const url = new URL(route.request().url()); - - const keyName = - sdk === 'oldReact' ? decodeURIComponent(url.pathname).split('/').reverse()[0] : 'results'; - return route.fulfill({ status: 200, json: { - [keyName]: [x === 0 ? FIRST_SYMBOL_CONTENT : SECOND_SYMBOL_CONTENT], + results: [x === 0 ? FIRST_SYMBOL_CONTENT : SECOND_SYMBOL_CONTENT], }, }); }); @@ -113,24 +98,16 @@ test.describe('Symbols', () => { let x = 0; let headers; - const urlMatch = - sdk === 'oldReact' - ? 'https://cdn.builder.io/api/v3/query/abcd/symbol*' - : /https:\/\/cdn\.builder\.io\/api\/v3\/content\/symbol\.*/; + const urlMatch = /https:\/\/cdn\.builder\.io\/api\/v3\/content\/symbol\.*/; await page.route(urlMatch, route => { x++; headers = route.request().headers(); - const url = new URL(route.request().url()); - - const keyName = - sdk === 'oldReact' ? decodeURIComponent(url.pathname).split('/').reverse()[0] : 'results'; - return route.fulfill({ status: 200, json: { - [keyName]: [x === 1 ? DEFAULT_TEXT_SYMBOL : FRENCH_TEXT_SYMBOL], + results: [x === 1 ? DEFAULT_TEXT_SYMBOL : FRENCH_TEXT_SYMBOL], }, }); }); @@ -158,24 +135,16 @@ test.describe('Symbols', () => { let x = 0; let headers; - const urlMatch = checkIsGen1React(sdk) - ? 'https://cdn.builder.io/api/v3/query/abcd/symbol*' - : /.*cdn\.builder\.io\/api\/v3\/content\/symbol.*/; + const urlMatch = /.*cdn\.builder\.io\/api\/v3\/content\/symbol.*/; await page.route(urlMatch, route => { x++; headers = route.request().headers(); - const url = new URL(route.request().url()); - - const keyName = checkIsGen1React(sdk) - ? decodeURIComponent(url.pathname).split('/').reverse()[0] - : 'results'; - return route.fulfill({ status: 200, json: { - [keyName]: [x === 0 ? FIRST_SYMBOL_CONTENT : SECOND_SYMBOL_CONTENT], + results: [x === 0 ? FIRST_SYMBOL_CONTENT : SECOND_SYMBOL_CONTENT], }, }); }); @@ -197,24 +166,16 @@ test.describe('Symbols', () => { let x = 0; let headers; - const urlMatch = checkIsGen1React(sdk) - ? 'https://cdn.builder.io/api/v3/query/abcd/symbol*' - : /.*cdn\.builder\.io\/api\/v3\/content\/symbol.*/; + const urlMatch = /.*cdn\.builder\.io\/api\/v3\/content\/symbol.*/; await page.route(urlMatch, route => { x++; headers = route.request().headers(); - const url = new URL(route.request().url()); - - const keyName = checkIsGen1React(sdk) - ? decodeURIComponent(url.pathname).split('/').reverse()[0] - : 'results'; - return route.fulfill({ status: 200, json: { - [keyName]: [x === 0 ? FIRST_SYMBOL_CONTENT : SECOND_SYMBOL_CONTENT], + results: [x === 0 ? FIRST_SYMBOL_CONTENT : SECOND_SYMBOL_CONTENT], }, }); }); diff --git a/packages/sdks-tests/src/helpers/visual-editor.ts b/packages/sdks-tests/src/helpers/visual-editor.ts index 7dd1751f8d8..2816c3eccd3 100644 --- a/packages/sdks-tests/src/helpers/visual-editor.ts +++ b/packages/sdks-tests/src/helpers/visual-editor.ts @@ -22,13 +22,13 @@ export const launchEmbedderAndWaitForSdk = async ({ sdk?: Sdk; }) => { if (sdk === 'oldReact') { - await page.route('https://cdn.builder.io/api/v3/query/**', async route => { + await page.route('https://cdn.builder.io/api/v3/content/**', async route => { const newLocal = PAGES[path as keyof typeof PAGES]; await route.fulfill({ status: 200, contentType: 'application/json', - body: JSON.stringify({ page: [newLocal] }), + body: JSON.stringify({ results: [newLocal] }), }); }); } diff --git a/packages/sdks-tests/src/specs/index.ts b/packages/sdks-tests/src/specs/index.ts index 8b0a5da8a63..492195e69df 100644 --- a/packages/sdks-tests/src/specs/index.ts +++ b/packages/sdks-tests/src/specs/index.ts @@ -142,8 +142,6 @@ export const PAGES = { '/custom-components-models-show': CUSTOM_COMPONENTS_MODELS_RESTRICTION, '/custom-components-models-not-show': CUSTOM_COMPONENTS_MODELS_RESTRICTION, '/editing-box-columns-inner-layout': EDITING_BOX_TO_COLUMN_INNER_LAYOUT, - '/get-content': HTTP_REQUESTS, - '/get-query': HTTP_REQUESTS, '/with-fetch-options': homepage, '/symbol-with-repeat-input-binding': SYMBOL_WITH_REPEAT_INPUT_BINDING, '/children-slot-placement': CUSTOM_COMPONENT_CHILDREN_SLOT_PLACEMENT, @@ -161,12 +159,7 @@ const apiVersionPathToProp = { export type Path = keyof typeof PAGES; -const GEN1_ONLY_PATHNAMES: Path[] = [ - '/api-version-v1', - '/personalization-container', - '/get-query', - '/get-content', -]; +const GEN1_ONLY_PATHNAMES: Path[] = ['/api-version-v1', '/personalization-container']; const GEN2_ONLY_PATHNAMES: Path[] = []; export const getAllPathnames = (target: 'gen1' | 'gen2'): string[] => { @@ -260,16 +253,6 @@ export const getProps = async (args: { strictStyleMode: true, }; break; - case '/get-content': - extraProps = { - options: { apiEndpoint: 'content' }, - }; - break; - case '/get-query': - extraProps = { - options: { apiEndpoint: 'query', format: 'html', model: 'abcd', key: 'abcd' }, - }; - break; case '/symbol-with-repeat-input-binding': extraProps = { data: { products: [{ header: 'title1' }, { header: 'title2' }, { header: 'title3' }] },