From bc525aed3976bf9c902e630ab84781dcb0d3de59 Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 13:17:59 +0200 Subject: [PATCH 01/14] initial commit --- packages/sitecore-jss/src/constants.ts | 3 + packages/sitecore-jss/src/debug.ts | 1 + .../src/i18n/content-token-service.ts | 72 +++++++ .../src/i18n/graphql-content-token-service.ts | 198 ++++++++++++++++++ 4 files changed, 274 insertions(+) create mode 100644 packages/sitecore-jss/src/i18n/content-token-service.ts create mode 100644 packages/sitecore-jss/src/i18n/graphql-content-token-service.ts diff --git a/packages/sitecore-jss/src/constants.ts b/packages/sitecore-jss/src/constants.ts index 295c116dcf..866efc73d5 100644 --- a/packages/sitecore-jss/src/constants.ts +++ b/packages/sitecore-jss/src/constants.ts @@ -4,6 +4,9 @@ export enum SitecoreTemplateId { // /sitecore/templates/System/Dictionary/Dictionary entry DictionaryEntry = '6d1cd89719364a3aa511289a94c2a7b1', + + // /sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token + ContentTokenEntry = '7d659ee9d4874d408a9210c6d68844c8' } export const FETCH_WITH = { diff --git a/packages/sitecore-jss/src/debug.ts b/packages/sitecore-jss/src/debug.ts index 692c342be8..da87446909 100644 --- a/packages/sitecore-jss/src/debug.ts +++ b/packages/sitecore-jss/src/debug.ts @@ -40,4 +40,5 @@ export default { personalize: debug(`${rootNamespace}:personalize`), errorpages: debug(`${rootNamespace}:errorpages`), proxy: debug(`${rootNamespace}:proxy`), + contenttokens: debug(`${rootNamespace}:contenttokens`), }; diff --git a/packages/sitecore-jss/src/i18n/content-token-service.ts b/packages/sitecore-jss/src/i18n/content-token-service.ts new file mode 100644 index 0000000000..458091d37d --- /dev/null +++ b/packages/sitecore-jss/src/i18n/content-token-service.ts @@ -0,0 +1,72 @@ +import { CacheClient, CacheOptions, MemoryCacheClient } from '../cache-client'; + +/** + * Object model for Sitecore dictionary phrases + */ +export interface ContentTokenPhrases { + [k: string]: string; +} + +/** + * Service that fetches dictionary data using Sitecore's GraphQL API. + */ +export interface ContentTokenService { + /** + * Fetch dictionary data for a language. + * @param {string} language the language to be used to fetch the dictionary + */ + fetchContentTokens(language: string): Promise; +} + +/** + * Base implementation of @see ContentTokenService that handles caching dictionary values + */ +export abstract class ContentTokenServiceBase + implements ContentTokenService, CacheClient { + private cache: CacheClient; + + /** + * Initializes a new instance of @see ContentTokenService using the provided @see CacheOptions + * @param {CacheOptions} options Configuration options + */ + constructor(public options: CacheOptions) { + this.cache = this.getCacheClient(); + } + + /** + * Caches a @see ContentTokenPhrases value for the specified cache key. + * @param {string} key The cache key. + * @param {ContentTokenPhrases} value The value to cache. + * @returns The value added to the cache. + * @mixes CacheClient + */ + setCacheValue(key: string, value: ContentTokenPhrases): ContentTokenPhrases { + return this.cache.setCacheValue(key, value); + } + + /** + * Retrieves a @see ContentTokenPhrases value from the cache. + * @param {string} key The cache key. + * @returns The @see ContentTokenPhrases value, or null if the specified key is not found in the cache. + */ + getCacheValue(key: string): ContentTokenPhrases | null { + return this.cache.getCacheValue(key); + } + + /** + * Gets a cache client that can cache data. Uses memory-cache as the default + * library for caching (@see MemoryCacheClient). Override this method if you + * want to use something else. + * @returns {CacheClient} implementation + */ + protected getCacheClient(): CacheClient { + return new MemoryCacheClient(this.options); + } + + /** + * Fetch dictionary data for a language. + * @param {string} language the language to be used to fetch the dictionary + * @returns {Promise} + */ + abstract fetchContentTokens(language: string): Promise; +} diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts new file mode 100644 index 0000000000..76e21d0970 --- /dev/null +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -0,0 +1,198 @@ +import { + GraphQLClient, + GraphQLRequestClientConfig, + GraphQLRequestClientFactory, +} from '../graphql-request-client'; +import { SitecoreTemplateId } from '../constants'; +import { ContentTokenPhrases, ContentTokenServiceBase } from './content-token-service'; +import { CacheOptions } from '../cache-client'; +import { getAppRootId, SearchQueryService, SearchQueryVariables } from '../graphql'; +import debug from '../debug'; + +/** @private */ +export const queryError = + 'Valid value for rootItemId not provided and failed to auto-resolve app root item.'; + +const query = /* GraphQL */ ` + query DictionarySearch( + $rootItemId: String! + $language: String! + $templates: String! + $pageSize: Int = 10 + $after: String + ) { + search( + where: { + AND: [ + { name: "_path", value: $rootItemId, operator: CONTAINS } + { name: "_language", value: $language } + { name: "_templates", value: $templates, operator: CONTAINS } + ] + } + first: $pageSize + after: $after + ) { + total + pageInfo { + endCursor + hasNext + } + results { + key: field(name: "Key") { + value + } + value: field(name: "Value") { + value + } + } + } + } +`; + +/** + * Configuration options for @see GraphQLContentTokenService instances + */ +export interface GraphQLContentTokenServiceConfig + extends Omit, + CacheOptions, + Pick { + /** + * The name of the current Sitecore site. This is used to to determine the search query root + * in cases where one is not specified by the caller. + */ + siteName: string; + + /** + * A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. + * This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + */ + clientFactory: GraphQLRequestClientFactory; + + /** + * Optional. The template ID to use when searching for content token entries. + * @default '7d659ee9d4874d408a9210c6d68844c8' (/sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token) + */ + contentTokenEntryTemplateId?: string; + + /** + * Optional. The template ID of a JSS App to use when searching for the appRootId. + * @default '061cba1554744b918a0617903b102b82' (/sitecore/templates/Foundation/JavaScript Services/App) + */ + jssAppTemplateId?: string; +} + +/** + * The schema of data returned in response to a dictionary query request. + */ +export type ContentTokenQueryResult = { + key: { value: string }; + value: { value: string }; +}; + +/** + * Service that fetch dictionary data using Sitecore's GraphQL API. + * @augments ContentTokenServiceBase + * @mixes SearchQueryService + */ +export class GraphQLContentTokenService extends ContentTokenServiceBase { + private graphQLClient: GraphQLClient; + private searchService: SearchQueryService; + + /** + * Creates an instance of graphQL dictionary service with the provided options + * @param {GraphQLContentTokenService} options instance + */ + constructor(public options: GraphQLContentTokenServiceConfig) { + super(options); + this.graphQLClient = this.getGraphQLClient(); + this.searchService = new SearchQueryService(this.graphQLClient); + } + + /** + * Fetches dictionary data for internalization. Uses search query by default + * @param {string} language the language to fetch + * @returns {Promise} dictionary phrases + * @throws {Error} if the app root was not found for the specified site and language. + */ + async fetchContentTokens(language: string): Promise { + const cacheKey = this.options.siteName + language; + const cachedValue = this.getCacheValue(cacheKey); + if (cachedValue) { + debug.contenttokens('using cached dictionary data for %s %s', language, this.options.siteName); + return cachedValue; + } + + const phrases = await this.fetchWithSearchQuery(language); + + this.setCacheValue(cacheKey, phrases); + return phrases; + } + + /** + * Fetches dictionary data with search query + * This is the default behavior for non-XMCloud deployments + * @param {string} language the language to fetch + * @default query (@see query) + * @returns {Promise} dictionary phrases + * @throws {Error} if the app root was not found for the specified site and language. + */ + async fetchWithSearchQuery(language: string): Promise { + debug.contenttokens('fetching site root for %s %s', language, this.options.siteName); + + // If the caller does not specify a root item ID, then we try to figure it out + const rootItemId = + this.options.rootItemId || + (await getAppRootId( + this.graphQLClient, + this.options.siteName, + language, + this.options.jssAppTemplateId + )); + + if (!rootItemId) { + throw new Error(queryError); + } + + debug.contenttokens('fetching dictionary data for %s %s', language, this.options.siteName); + const phrases: ContentTokenPhrases = {}; + //TODO: remove searchservice and implement gql client instead + // await this.getGraphQLClient.request(query, { + // rootItemId, + // language, + // templates: this.options.contentTokenEntryTemplateId || SitecoreTemplateId.ContentTokenEntry, + // pageSiqe: this.options.pageSize + // }) + // .then((results) => { + // results.forEach((item) => (phrases[item.key.value] = item.value.value)); + // }) + await this.searchService + .fetch(query, { + rootItemId, + language, + templates: this.options.contentTokenEntryTemplateId || SitecoreTemplateId.ContentTokenEntry, + pageSize: this.options.pageSize, + }) + .then((results) => { + results.forEach((item) => (phrases[item.key.value] = item.value.value)); + }); + + return phrases; + } + + /** + * Gets a GraphQL client that can make requests to the API. Uses graphql-request as the default + * library for fetching graphql data (@see GraphQLRequestClient). Override this method if you + * want to use something else. + * @returns {GraphQLClient} implementation + */ + protected getGraphQLClient(): GraphQLClient { + if (!this.options.clientFactory) { + throw new Error('clientFactory needs to be provided when initializing GraphQL client.'); + } + return this.options.clientFactory({ + debugger: debug.contenttokens, + retries: this.options.retries, + retryStrategy: this.options.retryStrategy, + }); + } +} \ No newline at end of file From f42a7e39681a05e77d96b2daadf243b18f0553a8 Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 13:32:08 +0200 Subject: [PATCH 02/14] added export to Nextjs --- packages/sitecore-jss-nextjs/src/index.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/sitecore-jss-nextjs/src/index.ts b/packages/sitecore-jss-nextjs/src/index.ts index bff971f82c..15ca956c01 100644 --- a/packages/sitecore-jss-nextjs/src/index.ts +++ b/packages/sitecore-jss-nextjs/src/index.ts @@ -48,10 +48,16 @@ export { PageViewInstance, } from '@sitecore-jss/sitecore-jss/tracking'; export { + ContentTokenPhrases, + ContentTokenService, DictionaryPhrases, DictionaryService, + GraphQLContentTokenService, + GraphQLContentTokenServiceConfig, GraphQLDictionaryService, GraphQLDictionaryServiceConfig, + //TODO:RestContentTokenService, + //TODO:RestContentTokenServiceConfig, RestDictionaryService, RestDictionaryServiceConfig, } from '@sitecore-jss/sitecore-jss/i18n'; From 06b23cf82a37859cf16eb22004569c4a9271a749 Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 13:46:45 +0200 Subject: [PATCH 03/14] setup for tests --- .../graphql-content-token-service.test.ts | 273 ++++++++++++++++++ .../src/i18n/graphql-content-token-service.ts | 2 +- packages/sitecore-jss/src/i18n/index.ts | 5 + .../mockContentTokenQueryResponse.json | 29 ++ 4 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts create mode 100644 packages/sitecore-jss/src/test-data/mockContentTokenQueryResponse.json diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts new file mode 100644 index 0000000000..bb963a33bc --- /dev/null +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts @@ -0,0 +1,273 @@ +/* eslint-disable no-unused-expressions */ +import { expect } from 'chai'; +import sinon, { SinonSpy } from 'sinon'; +import nock from 'nock'; +import { SitecoreTemplateId } from '../constants'; +import { GraphQLClient, GraphQLRequestClient } from '../graphql-request-client'; +import { queryError, GraphQLContentTokenServiceConfig } from './graphql-content-token-service'; +import { GraphQLContentTokenService } from '.'; +import contentTokenQueryResponse from '../test-data/mockContentTokenQueryResponse.json'; +import appRootQueryResponse from '../test-data/mockAppRootQueryResponse.json'; + +class TestService extends GraphQLContentTokenService { + public client: GraphQLClient; + constructor(options: GraphQLContentTokenServiceConfig) { + super(options); + this.client = this.getGraphQLClient(); + } +} + +describe('GraphQLContentTokenService', () => { + const endpoint = 'http://site'; + const siteName = 'site-name'; + const apiKey = 'api-key'; + const rootItemId = '{GUID}'; + const clientFactory = GraphQLRequestClient.createClientFactory({ + endpoint, + apiKey, + }); + + afterEach(() => { + nock.cleanAll(); + }); + + it('should fetch dictionary phrases using clientFactory', async () => { + nock(endpoint, { reqheaders: { sc_apikey: apiKey } }) + .post('/', /ContentTokenSearch/gi) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + siteName, + rootItemId, + cacheEnabled: false, + clientFactory, + }); + const result = await service.fetchContentTokens('en'); + expect(result.foo).to.equal('foo'); + expect(result.bar).to.equal('bar'); + }); + + it('should attempt to fetch the rootItemId, if rootItemId not provided', async () => { + nock(endpoint) + .post('/', /AppRootQuery/) + .reply(200, appRootQueryResponse); + + nock(endpoint) + .post('/', (body) => body.variables.rootItemId === 'GUIDGUIDGUID') + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + cacheEnabled: false, + }); + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + // eslint-disable-next-line no-unused-expressions + expect(nock.isDone()).to.be.true; + }); + + it('should use a custom rootItemId, if provided', async () => { + const customRootId = 'cats'; + + nock(endpoint) + .post('/', (body) => body.variables.rootItemId === customRootId) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + cacheEnabled: false, + rootItemId: customRootId, + }); + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + }); + + it('should use a jssTemplateId, if provided', async () => { + const jssAppTemplateId = '{71d608ca-ac9c-4f1c-8e0a-85a6946e30f8}'; + const randomId = '{412286b7-6d4f-4deb-80e9-108ee986c6e9}'; + + nock(endpoint) + .post('/', (body) => body.variables.jssAppTemplateId === jssAppTemplateId) + .reply(200, { + data: { + layout: { + homePage: { + rootItem: [ + { + id: randomId, + }, + ], + }, + }, + }, + }); + + nock(endpoint) + .post('/', (body) => body.variables.rootItemId === randomId) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + cacheEnabled: false, + jssAppTemplateId, + }); + + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + }); + + it('should throw error if could not resolve rootItemId', async () => { + nock(endpoint) + .post('/', /AppRootQuery/) + .reply(200, { + data: { + layout: { + homePage: null, + }, + }, + }); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + cacheEnabled: false, + }); + + await service.fetchContentTokens('en').catch((error) => { + expect(error).to.be.instanceOf(Error); + expect(error.message).to.equal(queryError); + }); + }); + + it('should use default pageSize, if pageSize not provided', async () => { + nock(endpoint) + .post( + '/', + (body) => + body.query.indexOf('$pageSize: Int = 10') > 0 && body.variables.pageSize === undefined + ) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + rootItemId, + cacheEnabled: false, + pageSize: undefined, + }); + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + }); + + it('should use a custom pageSize, if provided', async () => { + const customPageSize = 2; + + nock(endpoint) + .post('/', (body) => body.variables.pageSize === customPageSize) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + rootItemId, + cacheEnabled: false, + pageSize: customPageSize, + }); + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + }); + + it('should use custom dictionary entry template ID, if provided', async () => { + const customTemplateId = 'custom-template-id'; + + nock(endpoint) + .post('/', (body) => body.variables.templates === customTemplateId) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + rootItemId, + cacheEnabled: false, + dictionaryEntryTemplateId: customTemplateId, + }); + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + }); + + it('should use default dictionary entry template ID, if template ID not provided', async () => { + nock(endpoint) + .post('/', (body) => body.variables.templates === SitecoreTemplateId.DictionaryEntry) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + rootItemId, + cacheEnabled: false, + }); + const result = await service.fetchContentTokens('en'); + expect(result).to.have.all.keys('foo', 'bar'); + }); + + it('should use cache', async () => { + nock(endpoint, { reqheaders: { sc_apikey: apiKey } }) + .post('/', /ContentTokenSearch/gi) + .reply(200, contentTokenQueryResponse); + + const service = new GraphQLContentTokenService({ + clientFactory, + siteName, + rootItemId, + cacheEnabled: true, + cacheTimeout: 2, + }); + + const result1 = await service.fetchContentTokens('en'); + expect(result1).to.have.all.keys('foo', 'bar'); + + const result2 = await service.fetchContentTokens('en'); + expect(result2).to.have.all.keys('foo', 'bar'); + }); + + it('should provide a default GraphQL client', () => { + const service = new TestService({ + clientFactory, + siteName, + rootItemId, + cacheEnabled: false, + }); + + const graphQLClient = service.client as GraphQLClient; + const graphQLRequestClient = service.client as GraphQLRequestClient; + // eslint-disable-next-line no-unused-expressions + expect(graphQLClient).to.exist; + // eslint-disable-next-line no-unused-expressions + expect(graphQLRequestClient).to.exist; + }); + + it('should call clientFactory with the correct arguments', () => { + const clientFactorySpy: SinonSpy = sinon.spy(); + const mockServiceConfig = { + siteName: 'supersite', + clientFactory: clientFactorySpy, + retries: 3, + retryStrategy: { + getDelay: () => 1000, + shouldRetry: () => true, + }, + }; + + new GraphQLContentTokenService(mockServiceConfig); + + expect(clientFactorySpy.calledOnce).to.be.true; + + const calledWithArgs = clientFactorySpy.firstCall.args[0]; + expect(calledWithArgs.debugger).to.exist; + expect(calledWithArgs.retries).to.equal(mockServiceConfig.retries); + expect(calledWithArgs.retryStrategy).to.deep.equal(mockServiceConfig.retryStrategy); + }); +}); diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts index 76e21d0970..a096fb7cef 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -14,7 +14,7 @@ export const queryError = 'Valid value for rootItemId not provided and failed to auto-resolve app root item.'; const query = /* GraphQL */ ` - query DictionarySearch( + query ContentTokenSearch( $rootItemId: String! $language: String! $templates: String! diff --git a/packages/sitecore-jss/src/i18n/index.ts b/packages/sitecore-jss/src/i18n/index.ts index 2d6f9242cf..5cf56f777d 100644 --- a/packages/sitecore-jss/src/i18n/index.ts +++ b/packages/sitecore-jss/src/i18n/index.ts @@ -1,3 +1,8 @@ +export { ContentTokenPhrases, ContentTokenService, ContentTokenServiceBase } from './content-token-service'; +export { + GraphQLContentTokenServiceConfig, + GraphQLContentTokenService, +} from './graphql-content-token-service' export { DictionaryPhrases, DictionaryService, DictionaryServiceBase } from './dictionary-service'; export { GraphQLDictionaryServiceConfig, diff --git a/packages/sitecore-jss/src/test-data/mockContentTokenQueryResponse.json b/packages/sitecore-jss/src/test-data/mockContentTokenQueryResponse.json new file mode 100644 index 0000000000..7c1bbf9fef --- /dev/null +++ b/packages/sitecore-jss/src/test-data/mockContentTokenQueryResponse.json @@ -0,0 +1,29 @@ +{ + "data": { + "search": { + "total": 5, + "pageInfo": { + "endCursor": "NQ==", + "hasNext": false + }, + "results": [ + { + "key": { + "value": "foo" + }, + "value": { + "value": "foo" + } + }, + { + "key": { + "value": "bar" + }, + "value": { + "value": "bar" + } + } + ] + } + } +} From e8102a4c95574c940860c8096de3fcdb6112025a Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 15:48:19 +0200 Subject: [PATCH 04/14] added tests --- .../src/i18n/graphql-content-token-service.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts index bb963a33bc..68d1b18bea 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts @@ -192,7 +192,7 @@ describe('GraphQLContentTokenService', () => { siteName, rootItemId, cacheEnabled: false, - dictionaryEntryTemplateId: customTemplateId, + contentTokenEntryTemplateId: customTemplateId, }); const result = await service.fetchContentTokens('en'); expect(result).to.have.all.keys('foo', 'bar'); @@ -200,7 +200,7 @@ describe('GraphQLContentTokenService', () => { it('should use default dictionary entry template ID, if template ID not provided', async () => { nock(endpoint) - .post('/', (body) => body.variables.templates === SitecoreTemplateId.DictionaryEntry) + .post('/', (body) => body.variables.templates === SitecoreTemplateId.ContentTokenEntry) .reply(200, contentTokenQueryResponse); const service = new GraphQLContentTokenService({ From 7ac432fed79ec1abe004a0379324cbbec2a9402a Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 16:20:24 +0200 Subject: [PATCH 05/14] implemented gql client rather than searchservice --- .../src/i18n/graphql-content-token-service.ts | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts index a096fb7cef..a65f5ef075 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -6,7 +6,7 @@ import { import { SitecoreTemplateId } from '../constants'; import { ContentTokenPhrases, ContentTokenServiceBase } from './content-token-service'; import { CacheOptions } from '../cache-client'; -import { getAppRootId, SearchQueryService, SearchQueryVariables } from '../graphql'; +import { getAppRootId, SearchQueryResult, SearchQueryVariables } from '../graphql'; import debug from '../debug'; /** @private */ @@ -96,7 +96,7 @@ export type ContentTokenQueryResult = { */ export class GraphQLContentTokenService extends ContentTokenServiceBase { private graphQLClient: GraphQLClient; - private searchService: SearchQueryService; + // private searchService: SearchQueryService; /** * Creates an instance of graphQL dictionary service with the provided options @@ -105,7 +105,7 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { constructor(public options: GraphQLContentTokenServiceConfig) { super(options); this.graphQLClient = this.getGraphQLClient(); - this.searchService = new SearchQueryService(this.graphQLClient); + // this.searchService = new SearchQueryService(this.graphQLClient); } /** @@ -155,26 +155,26 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { debug.contenttokens('fetching dictionary data for %s %s', language, this.options.siteName); const phrases: ContentTokenPhrases = {}; - //TODO: remove searchservice and implement gql client instead - // await this.getGraphQLClient.request(query, { - // rootItemId, - // language, - // templates: this.options.contentTokenEntryTemplateId || SitecoreTemplateId.ContentTokenEntry, - // pageSiqe: this.options.pageSize - // }) - // .then((results) => { - // results.forEach((item) => (phrases[item.key.value] = item.value.value)); - // }) - await this.searchService - .fetch(query, { - rootItemId, - language, - templates: this.options.contentTokenEntryTemplateId || SitecoreTemplateId.ContentTokenEntry, - pageSize: this.options.pageSize, - }) - .then((results) => { - results.forEach((item) => (phrases[item.key.value] = item.value.value)); - }); + let results: ContentTokenQueryResult[] = []; + let hasNext = true; + let after = ''; + + while (hasNext) { + const fetchResponse = + await this.graphQLClient.request>(query, { + rootItemId, + language, + templates: this.options.contentTokenEntryTemplateId || SitecoreTemplateId.ContentTokenEntry, + pageSize: this.options.pageSize, + after + }); + + results = results.concat(fetchResponse?.search?.results); + hasNext = fetchResponse.search.pageInfo.hasNext; + after = fetchResponse.search.pageInfo.endCursor; + } + + results.forEach((item) => (phrases[item.key.value] = item.value.value)); return phrases; } From b04f636cefb2fa913e9b875ae4346bcf5c75b4cd Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 17:13:29 +0200 Subject: [PATCH 06/14] documentation + rewrote dictionary to content token on the service --- .../src/i18n/graphql-content-token-service.ts | 18 +- .../classes/i18n.ContentTokenServiceBase.md | 201 ++++++++++++++++++ 2 files changed, 210 insertions(+), 9 deletions(-) create mode 100644 ref-docs/sitecore-jss/classes/i18n.ContentTokenServiceBase.md diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts index a65f5ef075..ad9e44ad44 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -82,7 +82,7 @@ export interface GraphQLContentTokenServiceConfig } /** - * The schema of data returned in response to a dictionary query request. + * The schema of data returned in response to a content token query request. */ export type ContentTokenQueryResult = { key: { value: string }; @@ -90,7 +90,7 @@ export type ContentTokenQueryResult = { }; /** - * Service that fetch dictionary data using Sitecore's GraphQL API. + * Service that fetch content token data using Sitecore's GraphQL API. * @augments ContentTokenServiceBase * @mixes SearchQueryService */ @@ -99,7 +99,7 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { // private searchService: SearchQueryService; /** - * Creates an instance of graphQL dictionary service with the provided options + * Creates an instance of graphQL content token service with the provided options * @param {GraphQLContentTokenService} options instance */ constructor(public options: GraphQLContentTokenServiceConfig) { @@ -109,16 +109,16 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { } /** - * Fetches dictionary data for internalization. Uses search query by default + * Fetches content token data for internalization. Uses search query by default * @param {string} language the language to fetch - * @returns {Promise} dictionary phrases + * @returns {Promise} content token phrases * @throws {Error} if the app root was not found for the specified site and language. */ async fetchContentTokens(language: string): Promise { const cacheKey = this.options.siteName + language; const cachedValue = this.getCacheValue(cacheKey); if (cachedValue) { - debug.contenttokens('using cached dictionary data for %s %s', language, this.options.siteName); + debug.contenttokens('using cached content token data for %s %s', language, this.options.siteName); return cachedValue; } @@ -129,11 +129,11 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { } /** - * Fetches dictionary data with search query + * Fetches content token data with search query * This is the default behavior for non-XMCloud deployments * @param {string} language the language to fetch * @default query (@see query) - * @returns {Promise} dictionary phrases + * @returns {Promise} content token phrases * @throws {Error} if the app root was not found for the specified site and language. */ async fetchWithSearchQuery(language: string): Promise { @@ -153,7 +153,7 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { throw new Error(queryError); } - debug.contenttokens('fetching dictionary data for %s %s', language, this.options.siteName); + debug.contenttokens('fetching content token data for %s %s', language, this.options.siteName); const phrases: ContentTokenPhrases = {}; let results: ContentTokenQueryResult[] = []; let hasNext = true; diff --git a/ref-docs/sitecore-jss/classes/i18n.ContentTokenServiceBase.md b/ref-docs/sitecore-jss/classes/i18n.ContentTokenServiceBase.md new file mode 100644 index 0000000000..9b6d95a9f3 --- /dev/null +++ b/ref-docs/sitecore-jss/classes/i18n.ContentTokenServiceBase.md @@ -0,0 +1,201 @@ +[@sitecore-jss/sitecore-jss](../README.md) / [i18n](../modules/i18n.md) / ContentTokenServiceBase + +# Class: ContentTokenServiceBase + +[i18n](../modules/i18n.md).ContentTokenServiceBase + +Base implementation of + +**`See`** + +ContentTokenService that handles caching content token values + +## Hierarchy + +- **`ContentTokenServiceBase`** + + ↳ [`GraphQLContentTokenService`](i18n.GraphQLContentTokenService.md) + +## Implements + +- [`ContentTokenService`](../interfaces/i18n.ContentTokenService.md) +- `CacheClient`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +## Table of contents + +### Constructors + +- [constructor](i18n.ContentTokenServiceBase.md#constructor) + +### Properties + +- [cache](i18n.ContentTokenServiceBase.md#cache) +- [options](i18n.ContentTokenServiceBase.md#options) + +### Methods + +- [fetchContentTokens](i18n.ContentTokenServiceBase.md#fetchContentTokens) +- [getCacheClient](i18n.ContentTokenServiceBase.md#getcacheclient) +- [getCacheValue](i18n.ContentTokenServiceBase.md#getcachevalue) +- [setCacheValue](i18n.ContentTokenServiceBase.md#setcachevalue) + +## Constructors + +### constructor + +• **new ContentTokenServiceBase**(`options`) + +Initializes a new instance of + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `options` | `CacheOptions` | Configuration options | + +**`See`** + + - ContentTokenService using the provided + - CacheOptions + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:32](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L32) + +## Properties + +### cache + +• `Private` **cache**: `CacheClient`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:26](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L26) + +___ + +### options + +• **options**: `CacheOptions` + +Configuration options + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:32](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L32) + +## Methods + +### fetchContentTokens + +▸ `Abstract` **fetchContentTokens**(`language`): `Promise`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +Fetch content tokens data for a language. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `language` | `string` | the language to be used to fetch the content tokens | + +#### Returns + +`Promise`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +#### Implementation of + +[ContentTokenService](../interfaces/i18n.ContentTokenService.md).[fetchContentTokens](../interfaces/i18n.ContentTokenService.md#fetchContentTokens) + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:71](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L71) + +___ + +### getCacheClient + +▸ `Protected` **getCacheClient**(): `CacheClient`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +Gets a cache client that can cache data. Uses memory-cache as the default +library for caching (@see MemoryCacheClient). Override this method if you +want to use something else. + +#### Returns + +`CacheClient`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +implementation + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:62](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L62) + +___ + +### getCacheValue + +▸ **getCacheValue**(`key`): ``null`` \| [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +Retrieves a + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `key` | `string` | The cache key. | + +#### Returns + +``null`` \| [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +The + +**`See`** + + - ContentTokenPhrases value from the cache. + - ContentTokenPhrases value, or null if the specified key is not found in the cache. + +#### Implementation of + +CacheClient.getCacheValue + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:52](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L52) + +___ + +### setCacheValue + +▸ **setCacheValue**(`key`, `value`): [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +Caches a + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `key` | `string` | The cache key. | +| `value` | [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) | The value to cache. | + +#### Returns + +[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +The value added to the cache. + +**`See`** + +ContentTokenPhrases value for the specified cache key. + +**`Mixes`** + +CacheClient + +#### Implementation of + +CacheClient.setCacheValue + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:43](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L43) From 6dc221b0d02d6c40478482da552d33187d5358aa Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 18:37:44 +0200 Subject: [PATCH 07/14] documentation update for gql content token service --- .../src/i18n/graphql-content-token-service.ts | 3 - .../i18n.GraphQLContentTokenService.md | 262 ++++++++++++++++++ 2 files changed, 262 insertions(+), 3 deletions(-) create mode 100644 ref-docs/sitecore-jss/classes/i18n.GraphQLContentTokenService.md diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts index ad9e44ad44..8e28535fe4 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -92,11 +92,9 @@ export type ContentTokenQueryResult = { /** * Service that fetch content token data using Sitecore's GraphQL API. * @augments ContentTokenServiceBase - * @mixes SearchQueryService */ export class GraphQLContentTokenService extends ContentTokenServiceBase { private graphQLClient: GraphQLClient; - // private searchService: SearchQueryService; /** * Creates an instance of graphQL content token service with the provided options @@ -105,7 +103,6 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { constructor(public options: GraphQLContentTokenServiceConfig) { super(options); this.graphQLClient = this.getGraphQLClient(); - // this.searchService = new SearchQueryService(this.graphQLClient); } /** diff --git a/ref-docs/sitecore-jss/classes/i18n.GraphQLContentTokenService.md b/ref-docs/sitecore-jss/classes/i18n.GraphQLContentTokenService.md new file mode 100644 index 0000000000..f0a1d43e6e --- /dev/null +++ b/ref-docs/sitecore-jss/classes/i18n.GraphQLContentTokenService.md @@ -0,0 +1,262 @@ +[@sitecore-jss/sitecore-jss](../README.md) / [i18n](../modules/i18n.md) / GraphQLContentTokenService + +# Class: GraphQLContentTokenService + +[i18n](../modules/i18n.md).GraphQLContentTokenService + +Service that fetch content token data using Sitecore's GraphQL API. + +## Hierarchy + +- [`ContentTokenServiceBase`](i18n.ContentTokenServiceBase.md) + + ↳ **`GraphQLContentTokenService`** + +## Table of contents + +### Constructors + +- [constructor](i18n.GraphQLContentTokenService.md#constructor) + +### Properties + +- [graphQLClient](i18n.GraphQLContentTokenService.md#graphqlclient) +- [options](i18n.GraphQLContentTokenService.md#options) + +### Methods + +- [fetchContentTokens](i18n.GraphQLContentTokenService.md#fetchContentTokens) +- [fetchWithSearchQuery](i18n.GraphQLContentTokenService.md#fetchwithsearchquery) +- [getCacheClient](i18n.GraphQLContentTokenService.md#getcacheclient) +- [getCacheValue](i18n.GraphQLContentTokenService.md#getcachevalue) +- [getGraphQLClient](i18n.GraphQLContentTokenService.md#getgraphqlclient) +- [setCacheValue](i18n.GraphQLContentTokenService.md#setcachevalue) + +## Constructors + +### constructor + +• **new GraphQLContentTokenService**(`options`) + +Creates an instance of graphQL content token service with the provided options + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `options` | [`GraphQLContentTokenServiceConfig`](../interfaces/i18n.GraphQLContentTokenServiceConfig.md) | instance | + +#### Overrides + +[ContentTokenServiceBase](i18n.ContentTokenServiceBase.md).[constructor](i18n.ContentTokenServiceBase.md#constructor) + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:103](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L103) + +## Properties + +### graphQLClient + +• `Private` **graphQLClient**: [`GraphQLClient`](../interfaces/index.GraphQLClient.md) + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:97](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L97) + +___ + +### options + +• **options**: [`GraphQLContentTokenServiceConfig`](../interfaces/i18n.GraphQLContentTokenServiceConfig.md) + +instance + +#### Inherited from + +[ContentTokenServiceBase](i18n.ContentTokenServiceBase.md).[options](i18n.ContentTokenServiceBase.md#options) + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:103](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L103) + +## Methods + +### fetchContentTokens + +▸ **fetchContentTokens**(`language`): `Promise`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +Fetches content token data for internalization. Uses search query by default + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `language` | `string` | the language to fetch | + +#### Returns + +`Promise`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +content token phrases + +**`Throws`** + +if the app root was not found for the specified site and language. + +#### Overrides + +[ContentTokenServiceBase](i18n.ContentTokenServiceBase.md).[fetchContentTokens](i18n.ContentTokenServiceBase.md#fetchContentTokens) + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:114](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L114) + +___ + +### fetchWithSearchQuery + +▸ **fetchWithSearchQuery**(`language`): `Promise`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +Fetches content token data with search query +This is the default behavior for non-XMCloud deployments + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `language` | `string` | the language to fetch | + +#### Returns + +`Promise`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +content token phrases + +**`Default`** + +```ts +query (@see query) +``` + +**`Throws`** + +if the app root was not found for the specified site and language. + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:136](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L136) + +___ + +### getCacheClient + +▸ `Protected` **getCacheClient**(): `CacheClient`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +Gets a cache client that can cache data. Uses memory-cache as the default +library for caching (@see MemoryCacheClient). Override this method if you +want to use something else. + +#### Returns + +`CacheClient`\<[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md)\> + +implementation + +#### Inherited from + +[ContentTokenServiceBase](i18n.ContentTokenServiceBase.md).[getCacheClient](i18n.ContentTokenServiceBase.md#getcacheclient) + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:62](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L62) + +___ + +### getCacheValue + +▸ **getCacheValue**(`key`): ``null`` \| [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +Retrieves a + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `key` | `string` | The cache key. | + +#### Returns + +``null`` \| [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +The + +**`See`** + + - ContentTokenPhrases value from the cache. + - ContentTokenPhrases value, or null if the specified key is not found in the cache. + +#### Inherited from + +[ContentTokenServiceBase](i18n.ContentTokenServiceBase.md).[getCacheValue](i18n.ContentTokenServiceBase.md#getcachevalue) + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:52](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L52) + +___ + +### getGraphQLClient + +▸ `Protected` **getGraphQLClient**(): [`GraphQLClient`](../interfaces/index.GraphQLClient.md) + +Gets a GraphQL client that can make requests to the API. Uses graphql-request as the default +library for fetching graphql data (@see GraphQLRequestClient). Override this method if you +want to use something else. + +#### Returns + +[`GraphQLClient`](../interfaces/index.GraphQLClient.md) + +implementation + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:185](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L185) + +___ + +### setCacheValue + +▸ **setCacheValue**(`key`, `value`): [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +Caches a + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `key` | `string` | The cache key. | +| `value` | [`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) | The value to cache. | + +#### Returns + +[`ContentTokenPhrases`](../interfaces/i18n.ContentTokenPhrases.md) + +The value added to the cache. + +**`See`** + +ContentTokenPhrases value for the specified cache key. + +**`Mixes`** + +CacheClient + +#### Inherited from + +[ContentTokenServiceBase](i18n.ContentTokenServiceBase.md).[setCacheValue](i18n.ContentTokenServiceBase.md#setcachevalue) + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:43](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L43) From 55d752ad8c796e4de53be000b74b1618dccc6a1c Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 18:42:16 +0200 Subject: [PATCH 08/14] documentation for interfaces --- .../interfaces/i18n.ContentTokenPhrases.md | 11 ++++++ .../interfaces/i18n.ContentTokenService.md | 39 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 ref-docs/sitecore-jss/interfaces/i18n.ContentTokenPhrases.md create mode 100644 ref-docs/sitecore-jss/interfaces/i18n.ContentTokenService.md diff --git a/ref-docs/sitecore-jss/interfaces/i18n.ContentTokenPhrases.md b/ref-docs/sitecore-jss/interfaces/i18n.ContentTokenPhrases.md new file mode 100644 index 0000000000..7f9b87bf8d --- /dev/null +++ b/ref-docs/sitecore-jss/interfaces/i18n.ContentTokenPhrases.md @@ -0,0 +1,11 @@ +[@sitecore-jss/sitecore-jss](../README.md) / [i18n](../modules/i18n.md) / ContentTokenPhrases + +# Interface: ContentTokenPhrases + +[i18n](../modules/i18n.md).ContentTokenPhrases + +Object model for Sitecore content token phrases + +## Indexable + +▪ [k: `string`]: `string` \ No newline at end of file diff --git a/ref-docs/sitecore-jss/interfaces/i18n.ContentTokenService.md b/ref-docs/sitecore-jss/interfaces/i18n.ContentTokenService.md new file mode 100644 index 0000000000..01d24e91f6 --- /dev/null +++ b/ref-docs/sitecore-jss/interfaces/i18n.ContentTokenService.md @@ -0,0 +1,39 @@ +[@sitecore-jss/sitecore-jss](../README.md) / [i18n](../modules/i18n.md) / ContentTokenService + +# Interface: ContentTokenService + +[i18n](../modules/i18n.md).ContentTokenService + +Service that fetches content token data using Sitecore's GraphQL API. + +## Implemented by + +- [`ContentTokenServiceBase`](../classes/i18n.ContentTokenServiceBase.md) + +## Table of contents + +### Methods + +- [fetchContentTokens](i18n.ContentTokenService.md#fetchContentTokens) + +## Methods + +### fetchContentTokens + +▸ **fetchContentTokens**(`language`): `Promise`\<[`ContentTokenPhrases`](i18n.ContentTokenPhrases.md)\> + +Fetch content token data for a language. + +#### Parameters + +| Name | Type | Description | +| :------ | :------ | :------ | +| `language` | `string` | the language to be used to fetch the content token | + +#### Returns + +`Promise`\<[`ContentTokenPhrases`](i18n.ContentTokenPhrases.md)\> + +#### Defined in + +[packages/sitecore-jss/src/i18n/content-token-service.ts:18](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/content-token-service.ts#L18) From 4acdc80eff913442c008cb271b81c0f830c156d9 Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 18:56:54 +0200 Subject: [PATCH 09/14] rename template reference from contentTokenEntryTemplateId to contentTokenTemplateId --- packages/sitecore-jss/src/constants.ts | 2 +- .../sitecore-jss/src/i18n/graphql-content-token-service.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/sitecore-jss/src/constants.ts b/packages/sitecore-jss/src/constants.ts index 866efc73d5..322341ea9a 100644 --- a/packages/sitecore-jss/src/constants.ts +++ b/packages/sitecore-jss/src/constants.ts @@ -6,7 +6,7 @@ export enum SitecoreTemplateId { DictionaryEntry = '6d1cd89719364a3aa511289a94c2a7b1', // /sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token - ContentTokenEntry = '7d659ee9d4874d408a9210c6d68844c8' + ContentToken = '7d659ee9d4874d408a9210c6d68844c8' } export const FETCH_WITH = { diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts index 8e28535fe4..37d68e8bd1 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -72,7 +72,7 @@ export interface GraphQLContentTokenServiceConfig * Optional. The template ID to use when searching for content token entries. * @default '7d659ee9d4874d408a9210c6d68844c8' (/sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token) */ - contentTokenEntryTemplateId?: string; + contentTokenTemplateId?: string; /** * Optional. The template ID of a JSS App to use when searching for the appRootId. @@ -161,7 +161,7 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { await this.graphQLClient.request>(query, { rootItemId, language, - templates: this.options.contentTokenEntryTemplateId || SitecoreTemplateId.ContentTokenEntry, + templates: this.options.contentTokenTemplateId || SitecoreTemplateId.ContentToken, pageSize: this.options.pageSize, after }); From 86d8b7e7a212acdaf4bccdb4f96f9b865e40fddc Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 20:48:16 +0200 Subject: [PATCH 10/14] more documentation --- .../i18n.GraphQLContentTokenServiceConfig.md | 218 ++++++++++++++++++ ref-docs/sitecore-jss/modules/i18n.md | 5 + 2 files changed, 223 insertions(+) create mode 100644 ref-docs/sitecore-jss/interfaces/i18n.GraphQLContentTokenServiceConfig.md diff --git a/ref-docs/sitecore-jss/interfaces/i18n.GraphQLContentTokenServiceConfig.md b/ref-docs/sitecore-jss/interfaces/i18n.GraphQLContentTokenServiceConfig.md new file mode 100644 index 0000000000..2649d19340 --- /dev/null +++ b/ref-docs/sitecore-jss/interfaces/i18n.GraphQLContentTokenServiceConfig.md @@ -0,0 +1,218 @@ +[@sitecore-jss/sitecore-jss](../README.md) / [i18n](../modules/i18n.md) / GraphQLContentTokenServiceConfig + +# Interface: GraphQLContentTokenServiceConfig + +[i18n](../modules/i18n.md).GraphQLContentTokenServiceConfig + +Configuration options for + +**`See`** + +GraphQLContentTokenService instances + +## Hierarchy + +- `Omit`\<[`SearchQueryVariables`](graphql.SearchQueryVariables.md), ``"language"``\> + +- `CacheOptions` + +- `Pick`\<[`GraphQLRequestClientConfig`](../modules/index.md#graphqlrequestclientconfig), ``"retries"`` \| ``"retryStrategy"``\> + + ↳ **`GraphQLContentTokenServiceConfig`** + +## Table of contents + +### Properties + +- [cacheEnabled](i18n.GraphQLContentTokenServiceConfig.md#cacheenabled) +- [cacheTimeout](i18n.GraphQLContentTokenServiceConfig.md#cachetimeout) +- [clientFactory](i18n.GraphQLContentTokenServiceConfig.md#clientfactory) +- [contentTokenTemplateId](i18n.GraphQLContentTokenServiceConfig.md#contentTokenTemplateId) +- [jssAppTemplateId](i18n.GraphQLContentTokenServiceConfig.md#jssapptemplateid) +- [pageSize](i18n.GraphQLContentTokenServiceConfig.md#pagesize) +- [retries](i18n.GraphQLContentTokenServiceConfig.md#retries) +- [retryStrategy](i18n.GraphQLContentTokenServiceConfig.md#retrystrategy) +- [rootItemId](i18n.GraphQLContentTokenServiceConfig.md#rootitemid) +- [siteName](i18n.GraphQLContentTokenServiceConfig.md#sitename) +- [templates](i18n.GraphQLContentTokenServiceConfig.md#templates) + +## Properties + +### cacheEnabled + +• `Optional` **cacheEnabled**: `boolean` + +Enable/disable caching mechanism + +**`Default`** + +```ts +true +``` + +#### Inherited from + +CacheOptions.cacheEnabled + +#### Defined in + +[packages/sitecore-jss/src/cache-client.ts:40](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/cache-client.ts#L40) + +___ + +### cacheTimeout + +• `Optional` **cacheTimeout**: `number` + +Cache timeout (sec) + +**`Default`** + +```ts +60 +``` + +#### Inherited from + +CacheOptions.cacheTimeout + +#### Defined in + +[packages/sitecore-jss/src/cache-client.ts:45](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/cache-client.ts#L45) + +___ + +### clientFactory + +• **clientFactory**: [`GraphQLRequestClientFactory`](../modules/index.md#graphqlrequestclientfactory) + +A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. +This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:94](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L94) + +___ + +### contentTokenTemplateId + +• `Optional` **contentTokenTemplateId**: `string` + +Optional. The template ID to use when searching for dictionary entries. + +**`Default`** + +```ts +'7d659ee9d4874d408a9210c6d68844c8' (/sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token) +``` + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:75](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L75) + +___ + +### jssAppTemplateId + +• `Optional` **jssAppTemplateId**: `string` + +Optional. The template ID of a JSS App to use when searching for the appRootId. + +**`Default`** + +```ts +'061cba1554744b918a0617903b102b82' (/sitecore/templates/Foundation/JavaScript Services/App) +``` + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:81](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L81) + +___ + +### pageSize + +• `Optional` **pageSize**: `number` + +common variable for all GraphQL queries +it will be used for every type of query to regulate result batch size +Optional. How many result items to fetch in each GraphQL call. This is needed for pagination. + +**`Default`** + +```ts +10 +``` + +#### Inherited from + +Omit.pageSize +___ + +### retries + +• `Optional` **retries**: `number` + +Number of retries for client. Will use the specified `retryStrategy`. + +#### Inherited from + +Pick.retries + +#### Defined in + +[packages/sitecore-jss/src/graphql-request-client.ts:83](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/graphql-request-client.ts#L83) + +___ + +### retryStrategy + +• `Optional` **retryStrategy**: [`RetryStrategy`](index.RetryStrategy.md) + +Retry strategy for the client. Uses `DefaultRetryStrategy` by default with exponential +back-off factor of 2 for codes 429, 502, 503, 504, 520, 521, 522, 523, 524. + +#### Inherited from + +Pick.retryStrategy + +#### Defined in + +[packages/sitecore-jss/src/graphql-request-client.ts:88](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/graphql-request-client.ts#L88) + +___ + +### rootItemId + +• `Optional` **rootItemId**: `string` + +Optional. The ID of the search root item. Fetch items that have this item as an ancestor. + +#### Inherited from + +Omit.rootItemId +___ + +### siteName + +• **siteName**: `string` + +The name of the current Sitecore site. This is used to to determine the search query root +in cases where one is not specified by the caller. + +#### Defined in + +[packages/sitecore-jss/src/i18n/graphql-content-token-service.ts:63](https://github.com/Sitecore/jss/blob/2794c8c94/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts#L63) + +___ + +### templates + +• `Optional` **templates**: `string` + +Optional. Sitecore template ID(s). Fetch items that inherit from this template(s). + +#### Inherited from + +Omit.templates diff --git a/ref-docs/sitecore-jss/modules/i18n.md b/ref-docs/sitecore-jss/modules/i18n.md index 77d9f8dc9a..7d846c3ce0 100644 --- a/ref-docs/sitecore-jss/modules/i18n.md +++ b/ref-docs/sitecore-jss/modules/i18n.md @@ -6,14 +6,19 @@ ### Classes +- [ContentTokenServiceBase](../classes/i18n.ContentTokenServiceBase.md) - [DictionaryServiceBase](../classes/i18n.DictionaryServiceBase.md) +- [GraphQLContentTokenService](../classes/i18n.GraphQLContentTokenService.md) - [GraphQLDictionaryService](../classes/i18n.GraphQLDictionaryService.md) - [RestDictionaryService](../classes/i18n.RestDictionaryService.md) ### Interfaces +- [ContentTokenPhrases](../interfaces/i18n.ContentTokenPhrases.md) +- [ContentTokenService](../interfaces/i18n.ContentTokenService.md) - [DictionaryPhrases](../interfaces/i18n.DictionaryPhrases.md) - [DictionaryService](../interfaces/i18n.DictionaryService.md) +- [GraphQLContentTokenServiceConfig](../interfaces/i18n.GraphQLContentTokenServiceConfig.md) - [GraphQLDictionaryServiceConfig](../interfaces/i18n.GraphQLDictionaryServiceConfig.md) ### Type Aliases From 63f3886288f0c59aca26eb0e4cc8ed3031d4ee17 Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sat, 5 Oct 2024 22:26:04 +0200 Subject: [PATCH 11/14] updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd6b8f3b3c..0625b5a832 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ Our versioning strategy is as follows: * `[sitecore-jss]` GenericFieldValue model is updated to accept Date type ([#1916](https://github.com/Sitecore/jss/pull/1916)) * `[template/node-xmcloud-proxy]` `[sitecore-jss-proxy]` Introduced /api/healthz endpoint ([#1928](https://github.com/Sitecore/jss/pull/1928)) * `[sitecore-jss]` `[sitecore-jss-angular]` Render field metdata chromes in editMode metadata - in edit mode metadata in Pages, angular package field directives will render wrapping `code` elements with field metadata required for editing; ([#1926](https://github.com/Sitecore/jss/pull/1926)) +* `[sitecore-jss]` Added services for Content Tokens `/sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token` so we're a step closer to enabling (custom) content replacement tokens in headless development ### 🛠 Breaking Change From bf7662d34a6a82d849afa001ceb3124148c9d1df Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sun, 6 Oct 2024 14:03:38 +0200 Subject: [PATCH 12/14] lint fixes --- packages/sitecore-jss-nextjs/src/index.ts | 4 +-- packages/sitecore-jss/src/constants.ts | 2 +- .../src/i18n/graphql-content-token-service.ts | 27 +++++++++++-------- packages/sitecore-jss/src/i18n/index.ts | 8 ++++-- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/sitecore-jss-nextjs/src/index.ts b/packages/sitecore-jss-nextjs/src/index.ts index 15ca956c01..1ca2981304 100644 --- a/packages/sitecore-jss-nextjs/src/index.ts +++ b/packages/sitecore-jss-nextjs/src/index.ts @@ -56,8 +56,8 @@ export { GraphQLContentTokenServiceConfig, GraphQLDictionaryService, GraphQLDictionaryServiceConfig, - //TODO:RestContentTokenService, - //TODO:RestContentTokenServiceConfig, + // TODO:RestContentTokenService, + // TODO:RestContentTokenServiceConfig, RestDictionaryService, RestDictionaryServiceConfig, } from '@sitecore-jss/sitecore-jss/i18n'; diff --git a/packages/sitecore-jss/src/constants.ts b/packages/sitecore-jss/src/constants.ts index 322341ea9a..08f5746481 100644 --- a/packages/sitecore-jss/src/constants.ts +++ b/packages/sitecore-jss/src/constants.ts @@ -6,7 +6,7 @@ export enum SitecoreTemplateId { DictionaryEntry = '6d1cd89719364a3aa511289a94c2a7b1', // /sitecore/templates/Feature/Experience Accelerator/Content Tokens/Content Token - ContentToken = '7d659ee9d4874d408a9210c6d68844c8' + ContentToken = '7d659ee9d4874d408a9210c6d68844c8', } export const FETCH_WITH = { diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts index 37d68e8bd1..bfd683a99a 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.ts @@ -115,7 +115,11 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { const cacheKey = this.options.siteName + language; const cachedValue = this.getCacheValue(cacheKey); if (cachedValue) { - debug.contenttokens('using cached content token data for %s %s', language, this.options.siteName); + debug.contenttokens( + 'using cached content token data for %s %s', + language, + this.options.siteName + ); return cachedValue; } @@ -157,16 +161,17 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { let after = ''; while (hasNext) { - const fetchResponse = - await this.graphQLClient.request>(query, { - rootItemId, - language, - templates: this.options.contentTokenTemplateId || SitecoreTemplateId.ContentToken, - pageSize: this.options.pageSize, - after - }); + const fetchResponse = await this.graphQLClient.request< + SearchQueryResult + >(query, { + rootItemId, + language, + templates: this.options.contentTokenTemplateId || SitecoreTemplateId.ContentToken, + pageSize: this.options.pageSize, + after, + }); - results = results.concat(fetchResponse?.search?.results); + results = results.concat(fetchResponse?.search?.results); hasNext = fetchResponse.search.pageInfo.hasNext; after = fetchResponse.search.pageInfo.endCursor; } @@ -192,4 +197,4 @@ export class GraphQLContentTokenService extends ContentTokenServiceBase { retryStrategy: this.options.retryStrategy, }); } -} \ No newline at end of file +} diff --git a/packages/sitecore-jss/src/i18n/index.ts b/packages/sitecore-jss/src/i18n/index.ts index 5cf56f777d..5534199182 100644 --- a/packages/sitecore-jss/src/i18n/index.ts +++ b/packages/sitecore-jss/src/i18n/index.ts @@ -1,8 +1,12 @@ -export { ContentTokenPhrases, ContentTokenService, ContentTokenServiceBase } from './content-token-service'; +export { + ContentTokenPhrases, + ContentTokenService, + ContentTokenServiceBase, +} from './content-token-service'; export { GraphQLContentTokenServiceConfig, GraphQLContentTokenService, -} from './graphql-content-token-service' +} from './graphql-content-token-service'; export { DictionaryPhrases, DictionaryService, DictionaryServiceBase } from './dictionary-service'; export { GraphQLDictionaryServiceConfig, From 9ff304fc57301d1d43becc08ef53d2e5e4e4850f Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Sun, 6 Oct 2024 22:16:01 +0200 Subject: [PATCH 13/14] fixed tests --- .../src/i18n/graphql-content-token-service.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts b/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts index 68d1b18bea..33b9a32295 100644 --- a/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts +++ b/packages/sitecore-jss/src/i18n/graphql-content-token-service.test.ts @@ -31,7 +31,7 @@ describe('GraphQLContentTokenService', () => { nock.cleanAll(); }); - it('should fetch dictionary phrases using clientFactory', async () => { + it('should fetch content token phrases using clientFactory', async () => { nock(endpoint, { reqheaders: { sc_apikey: apiKey } }) .post('/', /ContentTokenSearch/gi) .reply(200, contentTokenQueryResponse); @@ -180,7 +180,7 @@ describe('GraphQLContentTokenService', () => { expect(result).to.have.all.keys('foo', 'bar'); }); - it('should use custom dictionary entry template ID, if provided', async () => { + it('should use custom content token template ID, if provided', async () => { const customTemplateId = 'custom-template-id'; nock(endpoint) @@ -192,15 +192,15 @@ describe('GraphQLContentTokenService', () => { siteName, rootItemId, cacheEnabled: false, - contentTokenEntryTemplateId: customTemplateId, + contentTokenTemplateId: customTemplateId, }); const result = await service.fetchContentTokens('en'); expect(result).to.have.all.keys('foo', 'bar'); }); - it('should use default dictionary entry template ID, if template ID not provided', async () => { + it('should use default content token template ID, if template ID not provided', async () => { nock(endpoint) - .post('/', (body) => body.variables.templates === SitecoreTemplateId.ContentTokenEntry) + .post('/', (body) => body.variables.templates === SitecoreTemplateId.ContentToken) .reply(200, contentTokenQueryResponse); const service = new GraphQLContentTokenService({ From e096c2caf5d679998037369ef95bdc31bdd4475d Mon Sep 17 00:00:00 2001 From: "Frank.van.Zutphen" Date: Fri, 6 Dec 2024 22:28:36 +0100 Subject: [PATCH 14/14] stash to experiment with lerna --- .../sitecore-jss-nextjs-typeguards/.eslintrc | 7 + .../sitecore-jss-nextjs-typeguards/.gitignore | 1 + .../sitecore-jss-nextjs-typeguards/.nycrc | 23 ++ .../LICENSE.txt | 202 ++++++++++++++++++ .../sitecore-jss-nextjs-typeguards/README.md | 11 + .../global.d.ts | 21 ++ .../package.json | 93 ++++++++ .../src/index.ts | 0 .../src/tests/enzyme-setup.ts | 18 ++ .../src/tests/jsdom-setup.ts | 42 ++++ .../src/tests/request.ts | 8 + .../src/tests/shim.ts | 13 ++ .../src/utils/index.ts | 11 + .../src/utils/utils.test.ts | 0 .../src/utils/utils.ts | 151 +++++++++++++ .../test/setup.js | 5 + .../tsconfig-esm.json | 7 + .../tsconfig.json | 29 +++ 18 files changed, 642 insertions(+) create mode 100644 packages/sitecore-jss-nextjs-typeguards/.eslintrc create mode 100644 packages/sitecore-jss-nextjs-typeguards/.gitignore create mode 100644 packages/sitecore-jss-nextjs-typeguards/.nycrc create mode 100644 packages/sitecore-jss-nextjs-typeguards/LICENSE.txt create mode 100644 packages/sitecore-jss-nextjs-typeguards/README.md create mode 100644 packages/sitecore-jss-nextjs-typeguards/global.d.ts create mode 100644 packages/sitecore-jss-nextjs-typeguards/package.json create mode 100644 packages/sitecore-jss-nextjs-typeguards/src/index.ts create mode 100644 packages/sitecore-jss-nextjs-typeguards/src/tests/enzyme-setup.ts create mode 100644 packages/sitecore-jss-nextjs-typeguards/src/tests/jsdom-setup.ts create mode 100644 packages/sitecore-jss-nextjs-typeguards/src/tests/request.ts create mode 100644 packages/sitecore-jss-nextjs-typeguards/src/tests/shim.ts create mode 100644 packages/sitecore-jss-nextjs-typeguards/src/utils/index.ts create mode 100644 packages/sitecore-jss-nextjs-typeguards/src/utils/utils.test.ts create mode 100644 packages/sitecore-jss-nextjs-typeguards/src/utils/utils.ts create mode 100644 packages/sitecore-jss-nextjs-typeguards/test/setup.js create mode 100644 packages/sitecore-jss-nextjs-typeguards/tsconfig-esm.json create mode 100644 packages/sitecore-jss-nextjs-typeguards/tsconfig.json diff --git a/packages/sitecore-jss-nextjs-typeguards/.eslintrc b/packages/sitecore-jss-nextjs-typeguards/.eslintrc new file mode 100644 index 0000000000..c8d10a3c52 --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/.eslintrc @@ -0,0 +1,7 @@ +{ + "extends": [ + "../../.eslintrc", + "../../eslint-configs/react", + "../../eslint-configs/typescript" + ] +} diff --git a/packages/sitecore-jss-nextjs-typeguards/.gitignore b/packages/sitecore-jss-nextjs-typeguards/.gitignore new file mode 100644 index 0000000000..a7c47537dc --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/.gitignore @@ -0,0 +1 @@ +types/ diff --git a/packages/sitecore-jss-nextjs-typeguards/.nycrc b/packages/sitecore-jss-nextjs-typeguards/.nycrc new file mode 100644 index 0000000000..914900df4a --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/.nycrc @@ -0,0 +1,23 @@ +{ + "extension": [ + ".ts", + ".tsx" + ], + "exclude": [ + "**/index.ts", + "**/*.d.ts", + "**/*.test.tsx", + "**/*.test.ts", + "src/tests", + "src/test-data", + "dist", + "**/sharedTypes.ts", + "*.js" + ], + "all": true, + "reporter": [ + "cobertura", + "text" + ], + "require": ["ts-node/register/transpile-only"] +} diff --git a/packages/sitecore-jss-nextjs-typeguards/LICENSE.txt b/packages/sitecore-jss-nextjs-typeguards/LICENSE.txt new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/sitecore-jss-nextjs-typeguards/README.md b/packages/sitecore-jss-nextjs-typeguards/README.md new file mode 100644 index 0000000000..b0f1d62f83 --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/README.md @@ -0,0 +1,11 @@ +# `sitecore-jss-nextjs-typeguards` + +> TODO: description + +## Usage + +``` +const sitecoreJssNextjsTypeguards = require('sitecore-jss-nextjs-typeguards'); + +// TODO: DEMONSTRATE API +``` diff --git a/packages/sitecore-jss-nextjs-typeguards/global.d.ts b/packages/sitecore-jss-nextjs-typeguards/global.d.ts new file mode 100644 index 0000000000..450b1b5b50 --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/global.d.ts @@ -0,0 +1,21 @@ +declare module 'sync-disk-cache' { + export default import('sync-disk-cache').default; + + export interface CacheInstance { + root: string; + set(key: string, editingData: string): string; + get(key: string): { value: string; isCached?: boolean }; + remove(key: string): void; + } +} + +declare namespace NodeJS { + export interface Global { + [key: string]: unknown; + requestAnimationFrame: (callback: () => void) => void; + window: Window; + document: Document; + navigator: Navigator; + HTMLElement: HTMLElement; + } +} diff --git a/packages/sitecore-jss-nextjs-typeguards/package.json b/packages/sitecore-jss-nextjs-typeguards/package.json new file mode 100644 index 0000000000..a4977edad6 --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/package.json @@ -0,0 +1,93 @@ +{ + "name": "@sitecore-jss/sitecore-jss-nextjs-typeguards", + "version": "22.2.0-canary.60", + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + "sideEffects": false, + "scripts": { + "build": "npm run clean && tsc -p tsconfig.json && tsc -p tsconfig-esm.json", + "clean": "del-cli dist types", + "lint": "eslint \"./src/**/*.tsx\" \"./src/**/*.ts\"", + "test": "mocha --require ts-node/register/transpile-only --require ./src/tests/shim.ts ./src/tests/jsdom-setup.ts ./src/tests/enzyme-setup.ts \"./src/**/*.test.tsx\" --exit", + "prepublishOnly": "npm run build", + "coverage": "nyc npm test", + "generate-docs": "npx typedoc --plugin typedoc-plugin-markdown --readme none --out ../../ref-docs/sitecore-jss-nextjs-typeguards src/index.ts --githubPages false" + }, + "engines": { + "node": ">=20" + }, + "author": { + "name": "Sitecore Corporation", + "url": "https://jss.sitecore.com" + }, + "publishConfig": { + "access": "public" + }, + "license": "Apache-2.0", + "homepage": "https://jss.sitecore.com", + "bugs": { + "url": "https://github.com/sitecore/jss/issues" + }, + "devDependencies": { + "@sitecore-cloudsdk/personalize": "^0.3.1", + "@types/chai": "^4.3.4", + "@types/chai-as-promised": "^7.1.5", + "@types/chai-string": "^1.4.2", + "@types/enzyme": "^3.10.12", + "@types/mocha": "^10.0.1", + "@types/node": "~20.14.2", + "@types/prop-types": "^15.7.5", + "@types/react": "^18.2.22", + "@types/react-dom": "^18.0.10", + "@types/sinon": "^10.0.13", + "@types/sinon-chai": "^3.2.9", + "@wojtekmaj/enzyme-adapter-react-17": "^0.8.0", + "chai": "^4.3.7", + "chai-as-promised": "^7.1.1", + "chai-string": "^1.5.0", + "chalk": "^4.1.2", + "cheerio": "1.0.0-rc.12", + "cross-fetch": "^3.1.5", + "del-cli": "^5.0.0", + "enzyme": "^3.11.0", + "eslint": "^8.32.0", + "eslint-plugin-react": "^7.32.1", + "jsdom": "^21.1.0", + "mocha": "^10.2.0", + "next": "^14.2.7", + "nock": "^13.3.0", + "nyc": "^15.1.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sinon": "^15.0.1", + "sinon-chai": "^3.7.0", + "ts-node": "^10.9.1", + "typescript": "~4.9.4" + }, + "peerDependencies": { + "@sitecore-cloudsdk/events": "^0.3.1", + "@sitecore-cloudsdk/personalize": "^0.3.1", + "next": "^14.2.7", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "dependencies": { + "@sitecore-jss/sitecore-jss": "22.2.0-canary.60", + "@sitecore-jss/sitecore-jss-dev-tools": "22.2.0-canary.60", + "@sitecore-jss/sitecore-jss-nextjs": "22.2.0-canary.60", + "@sitecore-jss/sitecore-jss-react": "22.2.0-canary.60", + "@vercel/kv": "^0.2.1", + "prop-types": "^15.8.1", + "regex-parser": "^2.2.11", + "sync-disk-cache": "^2.1.0" + }, + "description": "", + "types": "types/index.d.ts", + "gitHead": "2f4820efddf4454eeee58ed1b2cc251969efdf5b", + "files": [ + "dist", + "types", + "/*.js", + "/*.d.ts" + ] +} diff --git a/packages/sitecore-jss-nextjs-typeguards/src/index.ts b/packages/sitecore-jss-nextjs-typeguards/src/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sitecore-jss-nextjs-typeguards/src/tests/enzyme-setup.ts b/packages/sitecore-jss-nextjs-typeguards/src/tests/enzyme-setup.ts new file mode 100644 index 0000000000..b40d09a7ae --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/src/tests/enzyme-setup.ts @@ -0,0 +1,18 @@ +import { configure } from 'enzyme'; +import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; + +configure({ adapter: new Adapter() }); + +const originalLog = console.error; + +// Suppress deprecation error, since there is no react 18 upgrade for Enzyme +console.error = (msg) => { + if ( + typeof msg === 'string' && + msg.includes('ReactDOM.render is no longer supported in React 18') + ) { + return; + } + + originalLog(msg); +}; diff --git a/packages/sitecore-jss-nextjs-typeguards/src/tests/jsdom-setup.ts b/packages/sitecore-jss-nextjs-typeguards/src/tests/jsdom-setup.ts new file mode 100644 index 0000000000..6a81298342 --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/src/tests/jsdom-setup.ts @@ -0,0 +1,42 @@ +/* eslint-disable spaced-comment */ +/* eslint-disable @typescript-eslint/triple-slash-reference */ +// https://github.com/airbnb/enzyme/blob/master/docs/guides/jsdom.md + +/// + +// eslint-disable-next-line no-var +declare var global: NodeJS.Global; + +const { JSDOM } = require('jsdom'); + +const jsdom = new JSDOM('', { + url: 'https://example.com/', +}); +const jsDomWindow = jsdom.window; + +/** + * @param {unknown} src + * @param {unknown} target + */ +function copyProps(src: unknown, target: { [key: string]: unknown }) { + const props = Object.getOwnPropertyNames(src) + .filter((prop) => typeof target[prop] === 'undefined') + .reduce( + (result, prop) => ({ + ...result, + [prop]: Object.getOwnPropertyDescriptor(src, prop), + }), + {} + ); + + Object.defineProperties(target, props); +} + +global.window = jsDomWindow; +global.document = jsDomWindow.document; +global.navigator = { + userAgent: 'node.js', +} as Navigator; + +global.HTMLElement = jsDomWindow.HTMLElement; // makes chai "happy" https://github.com/chaijs/chai/issues/1029 +copyProps(jsDomWindow, global); diff --git a/packages/sitecore-jss-nextjs-typeguards/src/tests/request.ts b/packages/sitecore-jss-nextjs-typeguards/src/tests/request.ts new file mode 100644 index 0000000000..d98853500f --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/src/tests/request.ts @@ -0,0 +1,8 @@ +// Mock fetch/Request for nextjs middleware + +const crossFetch = require('cross-fetch'); + +global.fetch = crossFetch; +global.Headers = crossFetch.Headers; +global.Request = crossFetch.Request; +global.Response = crossFetch.Response; diff --git a/packages/sitecore-jss-nextjs-typeguards/src/tests/shim.ts b/packages/sitecore-jss-nextjs-typeguards/src/tests/shim.ts new file mode 100644 index 0000000000..0de7f631b3 --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/src/tests/shim.ts @@ -0,0 +1,13 @@ +/* eslint-disable spaced-comment */ +// React 16 depends on requestAnimationFrame, need a shim for node.js +// eslint-disable-next-line spaced-comment +// https://github.com/facebook/jest/issues/4545 + +/// + +// eslint-disable-next-line no-var +declare var global: NodeJS.Global; + +global.requestAnimationFrame = (callback: () => void) => { + setTimeout(callback, 0); +}; diff --git a/packages/sitecore-jss-nextjs-typeguards/src/utils/index.ts b/packages/sitecore-jss-nextjs-typeguards/src/utils/index.ts new file mode 100644 index 0000000000..9ba795fc65 --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/src/utils/index.ts @@ -0,0 +1,11 @@ +export { + isField, + isImage, + isImageFieldValue, + isItem, + isItemArray, + isLinkField, + isLinkFieldValue, + isRichTextField, + isTextField, +} from './utils'; diff --git a/packages/sitecore-jss-nextjs-typeguards/src/utils/utils.test.ts b/packages/sitecore-jss-nextjs-typeguards/src/utils/utils.test.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sitecore-jss-nextjs-typeguards/src/utils/utils.ts b/packages/sitecore-jss-nextjs-typeguards/src/utils/utils.ts new file mode 100644 index 0000000000..ee83e1868c --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/src/utils/utils.ts @@ -0,0 +1,151 @@ +import { + Field, + ImageField, + ImageFieldValue, + Item, + LinkField, + LinkFieldValue, + RichTextField, + TextField, +} from '@sitecore-jss/sitecore-jss-nextjs'; + +/** + * typeguard for Field + * @param {Field | T} [input] + * @returns {boolean} + */ +export function isField(input: Field | T): input is Field { + if (input === null) return false; + if (typeof input !== 'object') return false; + if (Array.isArray(input)) return false; + + const result = + 'value' in (input as Field) && + Object.keys(input as Field).length >= 1 && + Object.keys(input as Field).length <= 2; + + return result; +} + +/** + * typeguard for ImageField + * @param {ImageField | T} [input] + * @returns {boolean} + */ +export function isImage(input: ImageField | T): input is ImageField { + const imageField = input as ImageField; + const result = + 'value' in imageField && + isImageFieldValue(imageField.value) && + Object.keys(imageField).length >= 1 && + Object.keys(imageField).length <= 2; + + return result; +} + +/** + * typeguard for ImageFieldValue + * @param {ImageFieldValue | T} [input] + * @returns {boolean} + */ +export function isImageFieldValue( + input: ImageFieldValue | T +): input is ImageFieldValue { + const imageFieldValue = input as ImageFieldValue; + const result = 'src' in imageFieldValue && Object.keys(imageFieldValue).length >= 1; + + return result; +} + +/** + * typeguard for Item + * @param {Item | T} [input] + * @returns {boolean} + */ +export function isItem(input: Item | T): input is Item { + const item = input as Item; + if (input === null) return false; + if (typeof input !== 'object') return false; + if (Array.isArray(input)) return false; + + const result = + 'fields' in item && + 'name' in item && + Object.keys(item).length >= 2 && + Object.keys(item).length <= 5; + + return result; +} + +/** + * typeguard for Array + * @param {Item[] | T} [input] + * @returns {boolean} + */ +export function isItemArray(input: Item[] | T): input is Item[] { + if (input === null) return false; + if (!Array.isArray(input)) return false; + + return (input as Item[]).every(isItem); +} + +/** + * typeguard for LinkFields. + * @param {LinkField | T} [input] + * @returns {boolean} + */ +export function isLinkField(input: LinkField | T): input is LinkField { + const linkField = input as LinkField; + const result = + 'value' in linkField && + isLinkFieldValue(linkField.value) && + Object.keys(linkField).length >= 1 && + Object.keys(linkField).length <= 3; + + return result; +} + +/** + * typeguard for LinkFieldValue + * @param {LinkFieldValue | T} [input] + * @returns {boolean} + */ +export function isLinkFieldValue( + input: LinkFieldValue | T +): input is LinkFieldValue { + const linkFieldValue = input as LinkFieldValue; + const result = '' in linkFieldValue; + + return result; +} + +/** + * typeguard for RichTextField + * @param {RichTextField | T} [input] + * @returns {boolean} + */ +export function isRichTextField(input: RichTextField | T): input is RichTextField { + const richtextfield = input as RichTextField; + const result = + 'value' in richtextfield && + typeof richtextfield.value === 'string' && + Object.keys(richtextfield).length >= 1 && + Object.keys(richtextfield).length <= 2; + + return result; +} + +/** + * typeguard for TextField + * @param {TextField | T} [input] + * @returns {boolean} + */ +export function isTextField(input: TextField | T): input is TextField { + const textField = input as TextField; + const result = + 'value' in textField && + Object.keys(textField).length >= 1 && + Object.keys(textField).length <= 2; + + return result; +} diff --git a/packages/sitecore-jss-nextjs-typeguards/test/setup.js b/packages/sitecore-jss-nextjs-typeguards/test/setup.js new file mode 100644 index 0000000000..ba74f06621 --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/test/setup.js @@ -0,0 +1,5 @@ +require('ts-node/register/transpile-only'); +require('../src/tests/request.ts'); +require('../src/tests/shim.ts'); +require('../src/tests/jsdom-setup.ts'); +require('../src/tests/enzyme-setup.ts'); diff --git a/packages/sitecore-jss-nextjs-typeguards/tsconfig-esm.json b/packages/sitecore-jss-nextjs-typeguards/tsconfig-esm.json new file mode 100644 index 0000000000..eb58bc1628 --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/tsconfig-esm.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "es6", + "outDir": "dist/esm" + }, +} diff --git a/packages/sitecore-jss-nextjs-typeguards/tsconfig.json b/packages/sitecore-jss-nextjs-typeguards/tsconfig.json new file mode 100644 index 0000000000..9d2cf9eaff --- /dev/null +++ b/packages/sitecore-jss-nextjs-typeguards/tsconfig.json @@ -0,0 +1,29 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "jsx": "react", + "skipLibCheck": true, + "outDir": "dist/cjs", + "typeRoots": ["node_modules/@types"], + "declarationDir": "./types" + }, + "exclude": [ + "node_modules", + "types", + "typings", + "dist", + "context.d.ts", + "middleware.d.ts", + "editing.d.ts", + "monitoring.d.ts", + "site.d.ts", + "graphql.d.ts", + "utils.d.ts", + "src/tests/*", + "src/test-data/*", + "**/*.test.ts", + "**/*.test.tsx" + ] +}