Skip to content

Commit

Permalink
Introduced interfaces for LayoutService and DictionaryService, existi…
Browse files Browse the repository at this point in the history
…ng (REST-based) implementations renamed with "Rest" prefix. This sets us up for introducing GraphQL-based Edge implementations (i.e. "GraphQLLayoutService" and "GraphQLDictionaryService") in next release.
  • Loading branch information
Adam Brauer committed Feb 10, 2021
1 parent 9629740 commit 8f59667
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 57 deletions.
6 changes: 4 additions & 2 deletions packages/sitecore-jss-nextjs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ export {
AxiosDataFetcher,
AxiosDataFetcherConfig,
LayoutService,
LayoutServiceInstanceConfig,
RestLayoutService,
RestLayoutServiceConfig,
DictionaryPhrases,
DictionaryServiceData,
DictionaryService,
DictionaryServiceConfig,
RestDictionaryService,
RestDictionaryServiceConfig,
LayoutServiceData,
LayoutServicePageState,
LayoutServiceContext,
Expand Down
12 changes: 6 additions & 6 deletions packages/sitecore-jss/src/dictionary-service.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/* eslint-disable no-unused-expressions */
import { expect, spy, use } from 'chai';
import spies from 'chai-spies';
import { DictionaryService } from './dictionary-service';
import { RestDictionaryService } from './dictionary-service';
import { DictionaryServiceData } from './dataModels';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { AxiosDataFetcher } from './data-fetcher';

use(spies);

describe('DictionaryService', () => {
describe('RestDictionaryService', () => {
let mock: MockAdapter;

before(() => {
Expand All @@ -29,7 +29,7 @@ describe('DictionaryService', () => {
return [200, { ...config, status: 200, statusText: 'ok', phrases: { x: 'x1', y: 'y2' } }];
});

const service = new DictionaryService({
const service = new RestDictionaryService({
apiHost: 'http://sctest',
apiKey: '0FBFF61E-267A-43E3-9252-B77E71CEE4BA',
siteName: 'supersite',
Expand All @@ -55,7 +55,7 @@ describe('DictionaryService', () => {
return [200, { ...config, status: 200, statusText: 'ok', phrases: { x: 'x1', y: 'y2' } }];
});

const service = new DictionaryService({
const service = new RestDictionaryService({
apiHost: 'http://sctest',
apiKey: '0FBFF61E-267A-43E3-9252-B77E71CEE4BA',
siteName: 'supersite',
Expand Down Expand Up @@ -94,7 +94,7 @@ describe('DictionaryService', () => {
return [200, { ...config, status: 200, statusText: 'ok', phrases: { x: 'x1', y: 'y2' } }];
});

const service = new DictionaryService({
const service = new RestDictionaryService({
apiHost: 'http://sctest',
apiKey: '0FBFF61E-267A-43E3-9252-B77E71CEE4BA',
siteName: 'supersite',
Expand Down Expand Up @@ -149,7 +149,7 @@ describe('DictionaryService', () => {
return [200, { ...config, status: 200, statusText: 'ok', phrases: { x: 'q1', y: 'w2' } }];
});

const service = new DictionaryService({
const service = new RestDictionaryService({
apiHost: 'http://sctest',
apiKey: '0FBFF61E-267A-43E3-9252-B77E71CEE4BA',
siteName: 'supersite',
Expand Down
20 changes: 16 additions & 4 deletions packages/sitecore-jss/src/dictionary-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@ import { fetchData } from './dataApi';
import { DictionaryPhrases, DictionaryServiceData } from './dataModels';
import { HttpJsonFetcher } from './httpClientInterface';

export type DictionaryServiceConfig = {
export interface DictionaryService {
/**
* Fetch dictionary data for a language.
* @param {string} language
*/
fetchDictionaryData(language: string): Promise<DictionaryPhrases>;
}

export type RestDictionaryServiceConfig = {
/**
* Your Sitecore instance hostname that is the backend for JSS
*/
Expand Down Expand Up @@ -34,18 +42,22 @@ export type DictionaryServiceConfig = {
cacheTimeout?: number;
};

export class DictionaryService {
/**
* Fetch dictionary data using the Sitecore Dictionary Service REST API.
* Uses Axios as the default data fetcher (@see AxiosDataFetcher).
*/
export class RestDictionaryService implements DictionaryService {
dictionaryCache: NodeCache;
STD_TTL = 60;

constructor(private dictionaryServiceConfig: DictionaryServiceConfig) {
constructor(private dictionaryServiceConfig: RestDictionaryServiceConfig) {
this.dictionaryCache = new NodeCache({
stdTTL: dictionaryServiceConfig.cacheTimeout || this.STD_TTL,
});
}

/**
* Fetch dictionary data
* Fetch dictionary data for a language.
* @param {string} language
*/
async fetchDictionaryData(language: string): Promise<DictionaryPhrases> {
Expand Down
13 changes: 11 additions & 2 deletions packages/sitecore-jss/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,18 @@ export { LayoutServiceRequestOptions } from './dataApi';

export { AxiosDataFetcher, AxiosDataFetcherConfig } from './data-fetcher';

export { LayoutService, LayoutServiceInstanceConfig, DataFetcherResolver } from './layout-service';
export {
LayoutService,
RestLayoutService,
RestLayoutServiceConfig,
DataFetcherResolver,
} from './layout-service';

export { DictionaryService, DictionaryServiceConfig } from './dictionary-service';
export {
DictionaryService,
RestDictionaryService,
RestDictionaryServiceConfig,
} from './dictionary-service';

export { isExperienceEditorActive, isServer, resetExperienceEditorChromes } from './util';

Expand Down
16 changes: 8 additions & 8 deletions packages/sitecore-jss/src/layout-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { expect, spy, use } from 'chai';
import spies from 'chai-spies';
import { LayoutService } from './layout-service';
import { RestLayoutService } from './layout-service';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { IncomingMessage, ServerResponse } from 'http';
Expand All @@ -12,7 +12,7 @@ import { LayoutServiceData, PlaceholderData } from './dataModels';

use(spies);

describe('LayoutService', () => {
describe('RestLayoutService', () => {
let mock: MockAdapter;

before(() => {
Expand All @@ -32,7 +32,7 @@ describe('LayoutService', () => {
return [200, { ...config, data: { sitecore: { context: {}, route: { name: 'xxx' } } } }];
});

const service = new LayoutService({
const service = new RestLayoutService({
apiHost: 'http://sctest',
apiKey: '0FBFF61E-267A-43E3-9252-B77E71CEE4BA',
siteName: 'supersite',
Expand Down Expand Up @@ -82,7 +82,7 @@ describe('LayoutService', () => {
setHeader: setHeaderSpy,
} as ServerResponse;

const service = new LayoutService({
const service = new RestLayoutService({
apiHost: 'http://sctest',
apiKey: '0FBFF61E-267A-43E3-9252-B77E71CEE4BA',
siteName: 'supersite',
Expand Down Expand Up @@ -139,7 +139,7 @@ describe('LayoutService', () => {
setHeader: setHeaderSpy,
} as ServerResponse;

const service = new LayoutService({
const service = new RestLayoutService({
apiHost: 'http://sctest',
apiKey: '0FBFF61E-267A-43E3-9252-B77E71CEE4BA',
siteName: 'supersite',
Expand Down Expand Up @@ -174,7 +174,7 @@ describe('LayoutService', () => {
return [200, { sitecore: { context: {}, route: { name: 'xxx' } } }];
});

const service = new LayoutService({
const service = new RestLayoutService({
apiHost: 'http://sctest',
apiKey: '0FBFF61E-267A-43E3-9252-B77E71CEE4BA',
siteName: 'supersite',
Expand Down Expand Up @@ -230,7 +230,7 @@ describe('LayoutService', () => {
setHeader: setHeaderSpy,
} as ServerResponse;

const service = new LayoutService({
const service = new RestLayoutService({
apiHost: 'http://sctest',
apiKey: '0FBFF61E-267A-43E3-9252-B77E71CEE4BA',
siteName: 'supersite',
Expand Down Expand Up @@ -265,7 +265,7 @@ describe('LayoutService', () => {
];
});

const service = new LayoutService({
const service = new RestLayoutService({
apiHost: 'http://sctest',
apiKey: '0FBFF61E-267A-43E3-9252-B77E71CEE4BA',
siteName: 'supersite',
Expand Down
33 changes: 27 additions & 6 deletions packages/sitecore-jss/src/layout-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,29 @@ import { HttpJsonFetcher } from './httpClientInterface';
import { AxiosRequestConfig, AxiosResponse } from 'axios';
import { IncomingMessage, ServerResponse } from 'http';

export interface LayoutService {
/**
* Fetch layout data for an item.
* @param {string} itemPath
* @param {string} [language]
* @param {IncomingMessage} [req] Request instance
* @param {ServerResponse} [res] Response instance
* @returns {Promise<LayoutServiceData>} layout data
*/
fetchLayoutData(
itemPath: string,
language?: string,
req?: IncomingMessage,
res?: ServerResponse
): Promise<LayoutServiceData>;
}

export type DataFetcherResolver = <T>(
req?: IncomingMessage,
res?: ServerResponse
) => HttpJsonFetcher<T>;

export type LayoutServiceInstanceConfig = {
export type RestLayoutServiceConfig = {
/**
* Your Sitecore instance hostname that is the backend for JSS
*/
Expand Down Expand Up @@ -54,11 +71,15 @@ interface FetchOptions {
querystringParams: FetchParams;
}

export class LayoutService {
constructor(private serviceConfig: LayoutServiceInstanceConfig) {}
/**
* Fetch layout data using the Sitecore Layout Service REST API.
* Uses Axios as the default data fetcher (@see AxiosDataFetcher).
*/
export class RestLayoutService implements LayoutService {
constructor(private serviceConfig: RestLayoutServiceConfig) {}

/**
* Fetch route data from LayoutService using @see dataApi.fetchRouteData
* Fetch layout data for an item.
* @param {string} itemPath
* @param {string} [language]
* @param {IncomingMessage} [req] Request instance
Expand All @@ -81,7 +102,7 @@ export class LayoutService {
}

/**
* Fetch route data from LayoutService using @see dataApi.fetchPlaceholderData
* Fetch layout data for a particular placeholder.
* Makes a request to Sitecore Layout Service for the specified placeholder in
* a specific route item. Allows you to retrieve rendered data for individual placeholders instead of entire routes.
* @param {string} placeholderName
Expand All @@ -108,7 +129,7 @@ export class LayoutService {
}

/**
* Provides fetch options in order to fetch route data
* Provides fetch options in order to fetch data
* @param {string} [language] language will be applied to `sc_lang` param
* @returns {FetchOptions} fetch options
*/
Expand Down
53 changes: 24 additions & 29 deletions samples/nextjs/src/lib/page-props-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { ParsedUrlQuery } from 'querystring';
import { GetServerSidePropsContext, GetStaticPropsContext } from 'next';
import {
ComponentPropsService,
DictionaryService,
LayoutService,
DictionaryPhrases,
DictionaryService,
RestDictionaryService,
LayoutServiceData,
LayoutService,
RestLayoutService,
editingDataService,
} from '@sitecore-jss/sitecore-jss-nextjs';
import { SitecorePageProps } from 'lib/page-props';
Expand Down Expand Up @@ -44,37 +46,26 @@ const isServerSidePropsContext = function (

export class SitecorePagePropsFactory {
private componentPropsService: ComponentPropsService;
private _dictionaryService: DictionaryService;
private _layoutService: LayoutService;
private dictionaryService: DictionaryService;
private layoutService: LayoutService;

constructor() {
this.componentPropsService = new ComponentPropsService();
this._dictionaryService = new DictionaryService({

// Note we're using our standard REST-based dictionary and layout services here,
// but in the very near future we'll also have GraphQL-based counterparts available (for Sitecore Experience Edge).
this.dictionaryService = new RestDictionaryService({
apiHost: config.sitecoreApiHost,
apiKey: config.sitecoreApiKey,
siteName: config.jssAppName,
});
this._layoutService = new LayoutService({
this.layoutService = new RestLayoutService({
apiHost: config.sitecoreApiHost,
apiKey: config.sitecoreApiKey,
siteName: config.jssAppName,
});
}

private get layoutService(): LayoutService {
// Just returning our REST layout service atm, but in the very
// near future we'll also have a GraphQL-based layout service.
// Stubbed out as getter for potential logic here (e.g. based on constructor props)...
return this._layoutService;
}

private get dictionaryService(): DictionaryService {
// Just returning our REST dictionary service atm, but in the very
// near future we'll also have a GraphQL-based dictionary service.
// Stubbed out as getter for potential logic here (e.g. based on constructor props)...
return this._dictionaryService;
}

/**
* Create SitecorePageProps for given context (SSR / GetServerSidePropsContext or SSG / GetStaticPropsContext)
* @param {GetServerSidePropsContext | GetStaticPropsContext} context
Expand Down Expand Up @@ -113,7 +104,7 @@ export class SitecorePagePropsFactory {
// Use context locale if Next.js i18n is configured, otherwise use language defined in package.json
locale = context.locale ?? packageConfig.language;

// Fetch layoutData from Layout Service, passing on req/res for SSR
// Fetch layout data, passing on req/res for SSR
layoutData = await this.layoutService
.fetchLayoutData(
path,
Expand All @@ -122,18 +113,22 @@ export class SitecorePagePropsFactory {
isServerSidePropsContext(context) ? (context as GetServerSidePropsContext).req : undefined,
isServerSidePropsContext(context) ? (context as GetServerSidePropsContext).res : undefined
)
.catch((error: AxiosError<LayoutServiceData>) => {
if (error.response?.status === 404) {
// Let 404s (invalid path) through.
// layoutData.sitecore.route will be missing, but
// layoutData.sitecore.context will provide valuable information
notFound = true;
return error.response.data;
.catch((error) => {
if (error.isAxiosError) {
// RestLayoutService uses Axios by default
const axiosError = error as AxiosError<LayoutServiceData>;
if (axiosError.response?.status === 404) {
// Let 404s (invalid path) through for RestLayoutService.
// layoutData.sitecore.route will be missing, but
// layoutData.sitecore.context will provide valuable information
notFound = true;
return axiosError.response?.data;
}
}
throw error;
});

// Fetch dictionary data from Dictionary Service
// Fetch dictionary data
dictionary = await this.dictionaryService.fetchDictionaryData(locale);
}

Expand Down

0 comments on commit 8f59667

Please sign in to comment.