Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 103 additions & 1 deletion packages/core/src/builder.class.test.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -1292,4 +1292,106 @@ 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()
);
});

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()
);
});
});
81 changes: 80 additions & 1 deletion packages/core/src/builder.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -495,6 +495,50 @@ 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;

/**
* 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;
};
};

export type Class = {
Expand Down Expand Up @@ -2698,6 +2742,15 @@ export class Builder {
queryParams.includeRefs = true;
}

if (this.apiEndpoint === 'query') {
if ('enrich' in options && options.enrich !== undefined) {
queryParams.enrich = options.enrich;
}
if (options.enrichOptions) {
this.flattenEnrichOptions(options.enrichOptions, 'enrichOptions', queryParams);
}
}

const properties: (keyof GetContentOptions)[] = [
'prerender',
'extractCss',
Expand All @@ -2724,6 +2777,16 @@ 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) {
this.flattenEnrichOptions(options.enrichOptions, 'enrichOptions', queryParams);
}
}
}
if (this.preview && this.previewingModel === queue?.[0]?.model) {
queryParams.preview = 'true';
Expand Down Expand Up @@ -2928,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<string, any>): 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(
Expand Down