From 67bef8bb4ba0451ef01c60b90e60ea0bb2ab0197 Mon Sep 17 00:00:00 2001 From: Davi Medeiros Date: Mon, 24 Nov 2025 09:59:27 +0100 Subject: [PATCH 1/6] test: add failing tests for missing support to enrichOptions --- packages/core/src/builder.class.test.ts | 66 ++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/packages/core/src/builder.class.test.ts b/packages/core/src/builder.class.test.ts index 6c65b362fb1..e12116a9846 100644 --- a/packages/core/src/builder.class.test.ts +++ b/packages/core/src/builder.class.test.ts @@ -1,4 +1,4 @@ -import { Builder, GetContentOptions } from './builder.class'; +import { Builder } from './builder.class'; import { BehaviorSubject } from './classes/observable.class'; import { BuilderContent } from './types/content'; @@ -1292,4 +1292,68 @@ describe('getAll', () => { { headers: { Authorization: `Bearer ${AUTH_TOKEN}` } } ); }); + + test('hits query url with enrich=true when passed in options', async () => { + const expectedModel = 'page'; + + await builder.getAll(expectedModel, { enrich: true }); + + expect(builder['makeFetchApiCall']).toBeCalledTimes(1); + expect(builder['makeFetchApiCall']).toBeCalledWith( + expect.stringContaining('enrich=true'), + expect.anything() + ); + }); + + test('hits query url with enrichOptions.enrichLevel when passed in options', async () => { + const expectedModel = 'page'; + + await builder.getAll(expectedModel, { + enrich: true, + enrichOptions: { enrichLevel: 2 }, + }); + + expect(builder['makeFetchApiCall']).toBeCalledTimes(1); + expect(builder['makeFetchApiCall']).toBeCalledWith( + expect.stringContaining('enrich=true'), + expect.anything() + ); + expect(builder['makeFetchApiCall']).toBeCalledWith( + expect.stringContaining('enrichOptions.enrichLevel=2'), + expect.anything() + ); + }); + + test('hits content url with enrich=true when apiEndpoint is content', async () => { + const expectedModel = 'page'; + + builder.apiEndpoint = 'content'; + await builder.getAll(expectedModel, { enrich: true }); + + expect(builder['makeFetchApiCall']).toBeCalledTimes(1); + expect(builder['makeFetchApiCall']).toBeCalledWith( + expect.stringContaining('enrich=true'), + expect.anything() + ); + }); + + test('hits content url with enrichOptions.enrichLevel when apiEndpoint is content', async () => { + const expectedModel = 'page'; + + builder.apiEndpoint = 'content'; + await builder.getAll(expectedModel, { + enrich: true, + enrichOptions: { enrichLevel: 3 }, + }); + + expect(builder['makeFetchApiCall']).toBeCalledTimes(1); + expect(builder['makeFetchApiCall']).toBeCalledWith( + expect.stringContaining('enrich=true'), + expect.anything() + ); + expect(builder['makeFetchApiCall']).toBeCalledWith( + expect.stringContaining('enrichOptions.enrichLevel=3'), + expect.anything() + ); + }); }); From a2fdb061da27a6e6bee6d0f5b293e8f04b01e834 Mon Sep 17 00:00:00 2001 From: Davi Medeiros Date: Mon, 24 Nov 2025 09:59:39 +0100 Subject: [PATCH 2/6] fix: missing support for enrichOptions --- packages/core/src/builder.class.ts | 41 +++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/core/src/builder.class.ts b/packages/core/src/builder.class.ts index b8641a20d2d..e60471e77bf 100644 --- a/packages/core/src/builder.class.ts +++ b/packages/core/src/builder.class.ts @@ -3,7 +3,7 @@ import { IncomingMessage, ServerResponse } from 'http'; import { nextTick } from './functions/next-tick.function'; import { QueryString } from './classes/query-string.class'; import { BehaviorSubject } from './classes/observable.class'; -import { getFetch, SimplifiedFetchOptions } from './functions/fetch.function'; +import { getFetch } from './functions/fetch.function'; import { assign } from './functions/assign.function'; import { throttle } from './functions/throttle.function'; import { Animator } from './classes/animator.class'; @@ -495,6 +495,20 @@ export type GetContentOptions = AllowEnrich & { * draft mode and un-archived. Default is false. */ includeUnpublished?: boolean; + + /** + * Options to configure how enrichment works. + * @see {@link https://www.builder.io/c/docs/content-api#code-enrich-options-code} + */ + enrichOptions?: { + /** + * The depth level for enriching references. For example, an enrichLevel of 1 + * would return one additional nested model within the original response. + * The maximum level is 4. + */ + enrichLevel?: number; + [key: string]: any; + }; }; export type Class = { @@ -2698,6 +2712,19 @@ export class Builder { queryParams.includeRefs = true; } + if (this.apiEndpoint === 'query') { + for (const options of queue) { + if ('enrich' in options && options.enrich !== undefined) { + queryParams.enrich = options.enrich; + } + if (options.enrichOptions) { + for (const [subKey, subValue] of Object.entries(options.enrichOptions)) { + queryParams[`enrichOptions.${subKey}`] = subValue; + } + } + } + } + const properties: (keyof GetContentOptions)[] = [ 'prerender', 'extractCss', @@ -2724,6 +2751,18 @@ export class Builder { } } } + + // Handle enrich and enrichOptions for content endpoint + if (this.apiEndpoint === 'content') { + if ('enrich' in options && options.enrich !== undefined) { + queryParams.enrich = options.enrich; + } + if (options.enrichOptions) { + for (const [subKey, subValue] of Object.entries(options.enrichOptions)) { + queryParams[`enrichOptions.${subKey}`] = subValue; + } + } + } } if (this.preview && this.previewingModel === queue?.[0]?.model) { queryParams.preview = 'true'; From 91c067211620c1ebecd6cc1ac1675b7550837155 Mon Sep 17 00:00:00 2001 From: Davi Medeiros Date: Mon, 24 Nov 2025 09:59:39 +0100 Subject: [PATCH 3/6] fix: remove unneeded loop through queue --- packages/core/src/builder.class.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/core/src/builder.class.ts b/packages/core/src/builder.class.ts index e60471e77bf..808d15b718f 100644 --- a/packages/core/src/builder.class.ts +++ b/packages/core/src/builder.class.ts @@ -2713,14 +2713,12 @@ export class Builder { } if (this.apiEndpoint === 'query') { - for (const options of queue) { - if ('enrich' in options && options.enrich !== undefined) { - queryParams.enrich = options.enrich; - } - if (options.enrichOptions) { - for (const [subKey, subValue] of Object.entries(options.enrichOptions)) { - queryParams[`enrichOptions.${subKey}`] = subValue; - } + if ('enrich' in options && options.enrich !== undefined) { + queryParams.enrich = options.enrich; + } + if (options.enrichOptions) { + for (const [subKey, subValue] of Object.entries(options.enrichOptions)) { + queryParams[`enrichOptions.${subKey}`] = subValue; } } } From 49d22dc44b2b5e1d1844a92e79b2a89c27f0807d Mon Sep 17 00:00:00 2001 From: Davi Medeiros Date: Tue, 25 Nov 2025 10:39:37 +0100 Subject: [PATCH 4/6] test: add failing test for support to enrichModels.model --- packages/core/src/builder.class.test.ts | 38 +++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/packages/core/src/builder.class.test.ts b/packages/core/src/builder.class.test.ts index e12116a9846..664e497b940 100644 --- a/packages/core/src/builder.class.test.ts +++ b/packages/core/src/builder.class.test.ts @@ -1356,4 +1356,42 @@ describe('getAll', () => { expect.anything() ); }); + + test('hits query url with enrichOptions.model when passed complex model options', async () => { + const expectedModel = 'page'; + + await builder.getAll(expectedModel, { + enrich: true, + enrichOptions: { + enrichLevel: 2, + model: { + product: { + fields: 'id,name,price', + omit: 'data.internalNotes', + }, + category: { + fields: 'id,name', + }, + }, + }, + }); + + expect(builder['makeFetchApiCall']).toBeCalledTimes(1); + expect(builder['makeFetchApiCall']).toBeCalledWith( + expect.stringContaining('enrichOptions.enrichLevel=2'), + expect.anything() + ); + expect(builder['makeFetchApiCall']).toBeCalledWith( + expect.stringContaining('enrichOptions.model.product.fields=id%2Cname%2Cprice'), + expect.anything() + ); + expect(builder['makeFetchApiCall']).toBeCalledWith( + expect.stringContaining('enrichOptions.model.product.omit=data.internalNotes'), + expect.anything() + ); + expect(builder['makeFetchApiCall']).toBeCalledWith( + expect.stringContaining('enrichOptions.model.category.fields=id%2Cname'), + expect.anything() + ); + }); }); From 2142b78c13c9357995dbe61352ccd9e18bdb9ca1 Mon Sep 17 00:00:00 2001 From: Davi Medeiros Date: Tue, 25 Nov 2025 10:39:51 +0100 Subject: [PATCH 5/6] fix: support for enrichOptions.model previous iteration was setting enrichOptions.model = "[object Object]" --- packages/core/src/builder.class.ts | 54 ++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/packages/core/src/builder.class.ts b/packages/core/src/builder.class.ts index 808d15b718f..8e573f0aa1d 100644 --- a/packages/core/src/builder.class.ts +++ b/packages/core/src/builder.class.ts @@ -507,6 +507,36 @@ export type GetContentOptions = AllowEnrich & { * The maximum level is 4. */ enrichLevel?: number; + + /** + * Model-specific enrichment options. Allows selective field inclusion/exclusion + * for each referenced model type. + * + * @example + * ```typescript + * enrichOptions: { + * model: { + * 'product': { + * fields: 'id,name,price', + * omit: 'data.internalNotes' + * }, + * 'category': { + * fields: 'id,name' + * } + * } + * } + * ``` + */ + model?: { + [modelName: string]: { + /** Comma-separated list of fields to include */ + fields?: string; + /** Comma-separated list of fields to omit */ + omit?: string; + [key: string]: any; + }; + }; + [key: string]: any; }; }; @@ -2717,9 +2747,7 @@ export class Builder { queryParams.enrich = options.enrich; } if (options.enrichOptions) { - for (const [subKey, subValue] of Object.entries(options.enrichOptions)) { - queryParams[`enrichOptions.${subKey}`] = subValue; - } + this.flattenEnrichOptions(options.enrichOptions, 'enrichOptions', queryParams); } } @@ -2756,9 +2784,7 @@ export class Builder { queryParams.enrich = options.enrich; } if (options.enrichOptions) { - for (const [subKey, subValue] of Object.entries(options.enrichOptions)) { - queryParams[`enrichOptions.${subKey}`] = subValue; - } + this.flattenEnrichOptions(options.enrichOptions, 'enrichOptions', queryParams); } } } @@ -2965,6 +2991,22 @@ export class Builder { return Builder.isBrowser && setCookie(name, value, expires); } + /** + * Recursively flattens enrichOptions object into dot-notation query parameters + * @private + */ + private flattenEnrichOptions(obj: any, prefix: string, result: Record): void { + for (const [key, value] of Object.entries(obj)) { + const newKey = `${prefix}.${key}`; + + if (value && typeof value === 'object' && !Array.isArray(value)) { + this.flattenEnrichOptions(value, newKey, result); + } else { + result[newKey] = value; + } + } + } + getContent(modelName: string, options: GetContentOptions = {}) { if (!this.apiKey) { throw new Error( From 337dc8728ecf7b6de889195f05c11cd7297a0957 Mon Sep 17 00:00:00 2001 From: Davi Medeiros Date: Mon, 1 Dec 2025 07:17:04 +0100 Subject: [PATCH 6/6] chore: narrow assertions --- packages/core/src/builder.class.test.ts | 33 +++++++++++-------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/core/src/builder.class.test.ts b/packages/core/src/builder.class.test.ts index 664e497b940..e750c7fa1b5 100644 --- a/packages/core/src/builder.class.test.ts +++ b/packages/core/src/builder.class.test.ts @@ -1314,13 +1314,12 @@ describe('getAll', () => { }); expect(builder['makeFetchApiCall']).toBeCalledTimes(1); - expect(builder['makeFetchApiCall']).toBeCalledWith( - expect.stringContaining('enrich=true'), - expect.anything() - ); + expect(builder['makeFetchApiCall']).toBeCalledWith(expect.stringContaining('enrich=true'), { + headers: { Authorization: `Bearer ${AUTH_TOKEN}` }, + }); expect(builder['makeFetchApiCall']).toBeCalledWith( expect.stringContaining('enrichOptions.enrichLevel=2'), - expect.anything() + { headers: { Authorization: `Bearer ${AUTH_TOKEN}` } } ); }); @@ -1331,10 +1330,9 @@ describe('getAll', () => { await builder.getAll(expectedModel, { enrich: true }); expect(builder['makeFetchApiCall']).toBeCalledTimes(1); - expect(builder['makeFetchApiCall']).toBeCalledWith( - expect.stringContaining('enrich=true'), - expect.anything() - ); + expect(builder['makeFetchApiCall']).toBeCalledWith(expect.stringContaining('enrich=true'), { + headers: { Authorization: `Bearer ${AUTH_TOKEN}` }, + }); }); test('hits content url with enrichOptions.enrichLevel when apiEndpoint is content', async () => { @@ -1347,13 +1345,12 @@ describe('getAll', () => { }); expect(builder['makeFetchApiCall']).toBeCalledTimes(1); - expect(builder['makeFetchApiCall']).toBeCalledWith( - expect.stringContaining('enrich=true'), - expect.anything() - ); + expect(builder['makeFetchApiCall']).toBeCalledWith(expect.stringContaining('enrich=true'), { + headers: { Authorization: `Bearer ${AUTH_TOKEN}` }, + }); expect(builder['makeFetchApiCall']).toBeCalledWith( expect.stringContaining('enrichOptions.enrichLevel=3'), - expect.anything() + { headers: { Authorization: `Bearer ${AUTH_TOKEN}` } } ); }); @@ -1379,19 +1376,19 @@ describe('getAll', () => { expect(builder['makeFetchApiCall']).toBeCalledTimes(1); expect(builder['makeFetchApiCall']).toBeCalledWith( expect.stringContaining('enrichOptions.enrichLevel=2'), - expect.anything() + { headers: { Authorization: `Bearer ${AUTH_TOKEN}` } } ); expect(builder['makeFetchApiCall']).toBeCalledWith( expect.stringContaining('enrichOptions.model.product.fields=id%2Cname%2Cprice'), - expect.anything() + { headers: { Authorization: `Bearer ${AUTH_TOKEN}` } } ); expect(builder['makeFetchApiCall']).toBeCalledWith( expect.stringContaining('enrichOptions.model.product.omit=data.internalNotes'), - expect.anything() + { headers: { Authorization: `Bearer ${AUTH_TOKEN}` } } ); expect(builder['makeFetchApiCall']).toBeCalledWith( expect.stringContaining('enrichOptions.model.category.fields=id%2Cname'), - expect.anything() + { headers: { Authorization: `Bearer ${AUTH_TOKEN}` } } ); }); });